byte-codec 1.0.0 → 1.0.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 (59) hide show
  1. package/dist/bytes/crc32-BVCBBYa3.d.cts +4 -0
  2. package/dist/bytes/crc32-BVCBBYa3.d.ts +4 -0
  3. package/dist/bytes/crc32.cjs +21 -0
  4. package/dist/bytes/crc32.js +20 -0
  5. package/dist/bytes/flate-CbSD2OU9.d.cts +12 -0
  6. package/dist/bytes/flate-CbSD2OU9.d.ts +12 -0
  7. package/dist/bytes/flate.cjs +53 -0
  8. package/dist/bytes/flate.js +49 -0
  9. package/dist/bytes/reader-CDx_NmAQ.d.cts +20 -0
  10. package/dist/bytes/reader-CDx_NmAQ.d.ts +20 -0
  11. package/dist/bytes/reader.cjs +60 -0
  12. package/dist/bytes/reader.js +58 -0
  13. package/dist/bytes/writer-B9uaqo-B.d.cts +13 -0
  14. package/dist/bytes/writer-B9uaqo-B.d.ts +13 -0
  15. package/dist/bytes/writer.cjs +37 -0
  16. package/dist/bytes/writer.js +35 -0
  17. package/dist/image/jpeg-info-Dh6D8PRh.d.cts +12 -0
  18. package/dist/image/jpeg-info-Dh6D8PRh.d.ts +12 -0
  19. package/dist/image/jpeg-info.cjs +71 -0
  20. package/dist/image/jpeg-info.js +70 -0
  21. package/dist/image/png-decode-Dvizumot.d.cts +14 -0
  22. package/dist/image/png-decode-Dvizumot.d.ts +14 -0
  23. package/dist/image/png-decode.cjs +181 -0
  24. package/dist/image/png-decode.js +180 -0
  25. package/dist/image/png-encode-C5Jj9gBX.d.ts +9 -0
  26. package/dist/image/png-encode-CpwBOmkg.d.cts +9 -0
  27. package/dist/image/png-encode.cjs +64 -0
  28. package/dist/image/png-encode.js +63 -0
  29. package/dist/image/png-filter-5L1Q0PDF.d.cts +6 -0
  30. package/dist/image/png-filter-5L1Q0PDF.d.ts +6 -0
  31. package/dist/image/png-filter.cjs +95 -0
  32. package/dist/image/png-filter.js +93 -0
  33. package/dist/index-VKR9nd94.d.ts +9 -0
  34. package/dist/index-v-4chATX.d.cts +9 -0
  35. package/dist/index.cjs +23 -0
  36. package/dist/index.js +9 -0
  37. package/package.json +5 -1
  38. package/.github/workflows/ci.yml +0 -254
  39. package/.github/workflows/dependabot-auto-merge.yml +0 -64
  40. package/.github/workflows/sibling-dependency-update.yml +0 -174
  41. package/.husky/commit-msg +0 -1
  42. package/.husky/pre-commit +0 -2
  43. package/.husky/pre-push +0 -2
  44. package/CHANGELOG.md +0 -12
  45. package/commitlint.config.ts +0 -8
  46. package/eslint.config.ts +0 -19
  47. package/release.config.ts +0 -65
  48. package/src/bytes/crc32.ts +0 -24
  49. package/src/bytes/flate.ts +0 -69
  50. package/src/bytes/reader.ts +0 -73
  51. package/src/bytes/writer.ts +0 -45
  52. package/src/image/jpeg-info.ts +0 -90
  53. package/src/image/png-decode.ts +0 -263
  54. package/src/image/png-encode.ts +0 -74
  55. package/src/image/png-filter.ts +0 -142
  56. package/src/index.test.ts +0 -33
  57. package/src/index.ts +0 -9
  58. package/tsconfig.json +0 -18
  59. package/tsdown.config.ts +0 -10
