@bars4j/core 0.0.0-stage → 0.7.1

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 (109) hide show
  1. package/README.md +116 -2
  2. package/bin/bars4j.ts +121 -0
  3. package/examples/all-formats.ts +24 -0
  4. package/examples/qrcode-demo.ts +44 -0
  5. package/examples/render.ts +25 -0
  6. package/examples/sample-content.ts +35 -0
  7. package/package.json +72 -4
  8. package/src/api/barcode-generator.ts +54 -0
  9. package/src/encode/api/barcode-encoder.ts +35 -0
  10. package/src/encode/api/barcode-exception.ts +11 -0
  11. package/src/encode/api/invalid-symbol-exception.ts +14 -0
  12. package/src/encode/api/matrix-encoder.ts +9 -0
  13. package/src/encode/api/unsupported-text-exception.ts +14 -0
  14. package/src/encode/linear/codabar-encoder.ts +60 -0
  15. package/src/encode/linear/code11-encoder.ts +67 -0
  16. package/src/encode/linear/code128-encoder.ts +115 -0
  17. package/src/encode/linear/code39-encoder.ts +94 -0
  18. package/src/encode/linear/code39ext-encoder.ts +48 -0
  19. package/src/encode/linear/code93-encoder.ts +124 -0
  20. package/src/encode/linear/code93ext-encoder.ts +51 -0
  21. package/src/encode/linear/datalogic2of5-encoder.ts +13 -0
  22. package/src/encode/linear/ean-support.ts +40 -0
  23. package/src/encode/linear/ean13-encoder.ts +69 -0
  24. package/src/encode/linear/ean5-encoder.ts +63 -0
  25. package/src/encode/linear/ean8-encoder.ts +46 -0
  26. package/src/encode/linear/errors.ts +13 -0
  27. package/src/encode/linear/iata2of5-encoder.ts +13 -0
  28. package/src/encode/linear/interleaved2of5-encoder.ts +50 -0
  29. package/src/encode/linear/itf14-encoder.ts +84 -0
  30. package/src/encode/linear/linear-bits.ts +59 -0
  31. package/src/encode/linear/matrix2of5-encoder.ts +37 -0
  32. package/src/encode/linear/msi-plessey-encoder.ts +88 -0
  33. package/src/encode/linear/postnet-encoder.ts +51 -0
  34. package/src/encode/linear/standard2of5-encoder.ts +51 -0
  35. package/src/encode/linear/tof-support.ts +30 -0
  36. package/src/encode/linear/upca-encoder.ts +48 -0
  37. package/src/encode/linear/upce-encoder.ts +95 -0
  38. package/src/encode/matrix/aztec/aztec-bit-buffer.ts +53 -0
  39. package/src/encode/matrix/aztec/aztec-bit-matrix.ts +42 -0
  40. package/src/encode/matrix/aztec/aztec-code.ts +45 -0
  41. package/src/encode/matrix/aztec/aztec-eci.ts +47 -0
  42. package/src/encode/matrix/aztec/aztec-encoder-state.ts +261 -0
  43. package/src/encode/matrix/aztec/aztec-field.ts +96 -0
  44. package/src/encode/matrix/aztec/aztec-high-level-encoder.ts +174 -0
  45. package/src/encode/matrix/aztec/aztec-layout.ts +186 -0
  46. package/src/encode/matrix/aztec/aztec-mode-tables.ts +171 -0
  47. package/src/encode/matrix/aztec/aztec-mode.ts +33 -0
  48. package/src/encode/matrix/aztec/aztec-reed-solomon.ts +60 -0
  49. package/src/encode/matrix/aztec/aztec-symbol-encoder.ts +234 -0
  50. package/src/encode/matrix/aztec-encoder.ts +54 -0
  51. package/src/encode/matrix/data-matrix-encoder.ts +72 -0
  52. package/src/encode/matrix/datamatrix/data-matrix-error-correction.ts +157 -0
  53. package/src/encode/matrix/datamatrix/data-matrix-high-level-encoder.ts +89 -0
  54. package/src/encode/matrix/datamatrix/data-matrix-placement.ts +184 -0
  55. package/src/encode/matrix/datamatrix/data-matrix-symbol-assembler.ts +79 -0
  56. package/src/encode/matrix/datamatrix/data-matrix-symbol-info.ts +257 -0
  57. package/src/encode/matrix/gf256/gf256.ts +67 -0
  58. package/src/encode/matrix/gf256/reed-solomon.ts +60 -0
  59. package/src/encode/matrix/pdf417/pdf417-barcode-matrix.ts +68 -0
  60. package/src/encode/matrix/pdf417/pdf417-barcode-row.ts +31 -0
  61. package/src/encode/matrix/pdf417/pdf417-barcode.ts +250 -0
  62. package/src/encode/matrix/pdf417/pdf417-code-words.ts +386 -0
  63. package/src/encode/matrix/pdf417/pdf417-compaction.ts +23 -0
  64. package/src/encode/matrix/pdf417/pdf417-dimensions.ts +40 -0
  65. package/src/encode/matrix/pdf417/pdf417-error-correction.ts +120 -0
  66. package/src/encode/matrix/pdf417/pdf417-high-level-encoder.ts +542 -0
  67. package/src/encode/matrix/pdf417-encoder.ts +69 -0
  68. package/src/encode/matrix/qr/qr-bit-stream.ts +91 -0
  69. package/src/encode/matrix/qr/qr-data-blocks.ts +58 -0
  70. package/src/encode/matrix/qr/qr-ecc-level.ts +19 -0
  71. package/src/encode/matrix/qr/qr-matrix-builder.ts +484 -0
  72. package/src/encode/matrix/qr/qr-mode.ts +28 -0
  73. package/src/encode/matrix/qr/qr-segment-encoder.ts +220 -0
  74. package/src/encode/matrix/qr/qr-segment.ts +43 -0
  75. package/src/encode/matrix/qr/qr-version.ts +172 -0
  76. package/src/encode/matrix/qr-code-encoder.ts +82 -0
  77. package/src/format/barcode-format.ts +21 -0
  78. package/src/format/barcode-registry.ts +53 -0
  79. package/src/format/bit-semantics.ts +15 -0
  80. package/src/format/builtin-formats.ts +159 -0
  81. package/src/format/format-spec.ts +21 -0
  82. package/src/format/linear-barcode-format.ts +218 -0
  83. package/src/format/matrix-barcode-format.ts +51 -0
  84. package/src/index.ts +76 -0
  85. package/src/render/barcode-renderer.ts +31 -0
  86. package/src/render/canvas-backend.ts +160 -0
  87. package/src/render/linear/abstract-linear-renderer.ts +34 -0
  88. package/src/render/linear/base-line-text-renderer.ts +43 -0
  89. package/src/render/linear/ean13-text-renderer.ts +60 -0
  90. package/src/render/linear/ean8-text-renderer.ts +56 -0
  91. package/src/render/linear/height-coded-renderer.ts +84 -0
  92. package/src/render/linear/itf14-renderer.ts +85 -0
  93. package/src/render/linear/itf14-text-renderer.ts +54 -0
  94. package/src/render/linear/linear-renderer.ts +13 -0
  95. package/src/render/linear/linear-text-renderer.ts +21 -0
  96. package/src/render/linear/symbol-bits.ts +14 -0
  97. package/src/render/linear/upca-text-renderer.ts +64 -0
  98. package/src/render/linear/upce-text-renderer.ts +58 -0
  99. package/src/render/linear/wide-ratio-coded-renderer.ts +109 -0
  100. package/src/render/linear/width-coded-renderer.ts +88 -0
  101. package/src/render/matrix/matrix-renderer.ts +66 -0
  102. package/src/render/png.ts +73 -0
  103. package/src/render/raster-image.ts +157 -0
  104. package/src/render/render-spec.ts +44 -0
  105. package/src/symbol/bar-element.ts +27 -0
  106. package/src/symbol/barcode-symbol.ts +16 -0
  107. package/src/symbol/linear-symbol.ts +28 -0
  108. package/src/symbol/matrix-symbol.ts +55 -0
  109. package/src/symbol/symbol-kind.ts +13 -0
