@sythos/js_barcode_universal 1.5.2 → 1.5.4

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/README.md CHANGED
@@ -118,8 +118,8 @@ The `unpkg` and `jsdelivr` fields point at the IIFE bundle, so a CDN needs no in
118
118
 
119
119
  ```html
120
120
  <script src="https://unpkg.com/@sythos/js_barcode_universal"></script>
121
- <script src="https://unpkg.com/@sythos/js_barcode_universal@1.5.2"></script>
122
- <script src="https://cdn.jsdelivr.net/npm/@sythos/js_barcode_universal@1.5.2"></script>
121
+ <script src="https://unpkg.com/@sythos/js_barcode_universal@1.5.4"></script>
122
+ <script src="https://cdn.jsdelivr.net/npm/@sythos/js_barcode_universal@1.5.4"></script>
123
123
  ```
124
124
 
125
125
  Pin the version for anything you ship; the unpinned form resolves to `latest` and will move under
@@ -407,6 +407,12 @@ the generic `ean2` and `ean5` format IDs. The image reader recognizes them only
407
407
  when attached to a validated EAN/UPC parent; use the composition helpers with
408
408
  an EAN/UPC base symbol.
409
409
 
410
+ EAN-2 and EAN-5 are parent-bound supplements, never standalone image results. They may be
411
+ listed with EAN-13, EAN-8, UPC-A, UPC-E or Bookland ISBN in `formats`: the parent remains valid
412
+ without a supplement, and a valid requested supplement is exposed only through `result.addon`.
413
+ If only `ean2` or `ean5` is requested, a validated EAN/UPC parent is still required and remains
414
+ the returned `format`; an absent, malformed or unrequested supplement never rejects the parent.
415
+
410
416
  ### GS1 DataBar
411
417
 
412
418
  The `databar` subpath exposes original GS1 GTIN/AI codecs plus physical
@@ -502,6 +508,8 @@ and go faster), `tryHarder` (retry inverted, default `true`), `binarizer`
502
508
  (`'global' | 'hybrid' | 'auto'`). A `Result` carries at least `text` and `format`; QR results also
503
509
  carry `bytes`, `version` and `ecc`.
504
510
 
511
+ For larger clean QR Code and PDF417 rasters, `auto` and `hybrid` retain their primary local-threshold pass and retry once with the global threshold only when that pass finds no result. An explicit `binarizer: 'global'` request remains single-pass.
512
+
505
513
  ```js
506
514
  listFormats() → { id, label, canWrite, canRead, kind }[]
507
515
  ```
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Sythos Barcode Suite v1.5.2
2
+ * Sythos Barcode Suite v1.5.4
3
3
  *
4
4
  * MIT License
5
5
  *
@@ -3907,6 +3907,34 @@ const DECODERS = [
3907
3907
  ['codabar', decodeCodabar],
3908
3908
  ];
3909
3909
 
