altium-toolkit 1.1.1 → 1.1.3

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 (40) hide show
  1. package/package.json +1 -1
  2. package/src/core/altium/AltiumLayoutParser.mjs +275 -13
  3. package/src/core/altium/AltiumParser.mjs +240 -8
  4. package/src/core/altium/NaturalStringComparator.mjs +175 -0
  5. package/src/core/altium/ParserUtils.mjs +83 -14
  6. package/src/core/altium/PcbComponentPrimitiveIndexer.mjs +104 -33
  7. package/src/core/altium/PcbEmbeddedFontExtractor.mjs +186 -43
  8. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +26 -4
  9. package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +28 -5
  10. package/src/core/altium/PcbOwnershipGraphBuilder.mjs +1 -3
  11. package/src/core/altium/PcbReviewMetadataBuilder.mjs +59 -28
  12. package/src/core/altium/PcbReviewRouteHighlightProfileBuilder.mjs +66 -29
  13. package/src/core/altium/PcbRouteAnalysisBuilder.mjs +50 -16
  14. package/src/core/altium/PrintableTextDecoder.mjs +133 -13
  15. package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +13 -0
  16. package/src/core/altium/SchematicComponentTextResolver.mjs +40 -1
  17. package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +36 -11
  18. package/src/core/altium/SchematicImageParser.mjs +291 -6
  19. package/src/core/altium/SchematicImplementationParser.mjs +75 -36
  20. package/src/core/altium/SchematicMultipartDesignatorNormalizer.mjs +164 -0
  21. package/src/core/altium/SchematicMultipartOwnerMatcher.mjs +2 -0
  22. package/src/core/altium/SchematicPinParser.mjs +175 -4
  23. package/src/core/altium/SchematicQaReportBuilder.mjs +115 -38
  24. package/src/core/altium/SchematicSheetStyleResolver.mjs +38 -0
  25. package/src/core/altium/SchematicTextParser.mjs +125 -11
  26. package/src/core/altium/SchematicTextPostProcessor.mjs +146 -102
  27. package/src/ui/PcbInteractionIndex.mjs +16 -1
  28. package/src/ui/SchematicColorResolver.mjs +78 -0
  29. package/src/ui/SchematicContentLayout.mjs +58 -1
  30. package/src/ui/SchematicImageRenderer.mjs +125 -10
  31. package/src/ui/SchematicJunctionRenderer.mjs +1 -1
  32. package/src/ui/SchematicNativeFooterPartitioner.mjs +275 -0
  33. package/src/ui/SchematicNoteRenderer.mjs +82 -6
  34. package/src/ui/SchematicOwnerPinLabelLayout.mjs +292 -3
  35. package/src/ui/SchematicPinSvgRenderer.mjs +197 -15
  36. package/src/ui/SchematicPowerDiagramImageProcessor.mjs +970 -0
  37. package/src/ui/SchematicPowerDiagramLineMasks.mjs +631 -0
  38. package/src/ui/SchematicPowerPortRenderer.mjs +1 -1
  39. package/src/ui/SchematicShapeRenderer.mjs +82 -23
  40. package/src/ui/SchematicSvgRenderer.mjs +293 -47
