@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.
- package/README.md +116 -2
- package/bin/bars4j.ts +121 -0
- package/examples/all-formats.ts +24 -0
- package/examples/qrcode-demo.ts +44 -0
- package/examples/render.ts +25 -0
- package/examples/sample-content.ts +35 -0
- package/package.json +72 -4
- package/src/api/barcode-generator.ts +54 -0
- package/src/encode/api/barcode-encoder.ts +35 -0
- package/src/encode/api/barcode-exception.ts +11 -0
- package/src/encode/api/invalid-symbol-exception.ts +14 -0
- package/src/encode/api/matrix-encoder.ts +9 -0
- package/src/encode/api/unsupported-text-exception.ts +14 -0
- package/src/encode/linear/codabar-encoder.ts +60 -0
- package/src/encode/linear/code11-encoder.ts +67 -0
- package/src/encode/linear/code128-encoder.ts +115 -0
- package/src/encode/linear/code39-encoder.ts +94 -0
- package/src/encode/linear/code39ext-encoder.ts +48 -0
- package/src/encode/linear/code93-encoder.ts +124 -0
- package/src/encode/linear/code93ext-encoder.ts +51 -0
- package/src/encode/linear/datalogic2of5-encoder.ts +13 -0
- package/src/encode/linear/ean-support.ts +40 -0
- package/src/encode/linear/ean13-encoder.ts +69 -0
- package/src/encode/linear/ean5-encoder.ts +63 -0
- package/src/encode/linear/ean8-encoder.ts +46 -0
- package/src/encode/linear/errors.ts +13 -0
- package/src/encode/linear/iata2of5-encoder.ts +13 -0
- package/src/encode/linear/interleaved2of5-encoder.ts +50 -0
- package/src/encode/linear/itf14-encoder.ts +84 -0
- package/src/encode/linear/linear-bits.ts +59 -0
- package/src/encode/linear/matrix2of5-encoder.ts +37 -0
- package/src/encode/linear/msi-plessey-encoder.ts +88 -0
- package/src/encode/linear/postnet-encoder.ts +51 -0
- package/src/encode/linear/standard2of5-encoder.ts +51 -0
- package/src/encode/linear/tof-support.ts +30 -0
- package/src/encode/linear/upca-encoder.ts +48 -0
- package/src/encode/linear/upce-encoder.ts +95 -0
- package/src/encode/matrix/aztec/aztec-bit-buffer.ts +53 -0
- package/src/encode/matrix/aztec/aztec-bit-matrix.ts +42 -0
- package/src/encode/matrix/aztec/aztec-code.ts +45 -0
- package/src/encode/matrix/aztec/aztec-eci.ts +47 -0
- package/src/encode/matrix/aztec/aztec-encoder-state.ts +261 -0
- package/src/encode/matrix/aztec/aztec-field.ts +96 -0
- package/src/encode/matrix/aztec/aztec-high-level-encoder.ts +174 -0
- package/src/encode/matrix/aztec/aztec-layout.ts +186 -0
- package/src/encode/matrix/aztec/aztec-mode-tables.ts +171 -0
- package/src/encode/matrix/aztec/aztec-mode.ts +33 -0
- package/src/encode/matrix/aztec/aztec-reed-solomon.ts +60 -0
- package/src/encode/matrix/aztec/aztec-symbol-encoder.ts +234 -0
- package/src/encode/matrix/aztec-encoder.ts +54 -0
- package/src/encode/matrix/data-matrix-encoder.ts +72 -0
- package/src/encode/matrix/datamatrix/data-matrix-error-correction.ts +157 -0
- package/src/encode/matrix/datamatrix/data-matrix-high-level-encoder.ts +89 -0
- package/src/encode/matrix/datamatrix/data-matrix-placement.ts +184 -0
- package/src/encode/matrix/datamatrix/data-matrix-symbol-assembler.ts +79 -0
- package/src/encode/matrix/datamatrix/data-matrix-symbol-info.ts +257 -0
- package/src/encode/matrix/gf256/gf256.ts +67 -0
- package/src/encode/matrix/gf256/reed-solomon.ts +60 -0
- package/src/encode/matrix/pdf417/pdf417-barcode-matrix.ts +68 -0
- package/src/encode/matrix/pdf417/pdf417-barcode-row.ts +31 -0
- package/src/encode/matrix/pdf417/pdf417-barcode.ts +250 -0
- package/src/encode/matrix/pdf417/pdf417-code-words.ts +386 -0
- package/src/encode/matrix/pdf417/pdf417-compaction.ts +23 -0
- package/src/encode/matrix/pdf417/pdf417-dimensions.ts +40 -0
- package/src/encode/matrix/pdf417/pdf417-error-correction.ts +120 -0
- package/src/encode/matrix/pdf417/pdf417-high-level-encoder.ts +542 -0
- package/src/encode/matrix/pdf417-encoder.ts +69 -0
- package/src/encode/matrix/qr/qr-bit-stream.ts +91 -0
- package/src/encode/matrix/qr/qr-data-blocks.ts +58 -0
- package/src/encode/matrix/qr/qr-ecc-level.ts +19 -0
- package/src/encode/matrix/qr/qr-matrix-builder.ts +484 -0
- package/src/encode/matrix/qr/qr-mode.ts +28 -0
- package/src/encode/matrix/qr/qr-segment-encoder.ts +220 -0
- package/src/encode/matrix/qr/qr-segment.ts +43 -0
- package/src/encode/matrix/qr/qr-version.ts +172 -0
- package/src/encode/matrix/qr-code-encoder.ts +82 -0
- package/src/format/barcode-format.ts +21 -0
- package/src/format/barcode-registry.ts +53 -0
- package/src/format/bit-semantics.ts +15 -0
- package/src/format/builtin-formats.ts +159 -0
- package/src/format/format-spec.ts +21 -0
- package/src/format/linear-barcode-format.ts +218 -0
- package/src/format/matrix-barcode-format.ts +51 -0
- package/src/index.ts +76 -0
- package/src/render/barcode-renderer.ts +31 -0
- package/src/render/canvas-backend.ts +160 -0
- package/src/render/linear/abstract-linear-renderer.ts +34 -0
- package/src/render/linear/base-line-text-renderer.ts +43 -0
- package/src/render/linear/ean13-text-renderer.ts +60 -0
- package/src/render/linear/ean8-text-renderer.ts +56 -0
- package/src/render/linear/height-coded-renderer.ts +84 -0
- package/src/render/linear/itf14-renderer.ts +85 -0
- package/src/render/linear/itf14-text-renderer.ts +54 -0
- package/src/render/linear/linear-renderer.ts +13 -0
- package/src/render/linear/linear-text-renderer.ts +21 -0
- package/src/render/linear/symbol-bits.ts +14 -0
- package/src/render/linear/upca-text-renderer.ts +64 -0
- package/src/render/linear/upce-text-renderer.ts +58 -0
- package/src/render/linear/wide-ratio-coded-renderer.ts +109 -0
- package/src/render/linear/width-coded-renderer.ts +88 -0
- package/src/render/matrix/matrix-renderer.ts +66 -0
- package/src/render/png.ts +73 -0
- package/src/render/raster-image.ts +157 -0
- package/src/render/render-spec.ts +44 -0
- package/src/symbol/bar-element.ts +27 -0
- package/src/symbol/barcode-symbol.ts +16 -0
- package/src/symbol/linear-symbol.ts +28 -0
- package/src/symbol/matrix-symbol.ts +55 -0
- package/src/symbol/symbol-kind.ts +13 -0
package/README.md
CHANGED
|
@@ -1,3 +1,117 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Bars4J for Node.js / TypeScript (`@bars4j/core`)
|
|
2
2
|
|
|
3
|
-
|
|
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 <format> <text> [--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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|