3910
+ const EAN_PARENT_FORMATS = new Set(['ean13', 'ean8', 'upca', 'upce', 'isbn']);
3911
+ const EAN_SUPPLEMENT_FORMATS = new Set(['ean2', 'ean5']);
3912
+
3913
+ /** @param {string} format @returns {boolean} */
3914
+ function isEANParentFormat(format) {
3915
+ return format === 'ean13' || format === 'ean8' || format === 'upca' || format === 'upce';
3916
+ }
3917
+
3918
+ /**
3919
+ * An ISBN is printed as a Bookland EAN-13, so its decoded parent remains
3920
+ * `ean13` while an ISBN filter accepts only the Bookland prefixes.
3921
+ *
3922
+ * @param {{format:string, text:string}} result
3923
+ * @param {Set<string>} enabled
3924
+ * @returns {boolean}
3925
+ */
3926
+ function isRequestedEANParent(result, enabled) {
3927
+ if (enabled.has(result.format)) return true;
3928
+ return result.format === 'ean13' && enabled.has('isbn') && /^97[89]/.test(result.text);
3929
+ }
3930
+
3931
+ /** @param {object} result @returns {object} */
3932
+ function withoutEANAddon(result) {
3933
+ const { addon, ...parent } = result;
3934
+ void addon;
3935
+ return parent;
3936
+ }
3937
+
3910
3938
  /**
3911
3939
  * Read every linear symbol found in a binarized image.
3912
3940
  *
@@ -3924,7 +3952,8 @@ function decodeOneD(image, options = {}) {
3924
3952
  if (!enabled) return true;
3925
3953
  if (enabled.has(id)) return true;
3926
3954
  if (id === 'ean13' || id === 'ean8' || id === 'upca' || id === 'upce') {
3927
- return enabled.has('ean2') || enabled.has('ean5');
3955
+ return enabled.has('ean2') || enabled.has('ean5') ||
3956
+ (id === 'ean13' && enabled.has('isbn'));
3928
3957
  }
3929
3958
  if (id === 'code128') return enabled.has('gs1128');
3930
3959
  if (id === 'gs1databar14') return enabled.has('databar') || enabled.has('gs1-databar14');
@@ -3961,11 +3990,21 @@ function decodeOneD(image, options = {}) {
3961
3990
  }
3962
3991
  if (!result) continue;
3963
3992
  if (enabled) {
3964
- const addonRequested = enabled.has('ean2') || enabled.has('ean5');
3965
- const isEANBase = result.format === 'ean13' || result.format === 'ean8' ||
3966
- result.format === 'upca' || result.format === 'upce';
3967
- if (isEANBase && addonRequested) {
3968
- if (!result.addon || !enabled.has(result.addon.format)) continue;
3993
+ if (isEANParentFormat(result.format)) {
3994
+ const baseRequested = [...EAN_PARENT_FORMATS].some((format) => enabled.has(format));
3995
+ const parentRequested = isRequestedEANParent(result, enabled);
3996
+ if (baseRequested && !parentRequested) {
3997
+ continue;
3998
+ }
3999
+ if (!baseRequested) {
4000
+ // A supplement is never an independent barcode. When it is the
4001
+ // only requested format, return its validated EAN/UPC parent.
4002
+ if (!result.addon || !EAN_SUPPLEMENT_FORMATS.has(result.addon.format) ||
4003
+ !enabled.has(result.addon.format)) continue;
4004
+ } else if (result.addon && !enabled.has(result.addon.format)) {
4005
+ // Supplements are optional whenever a requested parent exists.
4006
+ result = withoutEANAddon(result);
4007
+ }
3969
4008
  } else if (result.format === 'gs1128') {
3970
4009
  if (!enabled.has('gs1128') && !enabled.has('code128')) continue;
3971
4010
  } else if (result.format === 'gs1databar14') {
@@ -3985,7 +4024,15 @@ function decodeOneD(image, options = {}) {
3985
4024
  }
3986
4025
  }
3987
4026
 
3988
- return results;
4027
+ // A valid EAN/UPC parent is substantially more constrained than a generic
4028
+ // narrow/wide candidate. Suppress competing interpretations of the same
4029
+ // scanline, while retaining symbols detected on other rows.
4030
+ const eanRows = new Set(results
4031
+ .filter((result) => isEANParentFormat(result.format))
4032
+ .map((result) => result.row));
4033
+ return eanRows.size === 0
4034
+ ? results
4035
+ : results.filter((result) => !eanRows.has(result.row) || isEANParentFormat(result.format));
3989
4036
  }
3990
4037
 
3991
4038
  /**
@@ -16760,12 +16807,43 @@ function decode(image, options = {}) {
16760
16807
 
16761
16808
  // De-duplicate: the same symbol is often read on several scan rows.
16762
16809
  const seen = new Set();
16763
- return results.filter((r) => {
16810
+ const unique = results.filter((r) => {
16764
16811
  const key = `${r.format}:${r.text}`;
16765
16812
  if (seen.has(key)) return false;
16766
16813
  seen.add(key);
16767
16814
  return true;
16768
16815
  });
16816
+
16817
+ // On large clean rasters, hybrid thresholding can erase otherwise uniform
16818
+ // QR/PDF417 modules. Keep auto/hybrid as the primary strategy, then make one
16819
+ // focused global retry only when the complete primary pass found nothing.
16820
+ // This deliberately leaves an explicit global request single-pass.
16821
+ const retryFormats = formats
16822
+ ? formats.filter((format) => {
16823
+ const id = String(format).toLowerCase();
16824
+ return id === 'qr' || id === 'qrcode'
16825
+ || id === 'pdf417' || id === 'pdf-417'
16826
+ || id === 'compactpdf417' || id === 'compact-pdf417' || id === 'compact-pdf-417';
16827
+ })
16828
+ : ['qr', 'pdf417', 'compactpdf417'];
16829
+ const shouldRetryGlobal = unique.length === 0
16830
+ && (binarizer === 'auto' || binarizer === 'hybrid')
16831
+ && retryFormats.length > 0;
16832
+
16833
+ if (!shouldRetryGlobal) return unique;
16834
+
16835
+ const fallback = decode(image, {
16836
+ ...options,
16837
+ formats: retryFormats,
16838
+ binarizer: 'global',
16839
+ });
16840
+ const fallbackSeen = new Set();
16841
+ return [...unique, ...fallback].filter((r) => {
16842
+ const key = `${r.format}:${r.text}`;
16843
+ if (fallbackSeen.has(key)) return false;
16844
+ fallbackSeen.add(key);
16845
+ return true;
16846
+ });
16769
16847
  }
16770
16848
 
16771
16849
  /**
@@ -16782,7 +16860,7 @@ function decodeStrict(image, options) {
16782
16860
  }
16783
16861
 
16784
16862
  /** Library version, matching package.json. */