@@ -12,10 +12,15 @@ export class SchematicPinParser {
12
12
  /**
13
13
  * Normalizes schematic pin records into drawable pin primitives.
14
14
  * @param {{ fields: Record<string, string | string[]> }[]} records
15
+ * @param {{ ownerDrawnInternalPinOwners?: Set<string>, numericEndpointLabelOwners?: Set<string> }} [options]
15
16
  * @returns {{ x: number, y: number, length: number, name: string, nameSegments?: { text: string, overline: boolean }[], designator: string, orientation: 'left' | 'right' | 'top' | 'bottom', electrical?: number, symbolOuter?: number, color: string, labelColor: string, labelMode: 'hidden' | 'number-only' | 'name-only' | 'name-and-number', ownerIndex: string }[]}
16
17
  */
17
- static parseSchematicPins(records) {
18
+ static parseSchematicPins(records, options = {}) {
18
19
  const groups = new Map()
20
+ const ownerDrawnInternalPinOwners =
21
+ options.ownerDrawnInternalPinOwners || new Set()
22
+ const numericEndpointLabelOwners =
23
+ options.numericEndpointLabelOwners || new Set()
19
24
 
20
25
  for (const record of records) {
21
26
  const ownerIndex = ParserUtils.getField(record.fields, 'OwnerIndex')
@@ -81,7 +86,11 @@ export class SchematicPinParser {
81
86
  }
82
87
 
83
88
  return [...groups.values()].flatMap((pins) =>
84
- SchematicPinParser.#normalizeSchematicPinGroup(pins)
89
+ SchematicPinParser.#normalizeSchematicPinGroup(
90
+ pins,
91
+ ownerDrawnInternalPinOwners,
92
+ numericEndpointLabelOwners
93
+ )
85
94
  )
86
95
  }
87
96
 
@@ -730,9 +739,15 @@ export class SchematicPinParser {
730
739
  /**
731
740
  * Deduces the visible pins for one schematic symbol owner.
732
741
  * @param {{ x: number, y: number, length: number, conglomerate?: number, name: string, nameSegments?: { text: string, overline: boolean }[], designator: string, orientation: 'left' | 'right' | 'top' | 'bottom', electrical?: number, symbolOuter?: number, color?: string, labelColor?: string, ownerIndex: string }[]} pins
742
+ * @param {Set<string>} ownerDrawnInternalPinOwners
743
+ * @param {Set<string>} numericEndpointLabelOwners
733
744
  * @returns {{ x: number, y: number, length: number, name: string, nameSegments?: { text: string, overline: boolean }[], designator: string, orientation: 'left' | 'right' | 'top' | 'bottom', electrical?: number, symbolOuter?: number, color: string, labelColor: string, labelMode: 'hidden' | 'number-only' | 'name-only' | 'name-and-number', ownerIndex: string }[]}
734
745
  */
735
- static #normalizeSchematicPinGroup(pins) {
746
+ static #normalizeSchematicPinGroup(
747
+ pins,
748
+ ownerDrawnInternalPinOwners,
749
+ numericEndpointLabelOwners
750
+ ) {
736
751
  const deduped = SchematicPinParser.#dedupeSchematicPins(pins)
737
752
  const inferredSequentialDesignators =
738
753
  SchematicPinDesignatorInferer.inferSequentialCompactFourPinDesignators(
@@ -773,6 +788,7 @@ export class SchematicPinParser {
773
788
  /^\d+$/.test(String(pin.designator || '').trim()) &&
774
789
  (!pin.name || /^\d+$/.test(String(pin.name || '').trim()))
775
790
  )
791
+ const ownerIndex = normalizedPins[0]?.ownerIndex || ''
776
792
  let labelMode = 'name-and-number'
777
793
 
778
794
  if (
@@ -795,12 +811,26 @@ export class SchematicPinParser {
795
811
  labelMode = 'number-only'
796
812
  }
797
813
 
798
- if (allPassive && normalizedPins.length <= 2) {
814
+ if (
815
+ numericEndpointLabelOwners.has(ownerIndex) &&
816
+ SchematicPinParser.#isTwoPinNumericEndpointGroup(normalizedPins)
817
+ ) {
818
+ labelMode = 'number-only'
819
+ } else if (allPassive && normalizedPins.length <= 2) {
799
820
  labelMode = SchematicPinParser.#isCanonicalPassiveTwoPinGroup(
800
821
  normalizedPins
801
822
  )
802
823
  ? 'hidden'
803
824
  : 'number-only'
825
+ } else if (
826
+ ownerDrawnInternalPinOwners.has(ownerIndex) &&
827
+ SchematicPinParser.#isCompactNumberedFetTerminalGroup(
828
+ normalizedPins,
829
+ semanticNames,
830
+ orientationCount
831
+ )
832
+ ) {
833
+ labelMode = 'number-only'
804
834
  } else if (
805
835
  SchematicPinParser.#isOwnerDrawnTerminalGlyphGroup(
806
836
  normalizedPins,
@@ -809,6 +839,24 @@ export class SchematicPinParser {
809
839
  )
810
840
  ) {
811
841
  labelMode = 'hidden'
842
+ } else if (
843
+ ownerDrawnInternalPinOwners.has(ownerIndex) &&
844
+ SchematicPinParser.#isCompactInternalTerminalGroup(
845
+ normalizedPins,
846
+ names,
847
+ orientationCount
848
+ )
849
+ ) {
850
+ labelMode = 'hidden'
851
+ } else if (
852
+ ownerDrawnInternalPinOwners.has(ownerIndex) &&
853
+ SchematicPinParser.#isCompactTwoPinInternalTerminalGroup(
854
+ normalizedPins,
855
+ names,
856
+ orientationCount
857
+ )
858
+ ) {
859
+ labelMode = 'number-only'
812
860
  } else if (!semanticNames.length && orientationCount <= 2) {
813
861
  labelMode = 'number-only'
814
862
  } else if (
@@ -827,6 +875,33 @@ export class SchematicPinParser {
827
875
  }))
828
876
  }