package/.husky/pre-commit DELETED
@@ -1,2 +0,0 @@
1
- # Lint and auto-fix staged TypeScript files.
2
- pnpm exec lint-staged
package/.husky/pre-push DELETED
@@ -1,2 +0,0 @@
1
- # Run the full test suite before push to catch breakage early.
2
- pnpm test
package/CHANGELOG.md DELETED
@@ -1,12 +0,0 @@
1
- # 1.0.0 (2026-08-05)
2
-
3
-
4
- ### Bug Fixes
5
-
6
- * ci test and smoke steps for byte-codec's script set ([a64531d](https://github.com/ExaDev/byte-codec/commit/a64531df7cec8969ffe2f99cb4b100020b549ad4))
7
- * ci yaml — remove stray key, fix job name, correct alias ([c1c6291](https://github.com/ExaDev/byte-codec/commit/c1c629166831899be2b07f761fafdd130babbe78))
8
-
9
-
10
- ### Features
11
-
12
- * initial byte-codec package — generic byte/image utilities ([698c6ab](https://github.com/ExaDev/byte-codec/commit/698c6abd755e72e40759569a3ffa6b0f2f5d9f21))
@@ -1,8 +0,0 @@
1
- import { commitTypes } from './release.config';
2
-
3
- export default {
4
- extends: ['@commitlint/config-conventional'],
5
- rules: {
6
- 'type-enum': [2, 'always', commitTypes.map((t) => t.type)],
7
- },
8
- };
package/eslint.config.ts DELETED
@@ -1,19 +0,0 @@
1
- import js from '@eslint/js';
2
- import tseslint from 'typescript-eslint';
3
- import globals from 'globals';
4
-
5
- export default tseslint.config(
6
- js.configs.recommended,
7
- ...tseslint.configs.recommended,
8
- {
9
- languageOptions: {
10
- globals: { ...globals.node },
11
- },
12
- rules: {
13
- '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
14
- },
15
- },
16
- {
17
- ignores: ['dist/**', 'node_modules/**'],
18
- },
19
- );
package/release.config.ts DELETED
@@ -1,65 +0,0 @@
1
- import type { Options } from 'semantic-release';
2
-
3
- type ReleaseLevel = 'major' | 'minor' | 'patch' | false;
4
-
5
- interface CommitType {
6
- readonly type: string;
7
- readonly release: ReleaseLevel;
8
- }
9
-
10
- /**
11
- * Single source of truth for the conventional-commit types this project uses. commitlint's allowed type-enum (commitlint.config.ts imports this) and commit-analyzer's releaseRules below both derive from it, so a type can't trigger a release without also being accepted by commit-msg validation, or the reverse.
12
- *
13
- * Defined here rather than in a shared commit-types.ts: semantic-release loads this file via cosmiconfig, which transpiles only this one file to ESM, so a sibling .ts module would not resolve. commitlint's jiti loader has no such limit, so it imports commitTypes from here.
14
- */
15
- export const commitTypes: readonly CommitType[] = [
16
- { type: 'feat', release: 'minor' },
17
- { type: 'fix', release: 'patch' },
18
- { type: 'perf', release: 'patch' },
19
- { type: 'revert', release: 'patch' },
20
- { type: 'refactor', release: 'patch' },
21
- { type: 'docs', release: 'patch' },
22
- { type: 'style', release: 'patch' },
23
- { type: 'test', release: 'patch' },
24
- { type: 'build', release: 'patch' },
25
- { type: 'ci', release: 'patch' },
26
- { type: 'chore', release: 'patch' },
27
- ];
28
-
29
- /**
30
- * Runs on `main`. Analyses commits since the last tag, bumps the version, publishes to npmjs.org (trusted OIDC publishing, no stored token -- see .github/workflows/ci.yml), creates a versioned tag and GitHub Release with generated notes, and commits CHANGELOG.md + package.json back to main. The release commit's [skip ci] message avoids a redundant CI run.
31
- */
32
- const config: Options = {
33
- branches: ['main'],
34
- plugins: [
35
- [
36
- '@semantic-release/commit-analyzer',
37
- {
38
- preset: 'conventionalcommits',
39
- releaseRules: [
40
- { breaking: true, release: 'major' },
41
- ...commitTypes.map((t) => ({ type: t.type, release: t.release })),
42
- ],
43
- },
44
- ],
45
- [
46
- '@semantic-release/release-notes-generator',
47
- {
48
- // Deliberately angular, not conventionalcommits, despite commit-analyzer above using conventionalcommits without issue. conventional-changelog-conventionalcommits@10.2.1 exports its changelog body under the key `template`, but the conventional-changelog-writer version release-notes-generator@14.1.1 bundles only reads `options.mainTemplate` -- so the body silently falls back to the writer's own generic default, whose commit partial doesn't match conventionalcommits' function-based partial signature either. The result is a changelog with a version header and nothing under it, for every commit, with zero custom configuration: confirmed with `preset: 'conventionalcommits'` and no presetConfig at all. angular is release-notes-generator's own tested default and renders correctly. commitTypes above still drives commit-analyzer's releaseRules and commitlint's type-enum; it just can't also drive per-type changelog sections until this is fixed upstream, so there is no per-type `section` field here to go unused.
49
- preset: 'angular',
50
- },
51
- ],
52
- '@semantic-release/changelog',
53
- ['@semantic-release/npm', { npmPublish: true }],
54
- '@semantic-release/github',
55
- [
56
- '@semantic-release/git',
57
- {
58
- assets: ['CHANGELOG.md', 'package.json'],
59
- message: 'chore(release): ${nextRelease.version} [skip ci]',
60
- },
61
- ],
62
- ],
63
- };
64
-
65
- export default config;
@@ -1,24 +0,0 @@
1
- // Table-driven CRC32 (polynomial 0xEDB88320, the standard IEEE 802.3 / ZIP / PNG polynomial). fflate exports no CRC32 of its own (only an internal one used by its ZIP writer), and PNG's chunk format requires one per chunk, so this is genuinely new code, not a duplicate of anything already in the tree.
2
- const CRC32_POLYNOMIAL = 0xedb88320;
3
- const BYTE_VALUES = 256;
4
- const BITS_PER_BYTE = 8;
5
-
6
- const CRC32_TABLE: Uint32Array = (() => {
7
- const table = new Uint32Array(BYTE_VALUES);
8
- for (let n = 0; n < BYTE_VALUES; n++) {
9
- let c = n;
10
- for (let k = 0; k < BITS_PER_BYTE; k++) {
11
- c = (c & 1) === 1 ? CRC32_POLYNOMIAL ^ (c >>> 1) : c >>> 1;
12
- }
13
- table[n] = c >>> 0;
14
- }
15
- return table;
16
- })();
17
-
18
- export function crc32(bytes: Uint8Array<ArrayBuffer>): number {
19
- let crc = 0xffffffff;
20
- for (const byte of bytes) {
21
- crc = CRC32_TABLE[(crc ^ byte) & 0xff]! ^ (crc >>> 8);
22
- }
23
- return (crc ^ 0xffffffff) >>> 0;
24
- }
@@ -1,69 +0,0 @@
1
- import { Unzlib, inflateSync, unzlibSync, zlibSync } from 'fflate';
2
- import { isAsciiWhitespace } from './reader';
3
- import { concatBytes } from './writer';
4
-
5
- // The only file in the package that imports fflate -- the direct analogue of ooxml.js's own src/zip.ts ("a thin wrapper over fflate's zipSync/unzipSync, isomorphic and dependency-free"). PDF's FlateDecode filter and PNG's IDAT payload both use zlib-framed DEFLATE (RFC 1950 -- a 2-byte header plus a trailing Adler-32 checksum) -- that is zlibSync/unzlibSync, NOT fflate's deflateSync/inflateSync, which are raw DEFLATE (RFC 1951) with no wrapper. Emitting or expecting the wrong framing produces a stream every conformant PDF/PNG reader rejects.
6
-
7
- // Guards every call in this module against a maliciously or accidentally huge decompressed output -- both PDF and PNG streams here come from arbitrary, potentially adversarial input.
8
- export const MAX_INFLATE_OUTPUT_BYTES = 512 * 1024 * 1024;
9
-
10
- export type DeflateLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
11
-
12
- export function deflate(data: Uint8Array<ArrayBuffer>, level?: DeflateLevel): Uint8Array<ArrayBuffer> {
13
- return zlibSync(data, level === undefined ? undefined : { level });
14
- }
15
-
16
- export function inflate(data: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer> {
17
- const out = unzlibSync(data);
18
- if (out.length > MAX_INFLATE_OUTPUT_BYTES) {
19
- throw new Error(`inflated output exceeds the ${MAX_INFLATE_OUTPUT_BYTES}-byte limit`);
20
- }
21
- return out;
22
- }
23
-
24
- export interface InflateResult {
25
- readonly bytes: Uint8Array<ArrayBuffer>;
26
- readonly recovered: boolean;
27
- }
28
-
29
- // A tolerant inflate for the read path: real-world PDF/PNG streams occasionally carry a leading whitespace byte, are mistakenly raw-DEFLATE under a zlib-labelled filter, or are truncated. Tries, in order: a plain inflate(); skipping leading whitespace and retrying; raw inflateSync (some producers mislabel raw DEFLATE as FlateDecode); and finally the streaming Unzlib class, which emits chunks as they're produced, so a mid-stream truncation still yields whatever decoded successfully before the failure (`recovered: true`) rather than nothing at all.
30
- export function inflateTolerant(data: Uint8Array<ArrayBuffer>): InflateResult {
31
- try {
32
- return { bytes: inflate(data), recovered: false };
33
- } catch {
34
- // fall through to the recovery ladder below
35
- }
36
-
37
- let offset = 0;
38
- while (offset < data.length && isAsciiWhitespace(data[offset])) {
39
- offset++;
40
- }
41
- if (offset > 0) {
42
- try {
43
- return { bytes: inflate(data.subarray(offset)), recovered: true };
44
- } catch {
45
- // fall through
46
- }
47
- }
48
-
49
- try {
50
- return { bytes: inflateSync(data), recovered: true };
51
- } catch {
52
- // fall through
53
- }
54
-
55
- const chunks: Uint8Array<ArrayBuffer>[] = [];
56
- const unzlib = new Unzlib((chunk) => {
57
- chunks.push(chunk);
58
- });
59
- try {
60
- // Pushed as NOT final: fflate only flushes decoded output incrementally as it's produced when a push is not marked as the stream's end -- marking truncated data `final: true` instead makes it run the end-of-stream/checksum finalisation path, which throws atomically before emitting anything at all (verified empirically against fflate 0.8.3). Since this is already the last-resort recovery tier, skipping checksum verification here is an acceptable trade.
61
- unzlib.push(data, false);
62
- } catch {
63
- // whatever chunks were emitted before the throw are still valid partial output
64
- }
65
- if (chunks.length === 0) {
66
- throw new Error('unable to inflate stream: no data could be recovered');
67
- }
68
- return { bytes: concatBytes(chunks), recovered: true };
69
- }
@@ -1,73 +0,0 @@
1
- const ASCII_WHITESPACE_BYTES = new Set([0x00, 0x09, 0x0a, 0x0c, 0x0d, 0x20]);
2
-
3
- export function isAsciiWhitespace(byte: number | undefined): boolean {
4
- return byte !== undefined && ASCII_WHITESPACE_BYTES.has(byte);
5
- }
6
-
7
- // A forward-only cursor over a byte buffer, with explicit mark()/reset() for the backtracking a tokenizer needs -- e.g. the PDF lexer's `N G R` (a reference) vs `N G obj` (an indirect object header) ambiguity, resolved only by trying to read two integers and a keyword, then rewinding if it doesn't match.
8
- export class ByteReader {
9
- private readonly bytes: Uint8Array<ArrayBuffer>;
10
- private position = 0;
11
-
12
- constructor(bytes: Uint8Array<ArrayBuffer>) {
13
- this.bytes = bytes;
14
- }
15
-
16
- get offset(): number {
17
- return this.position;
18
- }
19
-
20
- get length(): number {
21
- return this.bytes.length;
22
- }
23
-
24
- atEnd(): boolean {
25
- return this.position >= this.bytes.length;
26
- }
27
-
28
- peek(aheadBy = 0): number | undefined {
29
- return this.bytes[this.position + aheadBy];
30
- }
31
-
32
- next(): number | undefined {
33
- const byte = this.bytes[this.position];
34
- if (byte !== undefined) {
35
- this.position++;
36
- }
37
- return byte;
38
- }
39
-
40
- // Returns a resumption point for reset(); does not itself change position.
41
- mark(): number {
42
- return this.position;
43
- }
44
-
45
- reset(mark: number): void {
46
- this.position = mark;
47
- }
48
-
49
- seek(offset: number): void {
50
- this.position = offset;
51
- }
52
-
53
- slice(start: number, end: number): Uint8Array<ArrayBuffer> {
54
- return this.bytes.subarray(start, end);
55
- }
56
-
57
- skipWhitespace(): void {
58
- while (isAsciiWhitespace(this.peek())) {
59
- this.position++;
60
- }
61
- }
62
-
63
- // Consumes `keyword` as a literal ASCII sequence at the current position, advancing past it, and returns true; otherwise leaves the position unchanged and returns false.
64
- matchKeyword(keyword: string): boolean {
65
- for (let i = 0; i < keyword.length; i++) {
66
- if (this.peek(i) !== keyword.charCodeAt(i)) {
67
- return false;
68
- }
69
- }
70
- this.position += keyword.length;
71
- return true;
72
- }
73
- }
@@ -1,45 +0,0 @@
1
- // A chunked, growable byte-output builder: writes accumulate into a list of chunks rather than repeatedly reallocating and copying one growing buffer, which is O(n^2) for many small writes -- exactly the access pattern the PDF writer (one write per operator) and the PNG encoder (one write per scanline) both have.
2
- export class ByteWriter {
3
- private readonly chunks: Uint8Array<ArrayBuffer>[] = [];
4
- private byteLength = 0;
5
-
6
- get length(): number {
7
- return this.byteLength;
8
- }
9
-
10
- writeBytes(bytes: Uint8Array<ArrayBuffer>): void {
11
- if (bytes.length === 0) {
12
- return;
13
- }
14
- this.chunks.push(bytes);
15
- this.byteLength += bytes.length;
16
- }
17
-
18
- writeByte(byte: number): void {
19
- this.writeBytes(new Uint8Array([byte]));
20
- }
21
-
22
- // Encodes `text` as UTF-8 (ASCII in practice, for the PDF/PNG syntax this writer produces) and appends it.
23
- writeAscii(text: string): void {
24
- this.writeBytes(new TextEncoder().encode(text));
25
- }
26
-
27
- toBytes(): Uint8Array<ArrayBuffer> {
28
- const out = new Uint8Array(this.byteLength);
29
- let offset = 0;
30
- for (const chunk of this.chunks) {
31
- out.set(chunk, offset);
32
- offset += chunk.length;
33
- }
34
- return out;
35
- }
36
- }
37
-
38
- // Concatenates a list of byte chunks into one contiguous array without the O(n^2) cost of repeated single-chunk concatenation.
39
- export function concatBytes(chunks: readonly Uint8Array<ArrayBuffer>[]): Uint8Array<ArrayBuffer> {
40
- const writer = new ByteWriter();
41
- for (const chunk of chunks) {
42
- writer.writeBytes(chunk);
43
- }
44
- return writer.toBytes();
45
- }
@@ -1,90 +0,0 @@
1
- // A JPEG's compressed byte stream passes through this whole package unchanged in both directions (embedded via a PDF Image XObject's /DCTDecode filter on write; extracted as-is on read) -- the single biggest scope reduction in the hand-written PDF codec, since no JPEG decoder or encoder is needed at all. The one piece of information still needed from a JPEG that isn't available without looking inside it is its pixel dimensions and component count, which the PDF Image XObject dictionary requires (/Width, /Height, /ColorSpace) -- this module recovers exactly that, by scanning marker segments, without decoding a single sample.
2
- export interface JpegInfo {
3
- readonly width: number;
4
- readonly height: number;
5
- readonly components: number;
6
- readonly precision: number;
7
- readonly progressive: boolean;
8
- // The Adobe APP14 marker's transform byte, if present: 0 = unknown/CMYK-as-is, 1 = YCbCr, 2 = YCCK. A 4-component (CMYK) JPEG with transform 2, or an untagged 4-component JPEG, almost always needs colour inversion (/Decode [1 0 1 0 1 0 1 0]) to render correctly -- a well-known, near-universal convention rather than something this scanner can verify from the bytes alone.
9
- readonly adobeTransform: number | undefined;
10
- }
11
-
12
- const SOI = 0xd8;
13
- const EOI = 0xd9;
14
- const APP14 = 0xee;
15
- // SOF0 (baseline), SOF1 (extended sequential Huffman), SOF2 (progressive Huffman), SOF9 (extended sequential arithmetic), SOF10 (progressive arithmetic) -- the marker codes actually used to carry frame dimensions. SOF3/SOF5-7/SOF11/SOF13-15 (lossless / differential / hierarchical variants) are not handled, since they are not produced by mainstream PDF-embedding producers.
16
- const SOF_MARKERS = new Set([0xc0, 0xc1, 0xc2, 0xc9, 0xca]);
17
- const PROGRESSIVE_SOF_MARKERS = new Set([0xc2, 0xca]);
18
- // Markers with no following length/payload: TEM, SOI, EOI, and the eight restart markers.
19
- const NO_PAYLOAD_MARKERS = new Set([0x01, 0xd8, 0xd9, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7]);
20
-
21
- function requireByte(bytes: Uint8Array<ArrayBuffer>, index: number): number {
22
- const value = bytes[index];
23
- if (value === undefined) {
24
- throw new Error('unexpected end of JPEG data');
25
- }
26
- return value;
27
- }
28
-
29
- function readUint16BE(bytes: Uint8Array<ArrayBuffer>, offset: number): number {
30
- return (requireByte(bytes, offset) << 8) | requireByte(bytes, offset + 1);
31
- }
32
-
33
- // Scans a JPEG file's marker segments for its SOF (start-of-frame) segment, recovering dimensions, component count and progressive-ness without decoding any entropy-coded scan data. Throws if the bytes don't start with SOI or no SOF marker is found before EOI/truncation.
34
- export function readJpegInfo(bytes: Uint8Array<ArrayBuffer>): JpegInfo {
35
- if (requireByte(bytes, 0) !== 0xff || requireByte(bytes, 1) !== SOI) {
36
- throw new Error('not a valid JPEG file: missing SOI marker');
37
- }
38
-
39
- let offset = 2;
40
- let adobeTransform: number | undefined;
41
-
42
- while (offset < bytes.length) {
43
- if (bytes[offset] !== 0xff) {
44
- offset++;
45
- continue;
46
- }
47
- let markerOffset = offset + 1;
48
- while (bytes[markerOffset] === 0xff) {
49
- markerOffset++;
50
- }
51
- const marker = bytes[markerOffset];
52
- if (marker === undefined) {
53
- break;
54
- }
55
- offset = markerOffset + 1;
56
-
57
- if (marker === EOI) {
58
- break;
59
- }
60
- if (NO_PAYLOAD_MARKERS.has(marker)) {
61
- continue;
62
- }
63
-
64
- const segmentLength = readUint16BE(bytes, offset); // includes the 2 length bytes themselves
65
-
66
- if (marker === APP14 && segmentLength >= 14) {
67
- adobeTransform = requireByte(bytes, offset + 2 + 11);
68
- }
69
-
70
- if (SOF_MARKERS.has(marker)) {
71
- const p = offset + 2;
72
- const precision = requireByte(bytes, p);
73
- const height = readUint16BE(bytes, p + 1);
74
- const width = readUint16BE(bytes, p + 3);
75
- const components = requireByte(bytes, p + 5);
76
- return {
77
- width,
78
- height,
79
- components,
80
- precision,
81
- progressive: PROGRESSIVE_SOF_MARKERS.has(marker),
82
- adobeTransform,
83
- };
84
- }
85
-
86
- offset += segmentLength;
87
- }
88
-
89
- throw new Error('no SOF marker found in JPEG file');
90
- }