16785
- const VERSION = '1.5.2';
16863
+ const VERSION = '1.5.4';
16786
16864
 
16787
16865
  __exports.listFormats = listFormats;
16788
16866
  __exports.encode = encode;
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Sythos Barcode Suite v1.5.2
2
+ * Sythos Barcode Suite v1.5.4
3
3
  *
4
4
  * MIT License
5
5
  *
@@ -3908,6 +3908,34 @@ const DECODERS = [
3908
3908
  ['codabar', decodeCodabar],
3909
3909
  ];
3910
3910
 
3911
+ const EAN_PARENT_FORMATS = new Set(['ean13', 'ean8', 'upca', 'upce', 'isbn']);
3912
+ const EAN_SUPPLEMENT_FORMATS = new Set(['ean2', 'ean5']);
3913
+
3914
+ /** @param {string} format @returns {boolean} */
3915
+ function isEANParentFormat(format) {
3916
+ return format === 'ean13' || format === 'ean8' || format === 'upca' || format === 'upce';
3917
+ }
3918
+
3919
+ /**
3920
+ * An ISBN is printed as a Bookland EAN-13, so its decoded parent remains
3921
+ * `ean13` while an ISBN filter accepts only the Bookland prefixes.
3922
+ *
3923
+ * @param {{format:string, text:string}} result
3924
+ * @param {Set<string>} enabled
3925
+ * @returns {boolean}
3926
+ */
3927
+ function isRequestedEANParent(result, enabled) {
3928
+ if (enabled.has(result.format)) return true;
3929
+ return result.format === 'ean13' && enabled.has('isbn') && /^97[89]/.test(result.text);
3930
+ }
3931
+
3932
+ /** @param {object} result @returns {object} */
3933
+ function withoutEANAddon(result) {
3934
+ const { addon, ...parent } = result;
3935
+ void addon;
3936
+ return parent;
3937
+ }
3938
+
3911
3939
  /**
3912
3940
  * Read every linear symbol found in a binarized image.
3913
3941
  *
@@ -3925,7 +3953,8 @@ function decodeOneD(image, options = {}) {
3925
3953
  if (!enabled) return true;
3926
3954
  if (enabled.has(id)) return true;
3927
3955
  if (id === 'ean13' || id === 'ean8' || id === 'upca' || id === 'upce') {
3928
- return enabled.has('ean2') || enabled.has('ean5');
3956
+ return enabled.has('ean2') || enabled.has('ean5') ||
3957
+ (id === 'ean13' && enabled.has('isbn'));
3929
3958
  }
3930
3959
  if (id === 'code128') return enabled.has('gs1128');
3931
3960
  if (id === 'gs1databar14') return enabled.has('databar') || enabled.has('gs1-databar14');
@@ -3962,11 +3991,21 @@ function decodeOneD(image, options = {}) {
3962
3991
  }
3963
3992
  if (!result) continue;
3964
3993
  if (enabled) {
3965
- const addonRequested = enabled.has('ean2') || enabled.has('ean5');
3966
- const isEANBase = result.format === 'ean13' || result.format === 'ean8' ||
3967
- result.format === 'upca' || result.format === 'upce';
3968
- if (isEANBase && addonRequested) {
3969
- if (!result.addon || !enabled.has(result.addon.format)) continue;
3994
+ if (isEANParentFormat(result.format)) {
3995
+ const baseRequested = [...EAN_PARENT_FORMATS].some((format) => enabled.has(format));
3996
+ const parentRequested = isRequestedEANParent(result, enabled);
3997
+ if (baseRequested && !parentRequested) {
3998
+ continue;
3999
+ }
4000
+ if (!baseRequested) {
4001
+ // A supplement is never an independent barcode. When it is the
4002
+ // only requested format, return its validated EAN/UPC parent.
4003
+ if (!result.addon || !EAN_SUPPLEMENT_FORMATS.has(result.addon.format) ||
4004
+ !enabled.has(result.addon.format)) continue;
4005
+ } else if (result.addon && !enabled.has(result.addon.format)) {
4006
+ // Supplements are optional whenever a requested parent exists.
4007
+ result = withoutEANAddon(result);
4008
+ }
3970
4009
  } else if (result.format === 'gs1128') {
3971
4010
  if (!enabled.has('gs1128') && !enabled.has('code128')) continue;
3972
4011
  } else if (result.format === 'gs1databar14') {
@@ -3986,7 +4025,15 @@ function decodeOneD(image, options = {}) {
3986
4025
  }
3987
4026
  }
3988
4027
 
3989
- return results;
4028
+ // A valid EAN/UPC parent is substantially more constrained than a generic
4029
+ // narrow/wide candidate. Suppress competing interpretations of the same
4030
+ // scanline, while retaining symbols detected on other rows.
4031
+ const eanRows = new Set(results
4032
+ .filter((result) => isEANParentFormat(result.format))
4033
+ .map((result) => result.row));
4034
+ return eanRows.size === 0
4035
+ ? results
4036
+ : results.filter((result) => !eanRows.has(result.row) || isEANParentFormat(result.format));
3990
4037
  }
3991
4038
 
3992
4039
  /**
@@ -16761,12 +16808,43 @@ function decode(image, options = {}) {
16761
16808
 
16762
16809
  // De-duplicate: the same symbol is often read on several scan rows.
16763
16810
  const seen = new Set();
16764
- return results.filter((r) => {
16811
+ const unique = results.filter((r) => {
16765
16812
  const key = `${r.format}:${r.text}`;
16766
16813
  if (seen.has(key)) return false;
16767
16814
  seen.add(key);
16768
16815
  return true;
16769
16816
  });
16817
+
16818
+ // On large clean rasters, hybrid thresholding can erase otherwise uniform
16819
+ // QR/PDF417 modules. Keep auto/hybrid as the primary strategy, then make one
16820
+ // focused global retry only when the complete primary pass found nothing.
16821
+ // This deliberately leaves an explicit global request single-pass.
16822
+ const retryFormats = formats
16823
+ ? formats.filter((format) => {
16824
+ const id = String(format).toLowerCase();
16825
+ return id === 'qr' || id === 'qrcode'
16826
+ || id === 'pdf417' || id === 'pdf-417'
16827
+ || id === 'compactpdf417' || id === 'compact-pdf417' || id === 'compact-pdf-417';
16828
+ })
16829
+ : ['qr', 'pdf417', 'compactpdf417'];
16830
+ const shouldRetryGlobal = unique.length === 0
16831
+ && (binarizer === 'auto' || binarizer === 'hybrid')
16832
+ && retryFormats.length > 0;
16833
+
16834
+ if (!shouldRetryGlobal) return unique;
16835
+
16836
+ const fallback = decode(image, {
16837
+ ...options,
16838
+ formats: retryFormats,
16839
+ binarizer: 'global',
16840
+ });
16841
+ const fallbackSeen = new Set();
16842
+ return [...unique, ...fallback].filter((r) => {
16843
+ const key = `${r.format}:${r.text}`;
16844
+ if (fallbackSeen.has(key)) return false;
16845
+ fallbackSeen.add(key);
16846
+ return true;
16847
+ });
16770
16848
  }
16771
16849
 
16772
16850
  /**
@@ -16783,7 +16861,7 @@ function decodeStrict(image, options) {
16783
16861
  }
16784
16862
 
16785
16863
  /** Library version, matching package.json. */