829
877
 
878
+ /**
879
+ * Returns true when a compact owner-drawn FET body uses semantic terminal
880
+ * names internally but still exposes external numeric contact labels.
881
+ * @param {{ designator: string, name: string, orientation: 'left' | 'right' | 'top' | 'bottom' }[]} pins
882
+ * @param {string[]} semanticNames
883
+ * @param {number} orientationCount
884
+ * @returns {boolean}
885
+ */
886
+ static #isCompactNumberedFetTerminalGroup(
887
+ pins,
888
+ semanticNames,
889
+ orientationCount
890
+ ) {
891
+ if (
892
+ pins.length !== 4 ||
893
+ orientationCount < 3 ||
894
+ semanticNames.length !== pins.length ||
895
+ !pins.every((pin) => /^\d+$/.test(String(pin.designator || '')))
896
+ ) {
897
+ return false
898
+ }
899
+
900
+ return semanticNames.every((name) =>
901
+ SchematicPinParser.#isFetTerminalName(name)
902
+ )
903
+ }
904
+
830
905
  /**
831
906
  * Returns true when a compact multi-side owner has transistor-like terminal
832
907
  * letters that are part of the drawn symbol body, not external pin labels.
@@ -858,6 +933,60 @@ export class SchematicPinParser {
858
933
  )
859
934
  }
860
935
 
936
+ /**
937
+ * Returns true when a compact owner-drawn body carries repeated internal
938
+ * terminal names that belong to the symbol body, not external labels.
939
+ * @param {{ designator: string, name: string, orientation: 'left' | 'right' | 'top' | 'bottom' }[]} pins
940
+ * @param {string[]} names
941
+ * @param {number} orientationCount
942
+ * @returns {boolean}
943
+ */
944
+ static #isCompactInternalTerminalGroup(pins, names, orientationCount) {
945
+ if (
946
+ pins.length < 3 ||
947
+ pins.length > 4 ||
948
+ orientationCount < 3 ||
949
+ names.length >= pins.length ||
950
+ !SchematicPinParser.#hasOptionalNumericPinDesignators(pins)
951
+ ) {
952
+ return false
953
+ }
954
+
955
+ return (
956
+ names.length > 0 &&
957
+ names.every((name) =>
958
+ SchematicPinParser.#isInternalTerminalName(name)
959
+ )
960
+ )
961
+ }
962
+
963
+ /**
964
+ * Returns true when a compact two-pin owner-drawn symbol stores internal
965
+ * placeholder terminal names that should not be rendered as labels.
966
+ * @param {{ designator: string, name: string, orientation: 'left' | 'right' | 'top' | 'bottom' }[]} pins
967
+ * @param {string[]} names
968
+ * @param {number} orientationCount
969
+ * @returns {boolean}
970
+ */
971
+ static #isCompactTwoPinInternalTerminalGroup(
972
+ pins,
973
+ names,
974
+ orientationCount
975
+ ) {
976
+ if (
977
+ pins.length !== 2 ||
978
+ orientationCount < 2 ||
979
+ names.length !== pins.length ||
980
+ !SchematicPinParser.#hasOptionalNumericPinDesignators(pins)
981
+ ) {
982
+ return false
983
+ }
984
+
985
+ return names.every((name) =>
986
+ SchematicPinParser.#isInternalTerminalName(name)
987
+ )
988
+ }
989
+
861
990
  /**
862
991
  * Returns true when compact owner-drawn terminal glyph pins have either no
863
992
  * external designators or ordinary numeric pin numbers.
@@ -881,6 +1010,28 @@ export class SchematicPinParser {
881
1010
  return /^[BCDEGS]$/i.test(String(name || '').trim())
882
1011
  }
883
1012
 
1013
+ /**
1014
+ * Returns true for FET terminal names, including numbered gate/source
1015
+ * variants used by dual-gate symbols.
1016
+ * @param {string} name
1017
+ * @returns {boolean}
1018
+ */
1019
+ static #isFetTerminalName(name) {
1020
+ return /^(?:[DS]|[GS]\d*)$/i.test(String(name || '').trim())
1021
+ }
1022
+
1023
+ /**
1024
+ * Returns true for compact internal terminal labels usually drawn inside
1025
+ * owner-authored symbol bodies.
1026
+ * @param {string} name
1027
+ * @returns {boolean}
1028
+ */
1029
+ static #isInternalTerminalName(name) {
1030
+ return /^(x|y|gnd|agnd|dgnd|pgnd|vcc|vdd|vee|vss|nc)$/i.test(
1031
+ String(name || '').trim()
1032
+ )
1033
+ }
1034
+
884
1035
  /**
885
1036
  * Returns true when one passive two-pin symbol uses the ordinary 1/2 pin
886
1037
  * numbering that should stay hidden for simple resistor-like parts.
@@ -899,6 +1050,24 @@ export class SchematicPinParser {
899
1050
  return designators[0] === '1' && designators[1] === '2'
900
1051
  }
901
1052
 
1053
+ /**
1054
+ * Returns true when one owner exposes exactly two numeric endpoints.
1055
+ * @param {{ designator: string, name: string }[]} pins
1056
+ * @returns {boolean}
1057
+ */
1058
+ static #isTwoPinNumericEndpointGroup(pins) {
1059
+ if (pins.length !== 2) {
1060
+ return false
1061
+ }
1062
+
1063
+ return pins.every((pin) => {
1064
+ const designator = String(pin.designator || '').trim()
1065
+ const name = String(pin.name || '').trim()
1066
+
1067
+ return /^\d+$/.test(designator) && (!name || /^\d+$/.test(name))
1068
+ })
1069
+ }
1070
+
902
1071
  /**
903
1072
  * Returns true when one owner uses the dense two-sided horizontal 48/50
904
1073
  * pin family whose semantic names belong to the owner-drawn symbol body
@@ -1049,10 +1218,12 @@ export class SchematicPinParser {
1049
1218
  static #inferSchematicPinOrientation(conglomerate) {
1050
1219
  switch (conglomerate) {
1051
1220
  case 34:
1221
+ case 42:
1052
1222
  case 50:
1053
1223
  case 58:
1054
1224
  return 'left'
1055
1225
  case 32:
1226
+ case 40:
1056
1227
  case 48:
1057
1228
  case 56:
1058
1229
  return 'right'
@@ -4,8 +4,7 @@
4
4
 
5
5
  import { ParserUtils } from './ParserUtils.mjs'
6
6
 
7
- const { getDisplayText, getField, parseBoolean, parseNumericField, toColor } =
8
- ParserUtils
7
+ const { parseBoolean, parseNumericField, toColor } = ParserUtils
9
8
 
10
9
  /**
11
10
  * Builds deterministic read-only QA summaries for schematic documents.
@@ -25,12 +24,17 @@ export class SchematicQaReportBuilder {
25
24
  const lineWidths = SchematicQaReportBuilder.#lineWidths(records)
26
25
  const unresolvedParameters =
27
26
  SchematicQaReportBuilder.#unresolvedParameters(records)
27
+ const titleBlockResidue =
28
+ SchematicQaReportBuilder.#titleBlockResidue(records)
28
29
  const findings = [
29
30
  ...SchematicQaReportBuilder.#fontFindings(fonts),
30
31
  ...SchematicQaReportBuilder.#unresolvedFindings(
31
32
  unresolvedParameters
32
33
  ),
33
- ...SchematicQaReportBuilder.#titleBlockFindings(records)
34
+ ...SchematicQaReportBuilder.#titleBlockFindings(
35
+ records,
36
+ titleBlockResidue
37
+ )
34
38
  ]
35
39
 
36
40
  return {
@@ -47,8 +51,7 @@ export class SchematicQaReportBuilder {
47
51
  colors,
48
52
  lineWidths,
49
53
  unresolvedParameters,
50
- titleBlockResidue:
51
- SchematicQaReportBuilder.#titleBlockResidue(records),
54
+ titleBlockResidue,
52
55
  geometryFallbacks: SchematicQaReportBuilder.#geometryFallbacks(
53
56
  input?.texts
54
57
  ),
@@ -138,26 +141,15 @@ export class SchematicQaReportBuilder {
138
141
  * @returns {string[]}
139
142
  */
