byte-codec 1.0.0 → 1.0.2

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.
@@ -1,142 +0,0 @@
1
- // The five PNG scanline (un)filters (PNG spec section 9.2), shared by the PDF cross-reference stream predictor path (src/pdf/predictors.ts): xref streams are almost always /Predictor 12, which is exactly PNG's "Up" filter applied to fixed-width rows, so this module sits on the critical path for reading modern PDFs, not just for PNG images.
2
- export type PngFilterType = 0 | 1 | 2 | 3 | 4; // None, Sub, Up, Average, Paeth
3
-
4
- function paethPredictor(a: number, b: number, c: number): number {
5
- const p = a + b - c;
6
- const pa = Math.abs(p - a);
7
- const pb = Math.abs(p - b);
8
- const pc = Math.abs(p - c);
9
- if (pa <= pb && pa <= pc) {
10
- return a;
11
- }
12
- if (pb <= pc) {
13
- return b;
14
- }
15
- return c;
16
- }
17
-
18
- // The value a filter type predicts from the left (a), above (b), and above-left (c) samples -- added back in during unfiltering, or subtracted out during filtering. Returning a value from a pure function (rather than assigning inside a switch) sidesteps having to prove a switch over a literal union is exhaustive to a variable declared without an initialiser.
19
- function isPngFilterType(value: number): value is PngFilterType {
20
- return value === 0 || value === 1 || value === 2 || value === 3 || value === 4;
21
- }
22
-
23
- function predictorValue(filterType: PngFilterType, a: number, b: number, c: number): number {
24
- if (filterType === 1) {
25
- return a;
26
- }
27
- if (filterType === 2) {
28
- return b;
29
- }
30
- if (filterType === 3) {
31
- return Math.floor((a + b) / 2);
32
- }
33
- if (filterType === 4) {
34
- return paethPredictor(a, b, c);
35
- }
36
- return 0; // None
37
- }
38
-
39
- // Reverses PNG's per-scanline filtering. `data` is the inflated IDAT payload: height rows, each prefixed by one filter-type byte followed by `bytesPerRow` filtered sample bytes. Returns the raw (unfiltered) pixel bytes, height * bytesPerRow long, with the filter-type bytes stripped.
40
- export function unfilterScanlines(
41
- data: Uint8Array<ArrayBuffer>,
42
- height: number,
43
- bytesPerRow: number,
44
- bpp: number,
45
- ): Uint8Array<ArrayBuffer> {
46
- const stride = bytesPerRow + 1;
47
- if (data.length < height * stride) {
48
- throw new Error(
49
- `PNG scanline data too short: expected at least ${height * stride} bytes, got ${data.length}`,
50
- );
51
- }
52
- const out = new Uint8Array(height * bytesPerRow);
53
- for (let y = 0; y < height; y++) {
54
- const filterByte = data[y * stride];
55
- if (filterByte === undefined || !isPngFilterType(filterByte)) {
56
- throw new Error(`unknown PNG filter type: ${String(filterByte)}`);
57
- }
58
- const rowStart = y * stride + 1;
59
- const outRowStart = y * bytesPerRow;
60
- const prevOutRowStart = y > 0 ? outRowStart - bytesPerRow : undefined;
61
- for (let x = 0; x < bytesPerRow; x++) {
62
- const raw = data[rowStart + x]!;
63
- const a = x >= bpp ? out[outRowStart + x - bpp]! : 0;
64
- const b = prevOutRowStart === undefined ? 0 : out[prevOutRowStart + x]!;
65
- const c = x >= bpp && prevOutRowStart !== undefined ? out[prevOutRowStart + x - bpp]! : 0;
66
- out[outRowStart + x] = (raw + predictorValue(filterByte, a, b, c)) & 0xff;
67
- }
68
- }
69
- return out;
70
- }
71
-
72
- function sumOfAbsSigned(bytes: Uint8Array<ArrayBuffer>): number {
73
- let sum = 0;
74
- for (const byte of bytes) {
75
- sum += byte < 128 ? byte : 256 - byte;
76
- }
77
- return sum;
78
- }
79
-
80
- function filterRowInto(
81
- raw: Uint8Array<ArrayBuffer>,
82
- rowStart: number,
83
- prevRowStart: number | undefined,
84
- bytesPerRow: number,
85
- bpp: number,
86
- filterType: PngFilterType,
87
- out: Uint8Array<ArrayBuffer>,
88
- outOffset: number,
89
- ): void {
90
- for (let x = 0; x < bytesPerRow; x++) {
91
- const rawByte = raw[rowStart + x]!;
92
- const a = x >= bpp ? raw[rowStart + x - bpp]! : 0;
93
- const b = prevRowStart === undefined ? 0 : raw[prevRowStart + x]!;
94
- const c = x >= bpp && prevRowStart !== undefined ? raw[prevRowStart + x - bpp]! : 0;
95
- out[outOffset + x] = (rawByte - predictorValue(filterType, a, b, c)) & 0xff;
96
- }
97
- }
98
-
99
- const ALL_FILTER_TYPES: readonly PngFilterType[] = [0, 1, 2, 3, 4];
100
-
101
- // Filters raw (unfiltered) pixel bytes into PNG's per-scanline IDAT payload shape. `strategy: 'none'` always emits filter type 0 (useful for deterministic, human-auditable test output); `'adaptive'` (the default) picks, per row, whichever of the five filters minimises the sum of the filtered bytes' absolute values interpreted as signed -- the heuristic the PNG spec itself recommends.
102
- export function filterScanlines(
103
- raw: Uint8Array<ArrayBuffer>,
104
- height: number,
105
- bytesPerRow: number,
106
- bpp: number,
107
- strategy: 'none' | 'adaptive' = 'adaptive',
108
- ): Uint8Array<ArrayBuffer> {
109
- const stride = bytesPerRow + 1;
110
- const out = new Uint8Array(height * stride);
111
- const candidate = new Uint8Array(bytesPerRow);
112
-
113
- for (let y = 0; y < height; y++) {
114
- const rowStart = y * bytesPerRow;
115
- const prevRowStart = y > 0 ? rowStart - bytesPerRow : undefined;
116
- const outRowStart = y * stride;
117
-
118
- if (strategy === 'none') {
119
- out[outRowStart] = 0;
120
- filterRowInto(raw, rowStart, prevRowStart, bytesPerRow, bpp, 0, out, outRowStart + 1);
121
- continue;
122
- }
123
-
124
- let bestType: PngFilterType = 0;
125
- let bestSum = Number.POSITIVE_INFINITY;
126
- let best: Uint8Array<ArrayBuffer> | undefined;
127
- for (const filterType of ALL_FILTER_TYPES) {
128
- filterRowInto(raw, rowStart, prevRowStart, bytesPerRow, bpp, filterType, candidate, 0);
129
- const sum = sumOfAbsSigned(candidate);
130
- if (sum < bestSum) {
131
- bestSum = sum;
132
- bestType = filterType;
133
- best = candidate.slice();
134
- }
135
- }
136
- out[outRowStart] = bestType;
137
- if (best !== undefined) {
138
- out.set(best, outRowStart + 1);
139
- }
140
- }
141
- return out;
142
- }
package/src/index.test.ts DELETED
@@ -1,33 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { crc32, encodePng, decodePng, readJpegInfo, ByteWriter, concatBytes } from './index';
3
-
4
- describe('byte-codec smoke', () => {
5
- it('crc32 produces a consistent hash for known input', () => {
6
- expect(crc32(new Uint8Array([1, 2, 3, 4]))).toBe(crc32(new Uint8Array([1, 2, 3, 4])));
7
- });
8
-
9
- it('encodePng then decodePng round-trips a small image', () => {
10
- const pixels = new Uint8Array([255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 0]);
11
- const png = encodePng({ width: 2, height: 2, channels: 3, data: pixels });
12
- const decoded = decodePng(png);
13
- expect(decoded.width).toBe(2);
14
- expect(decoded.height).toBe(2);
15
- });
16
-
17
- it('ByteWriter accumulates and produces concatenated output', () => {
18
- const w = new ByteWriter();
19
- w.writeByte(1);
20
- w.writeByte(2);
21
- w.writeByte(3);
22
- expect(Array.from(w.toBytes())).toEqual([1, 2, 3]);
23
- });
24
-
25
- it('concatBytes joins arrays', () => {
26
- const result = concatBytes([new Uint8Array([1, 2]), new Uint8Array([3, 4])]);
27
- expect(Array.from(result)).toEqual([1, 2, 3, 4]);
28
- });
29
-
30
- it('readJpegInfo throws for non-JPEG input', () => {
31
- expect(() => readJpegInfo(new Uint8Array([0, 0, 0]))).toThrow(/JPEG/);
32
- });
33
- });
package/src/index.ts DELETED
@@ -1,9 +0,0 @@
1
- // The shared byte/image utility package for the documents.js family: generic byte-level primitives (ByteWriter, ByteReader, CRC-32, deflate/inflate) and PNG/JPEG image encoding/decoding with zero PDF knowledge. Extracted from pdf-codec (where they lived as a directory-isolated subgraph with no PDF imports) so both pdf-codec and documents.js consume them from a neutral home rather than one fetching byte utilities from a backend.
2
- export * from './bytes/writer';
3
- export * from './bytes/reader';
4
- export * from './bytes/crc32';
5
- export * from './bytes/flate';
6
- export * from './image/png-encode';
7
- export * from './image/png-decode';
8
- export * from './image/png-filter';
9
- export * from './image/jpeg-info';
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "strict": true,
4
- "noUncheckedIndexedAccess": true,
5
- "target": "ES2024",
6
- "module": "ESNext",
7
- "moduleResolution": "bundler",
8
- "lib": ["ES2024"],
9
- "types": ["node"],
10
- "noEmit": true,
11
- "skipLibCheck": true,
12
- "isolatedModules": true,
13
- "verbatimModuleSyntax": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "esModuleInterop": true
16
- },
17
- "include": ["src"]
18
- }
package/tsdown.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from 'tsdown';
2
-
3
- export default defineConfig({
4
- entry: ['src/**/*.ts', '!src/**/*.test.ts'],
5
- root: 'src',
6
- format: ['esm', 'cjs'],
7
- dts: true,
8
- platform: 'neutral',
9
- clean: true,
10
- });