16786
- const VERSION = '1.5.2';
16864
+ const VERSION = '1.5.4';
16787
16865
 
16788
16866
  __exports.listFormats = listFormats;
16789
16867
  __exports.encode = encode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sythos/js_barcode_universal",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Read and write barcodes in JavaScript with zero runtime dependencies. QR Code, Micro QR, rMQR, FrameQR Code, Aztec Code and Rune, PDF417 variants, GS1 DataBar Omnidirectional/Truncated, EAN supplements and one-dimensional formats.",
5
5
  "author": {
6
6
  "name": "Sythos",
package/src/index.js CHANGED
@@ -535,12 +535,43 @@ export function decode(image, options = {}) {
535
535
 
536
536
  // De-duplicate: the same symbol is often read on several scan rows.
537
537
  const seen = new Set();
538
- return results.filter((r) => {
538
+ const unique = results.filter((r) => {
539
539
  const key = `${r.format}:${r.text}`;
540
540
  if (seen.has(key)) return false;
541
541
  seen.add(key);
542
542
  return true;
543
543
  });
544
+
545
+ // On large clean rasters, hybrid thresholding can erase otherwise uniform
546
+ // QR/PDF417 modules. Keep auto/hybrid as the primary strategy, then make one
547
+ // focused global retry only when the complete primary pass found nothing.
548
+ // This deliberately leaves an explicit global request single-pass.
549
+ const retryFormats = formats
550
+ ? formats.filter((format) => {
551
+ const id = String(format).toLowerCase();
552
+ return id === 'qr' || id === 'qrcode'
553
+ || id === 'pdf417' || id === 'pdf-417'
554
+ || id === 'compactpdf417' || id === 'compact-pdf417' || id === 'compact-pdf-417';
555
+ })
556
+ : ['qr', 'pdf417', 'compactpdf417'];
557
+ const shouldRetryGlobal = unique.length === 0
558
+ && (binarizer === 'auto' || binarizer === 'hybrid')
559
+ && retryFormats.length > 0;
560
+
561
+ if (!shouldRetryGlobal) return unique;
562
+
563
+ const fallback = decode(image, {
564
+ ...options,
565
+ formats: retryFormats,
566
+ binarizer: 'global',
567
+ });
568
+ const fallbackSeen = new Set();
569
+ return [...unique, ...fallback].filter((r) => {
570
+ const key = `${r.format}:${r.text}`;
571
+ if (fallbackSeen.has(key)) return false;
572
+ fallbackSeen.add(key);
573
+ return true;
574
+ });
544
575
  }
545
576
 
546
577
  /**
@@ -557,4 +588,4 @@ export function decodeStrict(image, options) {
557
588
  }
558
589
 
559
590
  /** Library version, matching package.json. */
560
- export const VERSION = '1.5.2';
591
+ export const VERSION = '1.5.4';
@@ -1142,6 +1142,34 @@ const DECODERS = [
1142
1142
  ['codabar', decodeCodabar],
1143
1143
  ];
1144
1144
 
1145
+ const EAN_PARENT_FORMATS = new Set(['ean13', 'ean8', 'upca', 'upce', 'isbn']);
1146
+ const EAN_SUPPLEMENT_FORMATS = new Set(['ean2', 'ean5']);
1147
+
1148
+ /** @param {string} format @returns {boolean} */
1149
+ function isEANParentFormat(format) {
1150
+ return format === 'ean13' || format === 'ean8' || format === 'upca' || format === 'upce';
1151
+ }
1152
+
1153
+ /**
1154
+ * An ISBN is printed as a Bookland EAN-13, so its decoded parent remains
1155
+ * `ean13` while an ISBN filter accepts only the Bookland prefixes.
1156
+ *
1157
+ * @param {{format:string, text:string}} result
1158
+ * @param {Set<string>} enabled
1159
+ * @returns {boolean}
1160
+ */
1161
+ function isRequestedEANParent(result, enabled) {
1162
+ if (enabled.has(result.format)) return true;
1163
+ return result.format === 'ean13' && enabled.has('isbn') && /^97[89]/.test(result.text);
1164
+ }
1165
+
1166
+ /** @param {object} result @returns {object} */
1167
+ function withoutEANAddon(result) {
1168
+ const { addon, ...parent } = result;
1169
+ void addon;
1170
+ return parent;
1171
+ }
1172
+
1145
1173
  /**
1146
1174
  * Read every linear symbol found in a binarized image.
1147
1175
  *
@@ -1159,7 +1187,8 @@ export function decodeOneD(image, options = {}) {
1159
1187
  if (!enabled) return true;
1160
1188
  if (enabled.has(id)) return true;
1161
1189
  if (id === 'ean13' || id === 'ean8' || id === 'upca' || id === 'upce') {
1162
- return enabled.has('ean2') || enabled.has('ean5');
1190
+ return enabled.has('ean2') || enabled.has('ean5') ||
1191
+ (id === 'ean13' && enabled.has('isbn'));
1163
1192
  }
1164
1193
  if (id === 'code128') return enabled.has('gs1128');
1165
1194
  if (id === 'gs1databar14') return enabled.has('databar') || enabled.has('gs1-databar14');
@@ -1196,11 +1225,21 @@ export function decodeOneD(image, options = {}) {
1196
1225
  }
1197
1226
  if (!result) continue;
1198
1227
  if (enabled) {
1199
- const addonRequested = enabled.has('ean2') || enabled.has('ean5');
1200
- const isEANBase = result.format === 'ean13' || result.format === 'ean8' ||
1201
- result.format === 'upca' || result.format === 'upce';
1202
- if (isEANBase && addonRequested) {
1203
- if (!result.addon || !enabled.has(result.addon.format)) continue;
1228
+ if (isEANParentFormat(result.format)) {
1229
+ const baseRequested = [...EAN_PARENT_FORMATS].some((format) => enabled.has(format));
1230
+ const parentRequested = isRequestedEANParent(result, enabled);
1231
+ if (baseRequested && !parentRequested) {
1232
+ continue;
1233
+ }
1234
+ if (!baseRequested) {
1235
+ // A supplement is never an independent barcode. When it is the
1236
+ // only requested format, return its validated EAN/UPC parent.
1237
+ if (!result.addon || !EAN_SUPPLEMENT_FORMATS.has(result.addon.format) ||
1238
+ !enabled.has(result.addon.format)) continue;
1239
+ } else if (result.addon && !enabled.has(result.addon.format)) {
1240
+ // Supplements are optional whenever a requested parent exists.
1241
+ result = withoutEANAddon(result);
1242
+ }
1204
1243
  } else if (result.format === 'gs1128') {
1205
1244
  if (!enabled.has('gs1128') && !enabled.has('code128')) continue;
1206
1245
  } else if (result.format === 'gs1databar14') {
@@ -1220,7 +1259,15 @@ export function decodeOneD(image, options = {}) {
1220
1259
  }
1221
1260
  }
1222
1261
 
1223
- return results;
1262
+ // A valid EAN/UPC parent is substantially more constrained than a generic
1263
+ // narrow/wide candidate. Suppress competing interpretations of the same
1264
+ // scanline, while retaining symbols detected on other rows.
1265
+ const eanRows = new Set(results
1266
+ .filter((result) => isEANParentFormat(result.format))
1267
+ .map((result) => result.row));
1268
+ return eanRows.size === 0
1269
+ ? results
1270
+ : results.filter((result) => !eanRows.has(result.row) || isEANParentFormat(result.format));
1224
1271
  }
1225
1272
 
1226
1273
  /**