140
143
  static #unresolvedParameters(records) {
141
- const metadata = new Set(
142
- (records || [])
143
- .filter((record) => getField(record.fields, 'RECORD') === '41')
144
- .map((record) => getField(record.fields, 'Name'))
145
- .filter(Boolean)
146
- )
147
144
  const unresolved = []
148
145
 
149
146
  for (const record of records || []) {
150
- const text = getDisplayText(record.fields)
147
+ const text = SchematicQaReportBuilder.#displayText(record.fields)
151
148
  const match = text.match(/^=([A-Za-z_][\w.]*)$/)
152
149
  if (!match) {
153
150
  continue
154
151
  }
155
- if (
156
- !metadata.has(match[1]) ||
157
- text === getDisplayText(record.fields)
158
- ) {
159
- unresolved.push(match[1])
160
- }
152
+ unresolved.push(match[1])
161
153
  }
162
154
 
163
155
  return [...new Set(unresolved)].sort()
@@ -200,23 +192,25 @@ export class SchematicQaReportBuilder {
200
192
  /**
201
193
  * Builds title-block findings.
202
194
  * @param {object[]} records Schematic records.
195
+ * @param {object[]} titleBlockResidue Hidden title-block residue rows.
203
196
  * @returns {object[]}
204
197
  */
205
- static #titleBlockFindings(records) {
198
+ static #titleBlockFindings(records, titleBlockResidue) {
206
199
  const sheet = (records || []).find(
207
- (record) => getField(record.fields, 'RECORD') === '31'
200
+ (record) =>
201
+ SchematicQaReportBuilder.#field(record.fields, 'RECORD') ===
202
+ '31'
208
203
  )
209
204
  if (!sheet || parseBoolean(sheet.fields.TitleBlockOn)) {
210
205
  return []
211
206
  }
212
207
 
213
- const residue = SchematicQaReportBuilder.#titleBlockResidue(records)
214
- return residue.length
208
+ return titleBlockResidue.length
215
209
  ? [
216
210
  {
217
211
  code: 'schematic.title-block.hidden-residue',
218
212
  severity: 'info',
219
- count: residue.length,
213
+ count: titleBlockResidue.length,
220
214
  message:
221
215
  'Hidden title-block parameter records remain while the title block is disabled.'
222
216
  }
@@ -240,22 +234,32 @@ export class SchematicQaReportBuilder {
240
234
  'drawnby'
241
235
  ])
242
236
 
243
- return (records || [])
244
- .filter(
245
- (record) =>
246
- getField(record.fields, 'RECORD') === '41' &&
247
- parseBoolean(record.fields.IsHidden) &&
248
- titleBlockNames.has(
249
- getField(record.fields, 'Name')
250
- .replace(/\s+/g, '')
251
- .toLowerCase()
252
- )
253
- )
254
- .map((record) => ({
237
+ const residue = []
238
+
239
+ for (const record of records || []) {
240
+ const fields = record.fields || {}
241
+ if (
242
+ SchematicQaReportBuilder.#field(fields, 'RECORD') !== '41' ||
243
+ !parseBoolean(
244
+ SchematicQaReportBuilder.#rawField(fields, 'IsHidden')
245
+ )
246
+ ) {
247
+ continue
248
+ }
249
+
250
+ const name = SchematicQaReportBuilder.#field(fields, 'Name')
251
+ if (!titleBlockNames.has(name.replace(/\s+/g, '').toLowerCase())) {
252
+ continue
253
+ }
254
+
255
+ residue.push({
255
256
  recordKey: SchematicQaReportBuilder.#recordKey(record),
256
- name: getField(record.fields, 'Name'),
257
- value: getDisplayText(record.fields)
258
- }))
257
+ name,
258
+ value: SchematicQaReportBuilder.#displayText(fields)
259
+ })
260
+ }
261
+
262
+ return residue
259
263
  }