package/README.md CHANGED
@@ -1,3 +1,117 @@
1
- # Temporary Holding Version
1
+ # Bars4J for Node.js / TypeScript (`@bars4j/core`)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A Node.js/TypeScript port of the [Bars4J](https://github.com/ronison/bars4j) barcode library,
4
+ keeping the same characteristics and functionality.
5
+
6
+ This package lives in the same repository as the Java implementation and shares its golden
7
+ fixtures, so the two stay in sync. The port is being delivered in phases:
8
+
9
+ | Phase | Scope | Status |
10
+ |-------|-------|--------|
11
+ | 1 | Core + SVG for all 20 linear (1D) formats | **done** |
12
+ | 2 | 2D formats (QRCode, Data Matrix, PDF417, Aztec) + SVG | **done** |
13
+ | 3 | Raster output (ARGB image + PNG) and optional canvas text | **done** |
14
+ | 4 | Demos, CLI, packaging and CI | **done** |
15
+
16
+ ## Usage
17
+
18
+ ```ts
19
+ import { BarcodeGenerator, encodePng } from "@bars4j/core";
20
+
21
+ const svg = BarcodeGenerator.of("EAN13").toSvg("789100031550");
22
+
23
+ // Raster: an opaque ARGB image (width, height, Int32Array pixels).
24
+ const image = BarcodeGenerator.of("EAN13").toImage("789100031550");
25
+ const png = encodePng(image); // Uint8Array, no native dependency required
26
+ ```
27
+
28
+ The raster path is split in two:
29
+
30
+ - **Geometry (dependency-free).** Bars and modules are rasterised directly into an
31
+ [`RasterImage`](src/render/raster-image.ts) — an opaque ARGB buffer matching the Java
32
+ `TYPE_INT_RGB` layout — and PNG export uses Node's built-in `zlib`. This path is
33
+ byte-for-byte identical to the Java `*.image.txt` goldens.
34
+ - **Human-readable text (optional).** Drawing glyphs needs a font rasteriser, so it uses the
35
+ optional [`@napi-rs/canvas`](https://www.npmjs.com/package/@napi-rs/canvas) backend. Glyphs are
36
+ not guaranteed to be pixel-identical to the Java AWT output (only the bar geometry is), and
37
+ requesting text without the backend installed throws a descriptive error. Use
38
+ `canvasAvailable()` to probe, or disable the label via `LinearBarcodeFormat.withOptions`.
39
+
40
+ `BarcodeGenerator.of("<format id>")` accepts any id registered in the `BarcodeRegistry`
41
+ (case-insensitive):
42
+
43
+ - 1D: `EAN13`, `EAN8`, `EAN5`, `UPCA`, `UPCE`, `CODABAR`, `CODE39`, `CODE39EXT`, `CODE93`,
44
+ `CODE93EXT`, `CODE128`, `CODE11`, `MSIPLESSEY`, `STANDARD2OF5`, `IATA2OF5`,
45
+ `INTERLEAVED2OF5`, `ITF14`, `MATRIX2OF5`, `DATALOGIC2OF5`, `POSTNET`.
46
+ - 2D: `QRCODE`, `DATAMATRIX`, `PDF417`, `AZTEC`.
47
+
48
+ ## Running the tests
49
+
50
+ Node.js 22.18+ runs the TypeScript sources directly (type stripping), so no build step is needed:
51
+
52
+ ```sh
53
+ npm test # conformance tests against the shared Java goldens
54
+ npm run typecheck # type-check with tsc
55
+ npm run check # both
56
+ ```
57
+
58
+ ## Demos and CLI
59
+
60
+ ```sh
61
+ npm run demo # SVG + PNG for every registered format, into node/images/
62
+ npm run demo:qrcode # 2D showcase (bigger modules, all four 2D formats)
63
+
64
+ # Command line (installed as the `bars4j` binary):
65
+ bars4j EAN13 789100031550 > barcode.svg
66
+ bars4j QRCODE "https://github.com/ronison/bars4j" --png qr.png --module-size 8
67
+ bars4j --list
68
+ ```
69
+
70
+ ## Conformance
71
+
72
+ The SVG output is verified byte-for-byte against the Java goldens stored in
73
+ `../src/test/resources/golden`. For the 1D formats these are the shared `.svg` files produced by the
74
+ Java build; for the 2D formats the module matrices and the end-to-end SVG documents live in
75
+ `../src/test/resources/golden/matrix` and are generated from the (unmodified) Java implementation.
76
+ The 1D raster geometry is additionally verified through the `*.image.txt` pixel fingerprints
77
+ (SHA-256 of the ARGB pixels), which the Node port reproduces exactly. Every registered format is
78
+ also smoke-tested for SVG and PNG output with the shared demo payloads.
79
+
80
+ ## Packaging
81
+
82
+ The package ships the TypeScript sources (`src/`, `bin/`, `examples/`) and runs directly on
83
+ Node.js 22.18+; there is no build step. Consumers therefore need Node.js 22.18+ (native type
84
+ stripping) or a bundler that understands TypeScript; TypeScript projects may need
85
+ `allowImportingTsExtensions` (or `moduleResolution: "bundler"`). The package contents are checked
86
+ with `npm pack --dry-run` in CI.
87
+
88
+ ### Publishing to npm
89
+
90
+ Publishing is automated by [`.github/workflows/node-publish.yml`](../.github/workflows/node-publish.yml),
91
+ triggered when a GitHub release is published (aligned with the Maven workflow), when a `node-v*` tag
92
+ is pushed, or manually. It runs the test suite first and then publishes with `--provenance`.
93
+
94
+ Authentication picks one of two paths:
95
+
96
+ - **Trusted publishing (recommended, no secret).** The npm package must already exist. Create it once
97
+ (e.g. publish a placeholder, or register `@bars4j/core` on npmjs.com), then on the package's
98
+ **Settings → Trusted Publisher** add a GitHub Actions publisher:
99
+ - Organization/User: `ronison`
100
+ - Repository: `bars4j`
101
+ - Workflow filename: `node-publish.yml`
102
+ - Environment: *(empty)*
103
+
104
+ With that configured, the workflow authenticates via OIDC (`id-token: write`).
105
+ - **Classic token.** Add an `NPM_TOKEN` repository secret (npm *Automation*/granular token with
106
+ publish rights for `@bars4j` and 2FA bypass). The workflow uses it automatically when present.
107
+
108
+ To validate the package locally before publishing:
109
+
110
+ ```sh
111
+ npm publish --dry-run --access public
112
+ ```
113
+
114
+
115
+ ## License
116
+
117
+ LGPL-2.1-or-later, inherited from the Java project. See the repository root `LICENSE`.
package/bin/bars4j.ts ADDED
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ import { writeFileSync } from "node:fs";
3
+
4
+ import {
5
+ BarcodeGenerator,
6
+ BarcodeRegistry,
7
+ encodePng,
8
+ linearDefaults,
9
+ LinearBarcodeFormat,
10
+ renderSpec,
11
+ } from "../src/index.ts";
12
+
13
+ /**
14
+ * Command-line interface for {@code @bars4j/core}.
15
+ *
16
+ * <pre>
17
+ * bars4j &lt;format&gt; &lt;text&gt; [--svg|--png FILE] [--module-size N] [--no-text]
18
+ * bars4j --list
19
+ * </pre>
20
+ */
21
+
22
+ interface Options {
23
+ id: string;
24
+ text: string;
25
+ png?: string;
26
+ out?: string;
27
+ moduleSize: number;
28
+ noText: boolean;
29
+ }
30
+
31
+ function usage(message?: string): never {
32
+ if (message !== undefined) {
33
+ process.stderr.write(`bars4j: ${message}\n\n`);
34
+ }
35
+ process.stdout.write(
36
+ "Usage: bars4j <format> <text> [options]\n"
37
+ + " bars4j --list\n\n"
38
+ + "Options:\n"
39
+ + " --svg emit the SVG to stdout (default)\n"
40
+ + " --png <file> write a PNG raster to <file>\n"
41
+ + " --out <file> write the SVG to <file> instead of stdout\n"
42
+ + " --module-size <n> module size in pixels for 2D formats (default 4)\n"
43
+ + " --no-text do not draw the human-readable text (1D formats)\n"
44
+ + " --list list the registered format ids\n",
45
+ );
46
+ process.exit(message === undefined ? 0 : 1);
47
+ }
48
+
49
+ function parse(argv: string[]): Options {
50
+ const positional: string[] = [];
51
+ let png: string | undefined;
52
+ let out: string | undefined;
53
+ let moduleSize = 4;
54
+ let noText = false;
55
+
56
+ for (let i = 0; i < argv.length; i++) {
57
+ const arg = argv[i];
58
+ switch (arg) {
59
+ case "--png":
60
+ png = argv[++i];
61
+ break;
62
+ case "--out":
63
+ out = argv[++i];
64
+ break;
65
+ case "--module-size":
66
+ moduleSize = Number(argv[++i]);
67
+ break;
68
+ case "--no-text":
69
+ noText = true;
70
+ break;
71
+ case "--svg":
72
+ break;
73
+ case "--list":
74
+ process.stdout.write(BarcodeRegistry.getInstance().ids().sort().join("\n") + "\n");
75
+ process.exit(0);
76
+ break;
77
+ default:
78
+ if (arg.startsWith("--")) {
79
+ usage(`unknown option ${arg}`);
80
+ }
81
+ positional.push(arg);
82
+ }
83
+ }
84
+
85
+ if (positional.length < 2) {
86
+ usage("missing <format> and <text>");
87
+ }
88
+ if (!Number.isInteger(moduleSize) || moduleSize <= 0) {
89
+ usage(`invalid --module-size ${moduleSize}`);
90
+ }
91
+ return { id: positional[0], text: positional[1], png, out, moduleSize, noText };
92
+ }
93
+
94
+ const options = parse(process.argv.slice(2));
95
+ const spec = renderSpec(1, 70, 2.0, options.moduleSize, "#000000", "#ffffff");
96
+
97
+ try {
98
+ const format = BarcodeRegistry.getInstance().get(options.id);
99
+ const bare = options.noText && format instanceof LinearBarcodeFormat;
100
+
101
+ const svg = bare
102
+ ? format.withOptions({ ...format.options(), showText: false }).toSvg(options.text, linearDefaults())
103
+ : BarcodeGenerator.of(options.id).with(spec).toSvg(options.text);
104
+
105
+ if (options.out !== undefined) {
106
+ writeFileSync(options.out, svg);
107
+ process.stderr.write(`bars4j: wrote ${options.out}\n`);
108
+ } else if (options.png === undefined) {
109
+ process.stdout.write(svg);
110
+ }
111
+
112
+ if (options.png !== undefined) {
113
+ const image = bare
114
+ ? format.withOptions({ ...format.options(), showText: false }).toImage(options.text, linearDefaults())
115
+ : BarcodeGenerator.of(options.id).with(spec).toImage(options.text);
116
+ writeFileSync(options.png, encodePng(image));
117
+ process.stderr.write(`bars4j: wrote ${options.png} (${image.width}x${image.height})\n`);
118
+ }
119
+ } catch (error) {
120
+ usage(error instanceof Error ? error.message : String(error));
121
+ }
@@ -0,0 +1,24 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import { BarcodeGenerator, BarcodeRegistry, encodePng } from "../src/index.ts";
5
+ import { rasterImage } from "./render.ts";
6
+ import { sampleFor } from "./sample-content.ts";
7
+
8
+ /**
9
+ * Generates an SVG and a PNG for every registered format, mirroring
10
+ * {@code org.bars4j.demo.SimpleDemo} in the Java project.
11
+ *
12
+ * <p>Run with {@code npm run demo} (optionally passing an output directory).
13
+ */
14
+ const outDir = process.argv[2] ?? fileURLToPath(new URL("../images/", import.meta.url));
15
+ mkdirSync(outDir, { recursive: true });
16
+
17
+ for (const id of BarcodeRegistry.getInstance().ids()) {
18
+ const text = sampleFor(id);
19
+ writeFileSync(`${outDir}/${id}.svg`, BarcodeGenerator.of(id).toSvg(text));
20
+ writeFileSync(`${outDir}/${id}.png`, encodePng(rasterImage(id, text)));
21
+ console.log(`${id.padEnd(14)} "${text}" -> ${id}.svg / ${id}.png`);
22
+ }
23
+
24
+ console.log(`\nGenerated ${BarcodeRegistry.getInstance().ids().length} formats in ${outDir}`);
@@ -0,0 +1,44 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import { BarcodeGenerator, BarcodeRegistry, encodePng, renderSpec } from "../src/index.ts";
5
+ import { rasterImage } from "./render.ts";
6
+
7
+ /**
8
+ * Demonstrates the unified API, including the 2D formats, mirroring
9
+ * {@code org.bars4j.demo.QRCodeDemo} in the Java project.
10
+ *
11
+ * <p>Run with {@code npm run demo:qrcode} (optionally passing an output directory).
12
+ */
13
+ const outDir = process.argv[2] ?? fileURLToPath(new URL("../images/", import.meta.url));
14
+ mkdirSync(outDir, { recursive: true });
15
+
16
+ console.log(`Registered formats: ${BarcodeRegistry.getInstance().ids().join(", ")}`);
17
+
18
+ const text = "https://github.com/ronison/bars4j";
19
+
20
+ // A QRCode rendered with the default settings (error correction level M, module size 4).
21
+ writeFileSync(`${outDir}/QRCode.svg`, BarcodeGenerator.of("QRCODE").toSvg(text));
22
+ writeFileSync(`${outDir}/QRCode.png`, encodePng(rasterImage("QRCODE", text)));
23
+
24
+ // A larger QRCode: bigger modules.
25
+ const big = renderSpec(1, 70, 2.0, 8, "#000000", "#ffffff");
26
+ const bigImage = BarcodeGenerator.of("QRCODE").with(big).toImage(text);
27
+ writeFileSync(`${outDir}/QRCode-big.png`, encodePng(bigImage));
28
+ console.log(`QRCODE: ${bigImage.width}x${bigImage.height}px -> QRCode-big.png`);
29
+
30
+ // The same unified entry point works for the 1D formats too.
31
+ writeFileSync(
32
+ `${outDir}/EAN13-from-new-api.svg`,
33
+ BarcodeGenerator.of("EAN13").toSvg("789100031550"),
34
+ );
35
+
36
+ // The remaining 2D formats, rendered through the same API.
37
+ for (const id of ["DATAMATRIX", "PDF417", "AZTEC"]) {
38
+ writeFileSync(`${outDir}/${id}.svg`, BarcodeGenerator.of(id).toSvg(text));
39
+ const image = rasterImage(id, text);
40
+ writeFileSync(`${outDir}/${id}.png`, encodePng(image));
41
+ console.log(`${id}: ${image.width}x${image.height}px -> ${id}.svg / ${id}.png`);
42
+ }
43
+
44
+ console.log(`\nWritten to ${outDir}`);
@@ -0,0 +1,25 @@
1
+ import {
2
+ BarcodeGenerator,
3
+ BarcodeRegistry,
4
+ canvasAvailable,
5
+ linearDefaults,
6
+ LinearBarcodeFormat,
7
+ matrixDefaults,
8
+ type RasterImage,
9
+ } from "../src/index.ts";
10
+
11
+ /**
12
+ * Renders the raster image for a format without requiring the optional canvas backend: when it is
13
+ * not installed, the human-readable text is disabled for the linear formats (the bar geometry is
14
+ * identical either way).
15
+ */
16
+ export function rasterImage(formatId: string, text: string): RasterImage {
17
+ if (canvasAvailable()) {
18
+ return BarcodeGenerator.of(formatId).toImage(text);
19
+ }
20
+ const format = BarcodeRegistry.getInstance().get(formatId);
21
+ if (format instanceof LinearBarcodeFormat) {
22
+ return format.withOptions({ ...format.options(), showText: false }).toImage(text, linearDefaults());
23
+ }
24
+ return format.toImage(text, matrixDefaults());
25
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Sample payloads for the demos, mirroring {@code org.bars4j.demo.SampleContent} in the Java
3
+ * project (and the codes used by the golden tests).
4
+ */
5
+ export const SAMPLES: ReadonlyMap<string, string> = new Map<string, string>([
6
+ ["EAN13", "789100031550"],
7
+ ["EAN8", "9788515"],
8
+ ["EAN5", "54495"],
9
+ ["UPCA", "07567816415"],
10
+ ["UPCE", "0756785"],
11
+ ["CODABAR", "97885150040-85"],
12
+ ["CODE39", "BARS4J-39"],
13
+ ["CODE39EXT", "Bars4J-39Ext"],
14
+ ["CODE93", "BARS4J-93"],
15
+ ["CODE93EXT", "Bars4J-93Ext"],
16
+ ["CODE128", "Bars4J-128"],
17
+ ["CODE11", "9788515004041"],
18
+ ["MSIPLESSEY", "9788515004041"],
19
+ ["STANDARD2OF5", "978851500404"],
20
+ ["IATA2OF5", "978851500404"],
21
+ ["MATRIX2OF5", "123456"],
22
+ ["DATALOGIC2OF5", "123456"],
23
+ ["INTERLEAVED2OF5", "9876543210921"],
24
+ ["ITF14", "1540014128876"],
25
+ ["POSTNET", "805365961"],
26
+ ["QRCODE", "https://github.com/ronison/bars4j"],
27
+ ["DATAMATRIX", "https://github.com/ronison/bars4j"],
28
+ ["PDF417", "https://github.com/ronison/bars4j"],
29
+ ["AZTEC", "https://github.com/ronison/bars4j"],
30
+ ]);
31
+
32
+ /** Returns a sample that encodes with the given format id, or {@code "Bars4J"} as fallback. */
33
+ export function sampleFor(formatId: string): string {
34
+ return SAMPLES.get(formatId.toUpperCase()) ?? "Bars4J";
35
+ }
package/package.json CHANGED
@@ -1,6 +1,74 @@
1
1
  {
2
2
  "name": "@bars4j/core",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.7.1",
4
+ "description": "Node.js/TypeScript port of the Bars4J barcode library (1D + 2D, SVG and raster).",
5
+ "type": "module",
6
+ "license": "LGPL-2.1-or-later",
7
+ "engines": {
8
+ "node": ">=22.18"
9
+ },
10
+ "main": "./src/index.ts",
11
+ "types": "./src/index.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./src/index.ts",
15
+ "default": "./src/index.ts"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "bin": {
20
+ "bars4j": "./bin/bars4j.ts"
21
+ },
22
+ "sideEffects": false,
23
+ "files": [
24
+ "src",
25
+ "bin",
26
+ "examples",
27
+ "README.md"
28
+ ],
29
+ "keywords": [
30
+ "barcode",
31
+ "barcodes",
32
+ "ean13",
33
+ "ean8",
34
+ "upca",
35
+ "upce",
36
+ "code128",
37
+ "code39",
38
+ "itf14",
39
+ "postnet",
40
+ "qrcode",
41
+ "datamatrix",
42
+ "pdf417",
43
+ "aztec",
44
+ "svg",
45
+ "png"
46
+ ],
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/ronison/bars4j.git",
50
+ "directory": "node"
51
+ },
52
+ "homepage": "https://github.com/ronison/bars4j",
53
+ "bugs": {
54
+ "url": "https://github.com/ronison/bars4j/issues"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "scripts": {
60
+ "test": "node --test",
61
+ "test:golden": "node --test test/",
62
+ "typecheck": "tsc --noEmit",
63
+ "check": "npm run typecheck && npm test",
64
+ "demo": "node examples/all-formats.ts",
65
+ "demo:qrcode": "node examples/qrcode-demo.ts"
66
+ },
67
+ "devDependencies": {
68
+ "@types/node": "^26.6.6",
69
+ "typescript": "^7.0.2"
70
+ },
71
+ "optionalDependencies": {
72
+ "@napi-rs/canvas": "^1.1.0"
73
+ }
74
+ }
@@ -0,0 +1,54 @@
1
+ import type { FormatSpec } from "../format/format-spec.ts";
2
+ import type { BarcodeFormat } from "../format/barcode-format.ts";
3
+ import { BarcodeRegistry } from "../format/barcode-registry.ts";
4
+ import type { RasterImage } from "../render/raster-image.ts";
5
+ import { SymbolKind } from "../symbol/symbol-kind.ts";
6
+ import { linearDefaults, matrixDefaults, type RenderSpec } from "../render/render-spec.ts";
7
+
8
+ /**
9
+ * Unified entry point to generate any registered barcode format (1D or 2D).
10
+ *
11
+ * <pre>{@code
12
+ * const svg = BarcodeGenerator.of("EAN13").toSvg("789100031550");
13
+ * }</pre>
14
+ *
15
+ * Port of {@code org.bars4j.api.BarcodeGenerator}.
16
+ */
17
+ export class BarcodeGenerator {
18
+ private readonly barcodeFormat: BarcodeFormat;
19
+ private readonly renderSpec: RenderSpec;
20
+
21
+ private constructor(barcodeFormat: BarcodeFormat, renderSpec: RenderSpec) {
22
+ this.barcodeFormat = barcodeFormat;
23
+ this.renderSpec = renderSpec;
24
+ }
25
+
26
+ static of(formatId: string): BarcodeGenerator {
27
+ const format = BarcodeRegistry.getInstance().get(formatId);
28
+ const spec = format.spec().kind === SymbolKind.MATRIX
29
+ ? matrixDefaults()
30
+ : linearDefaults();
31
+ return new BarcodeGenerator(format, spec);
32
+ }
33
+
34
+ with(renderSpec: RenderSpec): BarcodeGenerator {
35
+ return new BarcodeGenerator(this.barcodeFormat, renderSpec);
36
+ }
37
+
38
+ format(): FormatSpec {
39
+ return this.barcodeFormat.spec();
40
+ }
41
+
42
+ /** Raster output: renders the symbol to a {@link RasterImage} (opaque ARGB pixels). */
43
+ toImage(text: string): RasterImage {
44
+ return this.barcodeFormat.toImage(text, this.renderSpec);
45
+ }
46
+
47
+ toSvg(text: string): string {
48
+ return this.barcodeFormat.toSvg(text, this.renderSpec);
49
+ }
50
+
51
+ checkSum(text: string): string {
52
+ return this.barcodeFormat.checkSum(text);
53
+ }
54
+ }
@@ -0,0 +1,35 @@
1
+ import type { BarcodeSymbol } from "../../symbol/barcode-symbol.ts";
2
+ import type { LinearSymbol } from "../../symbol/linear-symbol.ts";
3
+
4
+ /**
5
+ * Encodes text into a {@link BarcodeSymbol}.
6
+ *
7
+ * Port of {@code org.bars4j.encode.api.BarcodeEncoder}.
8
+ */
9
+ export interface BarcodeEncoder<S extends BarcodeSymbol> {
10
+ encode(text: string): S;
11
+
12
+ /**
13
+ * Computes the check digit/sum for the given text. Formats without an external check value
14
+ * (e.g. Code128 computes it internally) may return an empty string.
15
+ */
16
+ computeCheckSum(text: string): string;
17
+ }
18
+
19
+ /**
20
+ * Encoder of linear (1D) symbols.
21
+ *
22
+ * Port of {@code org.bars4j.encode.api.LinearEncoder}.
23
+ */
24
+ export interface LinearEncoder extends BarcodeEncoder<LinearSymbol> {
25
+ /**
26
+ * Length of a complete symbol including its external check digit, or {@code undefined}/-1 when the
27
+ * format is variable length or has no external check value.
28
+ */
29
+ completeLength?(): number;
30
+ }
31
+
32
+ /** Effective complete length of a linear encoder (mirrors the Java default of -1). */
33
+ export function completeLength(encoder: LinearEncoder): number {
34
+ return encoder.completeLength ? encoder.completeLength() : -1;
35
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Base exception for all barcode encoding/decoding failures.
3
+ *
4
+ * Port of {@code org.bars4j.encode.api.BarcodeException}.
5
+ */
6
+ export class BarcodeException extends Error {
7
+ constructor(message: string, options?: { cause?: unknown }) {
8
+ super(message, options);
9
+ this.name = "BarcodeException";
10
+ }
11
+ }
@@ -0,0 +1,14 @@
1
+ import { BarcodeException } from "./barcode-exception.ts";
2
+
3
+ /**
4
+ * Thrown when the input cannot be represented by the target barcode format
5
+ * (wrong length, unsupported characters, value out of range, ...).
6
+ *
7
+ * Port of {@code org.bars4j.encode.api.InvalidSymbolException}.
8
+ */
9
+ export class InvalidSymbolException extends BarcodeException {
10
+ constructor(message: string, options?: { cause?: unknown }) {
11
+ super(message, options);
12
+ this.name = "InvalidSymbolException";
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ import type { MatrixSymbol } from "../../symbol/matrix-symbol.ts";
2
+ import type { BarcodeEncoder } from "./barcode-encoder.ts";
3
+
4
+ /**
5
+ * Encoder of matrix (2D) symbols, such as QRCode.
6
+ *
7
+ * Port of {@code org.bars4j.encode.api.MatrixEncoder}.
8
+ */
9
+ export interface MatrixEncoder extends BarcodeEncoder<MatrixSymbol> {}
@@ -0,0 +1,14 @@
1
+ import { InvalidSymbolException } from "./invalid-symbol-exception.ts";
2
+
3
+ /**
4
+ * Thrown when the input text is not supported by the selected encoding mode
5
+ * (e.g. non-numeric text on a numeric-only barcode).
6
+ *
7
+ * Port of {@code org.bars4j.encode.api.UnsupportedTextException}.
8
+ */
9
+ export class UnsupportedTextException extends InvalidSymbolException {
10
+ constructor(message: string, options?: { cause?: unknown }) {
11
+ super(message, options);
12
+ this.name = "UnsupportedTextException";
13
+ }
14
+ }
@@ -0,0 +1,60 @@
1
+ import { BitSemantics } from "../../format/bit-semantics.ts";
2
+ import type { LinearSymbol } from "../../symbol/linear-symbol.ts";
3
+ import type { LinearEncoder } from "../api/barcode-encoder.ts";
4
+ import { InvalidSymbolException } from "../api/invalid-symbol-exception.ts";
5
+ import { symbol } from "./linear-bits.ts";
6
+
7
+ const CODES_WIDTH = [
8
+ "0000011", "0000110", "0001001", "1100000", "0010010",
9
+ "1000010", "0100001", "0100100", "0110000", "1001000",
10
+ "0001100", "0011000", "1000101", "1010001", "1010100",
11
+ "0011111", "0011010", "0001011", "0101001", "0001110",
12
+ ];
13
+
14
+ const INTER_CHAR = "0";
15
+ const START_CHAR_A = 16;
16
+ const STOP_CHAR_ASTERISC = 18;
17
+
18
+ /**
19
+ * Native Codabar encoder. Ported from the legacy {@code CodabarEncoder} (default start 'A',
20
+ * stop '*').
21
+ */
22
+ export class CodabarEncoder implements LinearEncoder {
23
+ encode(text: string): LinearSymbol {
24
+ if (text.length < 1) {
25
+ throw new InvalidSymbolException(`[Codabar] Invalid text length (${text.length}).`);
26
+ }
27
+ let bits = CODES_WIDTH[START_CHAR_A];
28
+ for (let i = 0; i < text.length; i++) {
29
+ bits += INTER_CHAR;
30
+ bits += CODES_WIDTH[getCharIndex(text.charAt(i))];
31
+ }
32
+ bits += INTER_CHAR;
33
+ bits += CODES_WIDTH[STOP_CHAR_ASTERISC];
34
+ return symbol(bits, BitSemantics.WIDE_NARROW_ALTERNATING);
35
+ }
36
+
37
+ computeCheckSum(_text: string): string {
38
+ return "";
39
+ }
40
+ }
41
+
42
+ function getCharIndex(c: string): number {
43
+ const code = c.charCodeAt(0);
44
+ if (c >= "0" && c <= "9") {
45
+ return code - 48;
46
+ } else if (c === "-") {
47
+ return 10;
48
+ } else if (c === "$") {
49
+ return 11;
50
+ } else if (c === ":") {
51
+ return 12;
52
+ } else if (c === "/") {
53
+ return 13;
54
+ } else if (c === ".") {
55
+ return 14;
56
+ } else if (c === "+") {
57
+ return 15;
58
+ }
59
+ throw new InvalidSymbolException(`[Codabar] The text contains not suported chars (${c}).`);
60
+ }