260
264
 
261
265
  /**
@@ -281,4 +285,77 @@ export class SchematicQaReportBuilder {
281
285
  static #recordKey(record) {
282
286
  return 'schematic-record-' + String(record?.recordIndex ?? 0)
283
287
  }
288
+
289
+ /**
290
+ * Reads one common field without invoking generic field-cache bookkeeping.
291
+ * @param {Record<string, string | string[]> | undefined} fields Record fields.
292
+ * @param {string} key Requested key.
293
+ * @returns {string}
294
+ */
295
+ static #field(fields, key) {
296
+ return SchematicQaReportBuilder.#pickFieldValue(
297
+ SchematicQaReportBuilder.#rawField(fields, key),
298
+ false
299
+ )
300
+ }
301
+
302
+ /**
303
+ * Returns the best schematic display text without shared field-cache overhead.
304
+ * @param {Record<string, string | string[]> | undefined} fields Record fields.
305
+ * @returns {string}
306
+ */
307
+ static #displayText(fields) {
308
+ return (
309
+ SchematicQaReportBuilder.#pickFieldValue(
310
+ SchematicQaReportBuilder.#rawField(fields, 'UTF8:Text'),
311
+ true
312
+ ) ||
313
+ SchematicQaReportBuilder.#pickFieldValue(
314
+ SchematicQaReportBuilder.#rawField(fields, 'Text'),
315
+ true
316
+ )
317
+ )
318
+ }
319
+
320
+ /**
321
+ * Reads one raw field from parsed records or simple fixture objects.
322
+ * @param {Record<string, string | string[]> | undefined} fields Record fields.
323
+ * @param {string} key Requested key.
324
+ * @returns {string | string[] | undefined}
325
+ */
326
+ static #rawField(fields, key) {
327
+ if (!fields || typeof fields !== 'object') {
328
+ return undefined
329
+ }
330
+
331
+ const direct = fields[key]
332
+ if (direct !== undefined) {
333
+ return direct
334
+ }
335
+
336
+ const upperKey = key.toUpperCase()
337
+ return upperKey === key ? undefined : fields[upperKey]
338
+ }
339
+
340
+ /**
341
+ * Returns the last meaningful value from one field payload.
342
+ * @param {string | string[] | undefined} raw Raw field payload.
343
+ * @param {boolean} skipAsterisk Whether placeholder asterisks are ignored.
344
+ * @returns {string}
345
+ */
346
+ static #pickFieldValue(raw, skipAsterisk) {
347
+ if (!Array.isArray(raw)) {
348
+ const value = String(raw || '').trim()
349
+ return value && (!skipAsterisk || value !== '*') ? value : ''
350
+ }
351
+
352
+ for (let index = raw.length - 1; index >= 0; index -= 1) {
353
+ const value = String(raw[index] || '').trim()
354
+ if (value && (!skipAsterisk || value !== '*')) {
355
+ return value
356
+ }
357
+ }
358
+
359
+ return ''
360
+ }
284
361
  }
@@ -43,4 +43,42 @@ export class SchematicSheetStyleResolver {
43
43
 
44
44
  return configuredXZones
45
45
  }
46
+
47
+ /**
48
+ * Resolves the displayed vertical sheet-zone count after the page size
49
+ * has been normalized.
50
+ * @param {{ width: number, height: number, yZones: number, paperSize?: string, sheetStyle?: number }} sheet
51
+ * @returns {number}
52
+ */
53
+ static resolveYZones(sheet) {
54
+ const configuredYZones = Math.max(Number(sheet?.yZones || 0), 1)
55
+ const paperSize = String(sheet?.paperSize || '')
56
+ .trim()
57
+ .toUpperCase()
58
+
59
+ if (Number(sheet?.sheetStyle || 0) !== 1 && !paperSize) {
60
+ return configuredYZones
61
+ }
62
+
63
+ const width = Number(sheet?.width || 0)
64
+ const height = Number(sheet?.height || 0)
65
+ if (height < width) {
66
+ return configuredYZones
67
+ }
68
+
69
+ if (
70
+ paperSize === 'A2' ||
71
+ (width === 1654 && height === 2339) ||
72
+ paperSize === 'A3' ||
73
+ (width === 1169 && height === 1654)
74
+ ) {
75
+ return 8
76
+ }
77
+
78
+ if (paperSize === 'A4' || (width === 827 && height === 1169)) {
79
+ return 4
80
+ }
81
+
82
+ return configuredYZones
83
+ }
46
84
  }