@page-scanner/cli 0.1.0 → 0.2.0
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 +72 -17
- package/dist/api.d.ts +82 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +149 -19
- package/dist/api.js.map +1 -1
- package/dist/batch.d.ts +60 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/batch.js +116 -0
- package/dist/batch.js.map +1 -0
- package/dist/bridge/server.d.ts.map +1 -1
- package/dist/bridge/server.js +5 -6
- package/dist/bridge/server.js.map +1 -1
- package/dist/cli/args.d.ts +21 -4
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +94 -10
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/commands.d.ts +2 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +148 -20
- package/dist/cli/commands.js.map +1 -1
- package/dist/daemon/rpc-server.d.ts.map +1 -1
- package/dist/daemon/rpc-server.js +17 -1
- package/dist/daemon/rpc-server.js.map +1 -1
- package/dist/daemon/run.js +3 -3
- package/dist/daemon/run.js.map +1 -1
- package/dist/daemon/self.d.ts +0 -2
- package/dist/daemon/self.d.ts.map +1 -1
- package/dist/daemon/self.js +4 -41
- package/dist/daemon/self.js.map +1 -1
- package/dist/diff/image.d.ts +42 -0
- package/dist/diff/image.d.ts.map +1 -0
- package/dist/diff/image.js +144 -0
- package/dist/diff/image.js.map +1 -0
- package/dist/diff/index.d.ts +46 -0
- package/dist/diff/index.d.ts.map +1 -0
- package/dist/diff/index.js +108 -0
- package/dist/diff/index.js.map +1 -0
- package/dist/diff/png.d.ts +12 -0
- package/dist/diff/png.d.ts.map +1 -0
- package/dist/diff/png.js +169 -0
- package/dist/diff/png.js.map +1 -0
- package/dist/diff/text.d.ts +54 -0
- package/dist/diff/text.d.ts.map +1 -0
- package/dist/diff/text.js +198 -0
- package/dist/diff/text.js.map +1 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +18 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/output.d.ts +10 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +24 -4
- package/dist/output.js.map +1 -1
- package/dist/protocol.d.ts +37 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +23 -2
- package/dist/protocol.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/package.json +4 -5
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
export const CELL = 8;
|
|
2
|
+
export const TOLERANCE = 48;
|
|
3
|
+
export const MERGE_GAP = 16;
|
|
4
|
+
/** The outline's colour and width, and how far it sits outside its region. */
|
|
5
|
+
const OUTLINE = [230, 40, 40, 255];
|
|
6
|
+
const OUTLINE_WIDTH = 3;
|
|
7
|
+
const OUTLINE_MARGIN = 3;
|
|
8
|
+
function differs(a, at, b, bt) {
|
|
9
|
+
return (Math.abs(a[at] - b[bt]) +
|
|
10
|
+
Math.abs(a[at + 1] - b[bt + 1]) +
|
|
11
|
+
Math.abs(a[at + 2] - b[bt + 2]) +
|
|
12
|
+
Math.abs(a[at + 3] - b[bt + 3]) >
|
|
13
|
+
TOLERANCE);
|
|
14
|
+
}
|
|
15
|
+
function near(a, b, gap) {
|
|
16
|
+
return (a.x - gap <= b.x + b.width &&
|
|
17
|
+
b.x - gap <= a.x + a.width &&
|
|
18
|
+
a.y - gap <= b.y + b.height &&
|
|
19
|
+
b.y - gap <= a.y + a.height);
|
|
20
|
+
}
|
|
21
|
+
function union(a, b) {
|
|
22
|
+
const x = Math.min(a.x, b.x);
|
|
23
|
+
const y = Math.min(a.y, b.y);
|
|
24
|
+
return {
|
|
25
|
+
x,
|
|
26
|
+
y,
|
|
27
|
+
width: Math.max(a.x + a.width, b.x + b.width) - x,
|
|
28
|
+
height: Math.max(a.y + a.height, b.y + b.height) - y,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Regions within `gap` of each other, joined until none are. */
|
|
32
|
+
export function mergeRegions(regions, gap = MERGE_GAP) {
|
|
33
|
+
let merged = [...regions];
|
|
34
|
+
for (let changed = true; changed;) {
|
|
35
|
+
changed = false;
|
|
36
|
+
const next = [];
|
|
37
|
+
for (const region of merged) {
|
|
38
|
+
const into = next.findIndex((other) => near(other, region, gap));
|
|
39
|
+
if (into === -1)
|
|
40
|
+
next.push(region);
|
|
41
|
+
else {
|
|
42
|
+
next[into] = union(next[into], region);
|
|
43
|
+
changed = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
merged = next;
|
|
47
|
+
}
|
|
48
|
+
return merged.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `after` compared with `before`, pixel for pixel where they overlap; any
|
|
52
|
+
* part of `after` that `before` does not reach counts as changed.
|
|
53
|
+
*/
|
|
54
|
+
export function diffImages(before, after) {
|
|
55
|
+
const { width, height } = after;
|
|
56
|
+
const columns = Math.ceil(width / CELL);
|
|
57
|
+
const rows = Math.ceil(height / CELL);
|
|
58
|
+
const cells = new Uint8Array(columns * rows);
|
|
59
|
+
let changedPixels = 0;
|
|
60
|
+
for (let y = 0; y < height; y++) {
|
|
61
|
+
for (let x = 0; x < width; x++) {
|
|
62
|
+
const inside = x < before.width && y < before.height;
|
|
63
|
+
if (!inside ||
|
|
64
|
+
differs(after.data, (y * width + x) * 4, before.data, (y * before.width + x) * 4)) {
|
|
65
|
+
changedPixels++;
|
|
66
|
+
cells[Math.floor(y / CELL) * columns + Math.floor(x / CELL)] = 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Touching cells, the eight around each, are one region.
|
|
71
|
+
const regions = [];
|
|
72
|
+
const seen = new Uint8Array(cells.length);
|
|
73
|
+
for (let start = 0; start < cells.length; start++) {
|
|
74
|
+
if (!cells[start] || seen[start])
|
|
75
|
+
continue;
|
|
76
|
+
let [left, top, right, bottom] = [columns, rows, 0, 0];
|
|
77
|
+
const queue = [start];
|
|
78
|
+
seen[start] = 1;
|
|
79
|
+
while (queue.length > 0) {
|
|
80
|
+
const cell = queue.pop();
|
|
81
|
+
const cx = cell % columns;
|
|
82
|
+
const cy = Math.floor(cell / columns);
|
|
83
|
+
left = Math.min(left, cx);
|
|
84
|
+
right = Math.max(right, cx);
|
|
85
|
+
top = Math.min(top, cy);
|
|
86
|
+
bottom = Math.max(bottom, cy);
|
|
87
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
88
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
89
|
+
const nx = cx + dx;
|
|
90
|
+
const ny = cy + dy;
|
|
91
|
+
if (nx < 0 || ny < 0 || nx >= columns || ny >= rows)
|
|
92
|
+
continue;
|
|
93
|
+
const next = ny * columns + nx;
|
|
94
|
+
if (cells[next] && !seen[next]) {
|
|
95
|
+
seen[next] = 1;
|
|
96
|
+
queue.push(next);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const x = left * CELL;
|
|
102
|
+
const y = top * CELL;
|
|
103
|
+
regions.push({
|
|
104
|
+
x,
|
|
105
|
+
y,
|
|
106
|
+
width: Math.min(width, (right + 1) * CELL) - x,
|
|
107
|
+
height: Math.min(height, (bottom + 1) * CELL) - y,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const merged = mergeRegions(regions);
|
|
111
|
+
return {
|
|
112
|
+
changedPixels,
|
|
113
|
+
changedFraction: width * height > 0 ? changedPixels / (width * height) : 0,
|
|
114
|
+
regions: merged,
|
|
115
|
+
outlined: outline(after, merged),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** A copy of `image` with a rectangle drawn just outside each region. */
|
|
119
|
+
export function outline(image, regions) {
|
|
120
|
+
const data = Uint8Array.from(image.data);
|
|
121
|
+
const paint = (x, y) => {
|
|
122
|
+
if (x < 0 || y < 0 || x >= image.width || y >= image.height)
|
|
123
|
+
return;
|
|
124
|
+
data.set(OUTLINE, (y * image.width + x) * 4);
|
|
125
|
+
};
|
|
126
|
+
for (const region of regions) {
|
|
127
|
+
const left = region.x - OUTLINE_MARGIN - OUTLINE_WIDTH;
|
|
128
|
+
const top = region.y - OUTLINE_MARGIN - OUTLINE_WIDTH;
|
|
129
|
+
const right = region.x + region.width + OUTLINE_MARGIN + OUTLINE_WIDTH - 1;
|
|
130
|
+
const bottom = region.y + region.height + OUTLINE_MARGIN + OUTLINE_WIDTH - 1;
|
|
131
|
+
for (let band = 0; band < OUTLINE_WIDTH; band++) {
|
|
132
|
+
for (let x = left; x <= right; x++) {
|
|
133
|
+
paint(x, top + band);
|
|
134
|
+
paint(x, bottom - band);
|
|
135
|
+
}
|
|
136
|
+
for (let y = top; y <= bottom; y++) {
|
|
137
|
+
paint(left + band, y);
|
|
138
|
+
paint(right - band, y);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { width: image.width, height: image.height, data };
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/diff/image.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Region } from './image.js';
|
|
2
|
+
import { type Hunk } from './text.js';
|
|
3
|
+
export interface DiffOptions {
|
|
4
|
+
/** Where to write the outlined picture of a visual diff. Defaults to beside the newer file. */
|
|
5
|
+
out?: string;
|
|
6
|
+
/** What relative paths are relative to. Defaults to process.cwd(). */
|
|
7
|
+
cwd?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TextSide {
|
|
10
|
+
path: string;
|
|
11
|
+
/** From the Markdown's front matter, when it has one. */
|
|
12
|
+
source: string | null;
|
|
13
|
+
captured: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface TextDiffResult {
|
|
16
|
+
kind: 'text';
|
|
17
|
+
changed: boolean;
|
|
18
|
+
old: TextSide;
|
|
19
|
+
new: TextSide;
|
|
20
|
+
/** Whether the two front matters name the same address; null when either names none. */
|
|
21
|
+
sameAddress: boolean | null;
|
|
22
|
+
added: number;
|
|
23
|
+
removed: number;
|
|
24
|
+
hunks: Hunk[];
|
|
25
|
+
/** The same hunks as `diff -u` prints them. Empty when nothing changed. */
|
|
26
|
+
unified: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ImageSide {
|
|
29
|
+
path: string;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
}
|
|
33
|
+
export interface VisualDiffResult {
|
|
34
|
+
kind: 'visual';
|
|
35
|
+
changed: boolean;
|
|
36
|
+
old: ImageSide;
|
|
37
|
+
new: ImageSide;
|
|
38
|
+
changedFraction: number;
|
|
39
|
+
regions: Region[];
|
|
40
|
+
/** The newer capture with the regions outlined, or null when nothing changed. */
|
|
41
|
+
path: string | null;
|
|
42
|
+
}
|
|
43
|
+
export type DiffResult = TextDiffResult | VisualDiffResult;
|
|
44
|
+
/** Compares two captures and, for pictures, writes the outlined newer one. */
|
|
45
|
+
export declare function diffCaptures(oldPath: string, newPath: string, options?: DiffOptions): DiffResult;
|
|
46
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,EAA6C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjF,MAAM,WAAW,WAAW;IAC1B,+FAA+F;IAC/F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,QAAQ,CAAC;IACd,GAAG,EAAE,QAAQ,CAAC;IACd,wFAAwF;IACxF,WAAW,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,SAAS,CAAC;IACf,GAAG,EAAE,SAAS,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iFAAiF;IACjF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,gBAAgB,CAAC;AAgD3D,8EAA8E;AAC9E,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,WAAgB,GACxB,UAAU,CAwDZ"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `page-scanner diff` (#60): what changed between two captures of a page.
|
|
3
|
+
*
|
|
4
|
+
* Two kinds of comparison, picked by what the files are. Two Markdown files
|
|
5
|
+
* (`scan --markdown`, #61) are compared as text, a passage at a time. Two PNG
|
|
6
|
+
* captures are compared as pictures, and the newer one is written again with
|
|
7
|
+
* the changed regions outlined. A PDF is compared by the Markdown written
|
|
8
|
+
* beside it, because the text a PDF holds is where Chrome drew each glyph,
|
|
9
|
+
* not the passages a person reads. Nothing here needs the browser or the
|
|
10
|
+
* daemon: both captures are already on disk, which is the point of scanning
|
|
11
|
+
* a list on a schedule (#59) and diffing what it wrote.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { basename, dirname, extname, resolve } from 'node:path';
|
|
15
|
+
import { PageScannerError } from '../errors.js';
|
|
16
|
+
import { withExtension } from '../output.js';
|
|
17
|
+
import { diffImages } from './image.js';
|
|
18
|
+
import { decodePng, encodePng, PngError } from './png.js';
|
|
19
|
+
import { diffText, formatUnified, splitFrontMatter } from './text.js';
|
|
20
|
+
function badRequest(message, hint) {
|
|
21
|
+
return new PageScannerError('BAD_REQUEST', message, hint);
|
|
22
|
+
}
|
|
23
|
+
/** The file a side is compared by, and how. */
|
|
24
|
+
function sideOf(path) {
|
|
25
|
+
const extension = extname(path).toLowerCase();
|
|
26
|
+
if (extension === '.md' || extension === '.markdown')
|
|
27
|
+
return { path, kind: 'text' };
|
|
28
|
+
if (extension === '.png')
|
|
29
|
+
return { path, kind: 'visual' };
|
|
30
|
+
if (extension === '.pdf') {
|
|
31
|
+
const beside = withExtension(path, 'md');
|
|
32
|
+
if (existsSync(beside))
|
|
33
|
+
return { path: beside, kind: 'text' };
|
|
34
|
+
throw badRequest(`${basename(path)} is a PDF, and there is no ${basename(beside)} beside it to compare.`, 'Scan with `--markdown beside` so each PDF has its text next to it, and diff those.');
|
|
35
|
+
}
|
|
36
|
+
if (extension === '.jpg' || extension === '.jpeg') {
|
|
37
|
+
throw badRequest(`${basename(path)} is a JPEG, whose compression changes pixels on its own.`, 'Scan with `--format png` to compare pictures, or `--markdown` to compare text.');
|
|
38
|
+
}
|
|
39
|
+
throw badRequest(`${basename(path)} is not a capture diff can read.`, 'It compares two Markdown files (.md), two PNGs, or two PDFs with Markdown beside them.');
|
|
40
|
+
}
|
|
41
|
+
function read(path) {
|
|
42
|
+
try {
|
|
43
|
+
return readFileSync(path);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw badRequest(`Could not read ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function textSide(path, text) {
|
|
50
|
+
const { fields } = splitFrontMatter(text);
|
|
51
|
+
return { path, source: fields.source ?? null, captured: fields.captured ?? null };
|
|
52
|
+
}
|
|
53
|
+
/** Compares two captures and, for pictures, writes the outlined newer one. */
|
|
54
|
+
export function diffCaptures(oldPath, newPath, options = {}) {
|
|
55
|
+
const cwd = options.cwd ?? process.cwd();
|
|
56
|
+
const before = sideOf(resolve(cwd, oldPath));
|
|
57
|
+
const after = sideOf(resolve(cwd, newPath));
|
|
58
|
+
if (before.kind !== after.kind) {
|
|
59
|
+
throw badRequest('The two captures are of different kinds: one is text and the other a picture.', 'Compare two Markdown files, or two PNGs.');
|
|
60
|
+
}
|
|
61
|
+
if (before.kind === 'text') {
|
|
62
|
+
const oldText = read(before.path).toString('utf8');
|
|
63
|
+
const newText = read(after.path).toString('utf8');
|
|
64
|
+
const diff = diffText(oldText, newText);
|
|
65
|
+
const old = textSide(before.path, oldText);
|
|
66
|
+
const next = textSide(after.path, newText);
|
|
67
|
+
return {
|
|
68
|
+
kind: 'text',
|
|
69
|
+
changed: diff.hunks.length > 0,
|
|
70
|
+
old,
|
|
71
|
+
new: next,
|
|
72
|
+
sameAddress: old.source && next.source ? old.source === next.source : null,
|
|
73
|
+
added: diff.added,
|
|
74
|
+
removed: diff.removed,
|
|
75
|
+
hunks: diff.hunks,
|
|
76
|
+
unified: formatUnified(diff, before.path, after.path),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
let oldImage;
|
|
80
|
+
let newImage;
|
|
81
|
+
try {
|
|
82
|
+
oldImage = decodePng(read(before.path));
|
|
83
|
+
newImage = decodePng(read(after.path));
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof PngError)
|
|
87
|
+
throw badRequest(`Could not read the PNG: ${error.message}.`);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
const diff = diffImages(oldImage, newImage);
|
|
91
|
+
const changed = diff.regions.length > 0;
|
|
92
|
+
let path = null;
|
|
93
|
+
if (changed) {
|
|
94
|
+
path = options.out ? resolve(cwd, options.out) : withExtension(after.path, 'diff.png');
|
|
95
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
96
|
+
writeFileSync(path, encodePng(diff.outlined));
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
kind: 'visual',
|
|
100
|
+
changed,
|
|
101
|
+
old: { path: before.path, width: oldImage.width, height: oldImage.height },
|
|
102
|
+
new: { path: after.path, width: newImage.width, height: newImage.height },
|
|
103
|
+
changedFraction: diff.changedFraction,
|
|
104
|
+
regions: diff.regions,
|
|
105
|
+
path,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/diff/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface Rgba {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
/** Four bytes a pixel, row after row. */
|
|
5
|
+
data: Uint8Array;
|
|
6
|
+
}
|
|
7
|
+
export declare class PngError extends Error {
|
|
8
|
+
}
|
|
9
|
+
export declare function decodePng(bytes: Uint8Array): Rgba;
|
|
10
|
+
/** RGBA as a PNG, each row filtered Up, which suits a page's long runs of flat colour. */
|
|
11
|
+
export declare function encodePng(image: Rgba): Buffer;
|
|
12
|
+
//# sourceMappingURL=png.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"png.d.ts","sourceRoot":"","sources":["../../src/diff/png.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,qBAAa,QAAS,SAAQ,KAAK;CAAG;AAYtC,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAwGjD;AAWD,0FAA0F;AAC1F,wBAAgB,SAAS,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,CAuB7C"}
|
package/dist/diff/png.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PNG in and out, on `node:zlib` alone, for the visual diff (#60).
|
|
3
|
+
*
|
|
4
|
+
* The CLI ships one runtime dependency, and an image library would be a
|
|
5
|
+
* second for one command. A capture's PNG is the plainest kind there is: 8
|
|
6
|
+
* bits a channel, not interlaced. Decoding covers every colour type at 8 bits
|
|
7
|
+
* (grey, RGB, palette, grey with alpha, RGBA), which is what Chrome, a canvas
|
|
8
|
+
* and most tools write; anything else is refused by name. Encoding writes
|
|
9
|
+
* RGBA only.
|
|
10
|
+
*/
|
|
11
|
+
import { crc32, deflateSync, inflateSync } from 'node:zlib';
|
|
12
|
+
const SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
13
|
+
export class PngError extends Error {
|
|
14
|
+
}
|
|
15
|
+
const CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
|
|
16
|
+
function paeth(a, b, c) {
|
|
17
|
+
const p = a + b - c;
|
|
18
|
+
const pa = Math.abs(p - a);
|
|
19
|
+
const pb = Math.abs(p - b);
|
|
20
|
+
const pc = Math.abs(p - c);
|
|
21
|
+
return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
|
|
22
|
+
}
|
|
23
|
+
export function decodePng(bytes) {
|
|
24
|
+
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
25
|
+
if (buffer.length < 8 || !buffer.subarray(0, 8).equals(SIGNATURE)) {
|
|
26
|
+
throw new PngError('not a PNG file');
|
|
27
|
+
}
|
|
28
|
+
let width = 0;
|
|
29
|
+
let height = 0;
|
|
30
|
+
let colorType = -1;
|
|
31
|
+
let palette = null;
|
|
32
|
+
let transparency = null;
|
|
33
|
+
const data = [];
|
|
34
|
+
for (let at = 8; at + 8 <= buffer.length;) {
|
|
35
|
+
const length = buffer.readUInt32BE(at);
|
|
36
|
+
const type = buffer.toString('latin1', at + 4, at + 8);
|
|
37
|
+
const body = buffer.subarray(at + 8, at + 8 + length);
|
|
38
|
+
if (type === 'IHDR') {
|
|
39
|
+
width = body.readUInt32BE(0);
|
|
40
|
+
height = body.readUInt32BE(4);
|
|
41
|
+
const depth = body[8];
|
|
42
|
+
colorType = body[9];
|
|
43
|
+
const interlace = body[12];
|
|
44
|
+
if (depth !== 8)
|
|
45
|
+
throw new PngError(`a ${depth}-bit PNG, and only 8 bits a channel is read`);
|
|
46
|
+
if (interlace !== 0)
|
|
47
|
+
throw new PngError('an interlaced PNG, which is not read');
|
|
48
|
+
if (CHANNELS[colorType] === undefined)
|
|
49
|
+
throw new PngError(`PNG colour type ${colorType}`);
|
|
50
|
+
}
|
|
51
|
+
else if (type === 'PLTE')
|
|
52
|
+
palette = body;
|
|
53
|
+
else if (type === 'tRNS')
|
|
54
|
+
transparency = body;
|
|
55
|
+
else if (type === 'IDAT')
|
|
56
|
+
data.push(body);
|
|
57
|
+
else if (type === 'IEND')
|
|
58
|
+
break;
|
|
59
|
+
at += 12 + length;
|
|
60
|
+
}
|
|
61
|
+
if (width === 0 || height === 0)
|
|
62
|
+
throw new PngError('a PNG with no image header');
|
|
63
|
+
const channels = CHANNELS[colorType];
|
|
64
|
+
const stride = width * channels;
|
|
65
|
+
const raw = inflateSync(Buffer.concat(data));
|
|
66
|
+
if (raw.length < (stride + 1) * height)
|
|
67
|
+
throw new PngError('a PNG whose image data is cut short');
|
|
68
|
+
const rows = new Uint8Array(stride * height);
|
|
69
|
+
for (let y = 0; y < height; y++) {
|
|
70
|
+
const filter = raw[y * (stride + 1)];
|
|
71
|
+
const source = y * (stride + 1) + 1;
|
|
72
|
+
const row = y * stride;
|
|
73
|
+
for (let x = 0; x < stride; x++) {
|
|
74
|
+
const value = raw[source + x];
|
|
75
|
+
const left = x >= channels ? rows[row + x - channels] : 0;
|
|
76
|
+
const up = y > 0 ? rows[row - stride + x] : 0;
|
|
77
|
+
const upLeft = y > 0 && x >= channels ? rows[row - stride + x - channels] : 0;
|
|
78
|
+
let out;
|
|
79
|
+
switch (filter) {
|
|
80
|
+
case 0:
|
|
81
|
+
out = value;
|
|
82
|
+
break;
|
|
83
|
+
case 1:
|
|
84
|
+
out = value + left;
|
|
85
|
+
break;
|
|
86
|
+
case 2:
|
|
87
|
+
out = value + up;
|
|
88
|
+
break;
|
|
89
|
+
case 3:
|
|
90
|
+
out = value + ((left + up) >> 1);
|
|
91
|
+
break;
|
|
92
|
+
case 4:
|
|
93
|
+
out = value + paeth(left, up, upLeft);
|
|
94
|
+
break;
|
|
95
|
+
default:
|
|
96
|
+
throw new PngError(`a PNG row with filter ${filter}`);
|
|
97
|
+
}
|
|
98
|
+
rows[row + x] = out & 0xff;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
102
|
+
for (let pixel = 0; pixel < width * height; pixel++) {
|
|
103
|
+
const from = pixel * channels;
|
|
104
|
+
const to = pixel * 4;
|
|
105
|
+
switch (colorType) {
|
|
106
|
+
case 0:
|
|
107
|
+
rgba[to] = rgba[to + 1] = rgba[to + 2] = rows[from];
|
|
108
|
+
rgba[to + 3] = 255;
|
|
109
|
+
break;
|
|
110
|
+
case 2:
|
|
111
|
+
rgba[to] = rows[from];
|
|
112
|
+
rgba[to + 1] = rows[from + 1];
|
|
113
|
+
rgba[to + 2] = rows[from + 2];
|
|
114
|
+
rgba[to + 3] = 255;
|
|
115
|
+
break;
|
|
116
|
+
case 3: {
|
|
117
|
+
const index = rows[from];
|
|
118
|
+
if (!palette)
|
|
119
|
+
throw new PngError('a palette PNG with no palette');
|
|
120
|
+
rgba[to] = palette[index * 3] ?? 0;
|
|
121
|
+
rgba[to + 1] = palette[index * 3 + 1] ?? 0;
|
|
122
|
+
rgba[to + 2] = palette[index * 3 + 2] ?? 0;
|
|
123
|
+
rgba[to + 3] = transparency?.[index] ?? 255;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 4:
|
|
127
|
+
rgba[to] = rgba[to + 1] = rgba[to + 2] = rows[from];
|
|
128
|
+
rgba[to + 3] = rows[from + 1];
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
rgba.set(rows.subarray(from, from + 4), to);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { width, height, data: rgba };
|
|
135
|
+
}
|
|
136
|
+
function chunk(type, body) {
|
|
137
|
+
const head = Buffer.alloc(8);
|
|
138
|
+
head.writeUInt32BE(body.length, 0);
|
|
139
|
+
head.write(type, 4, 'latin1');
|
|
140
|
+
const tail = Buffer.alloc(4);
|
|
141
|
+
tail.writeUInt32BE(crc32(Buffer.concat([head.subarray(4), body])) >>> 0, 0);
|
|
142
|
+
return Buffer.concat([head, body, tail]);
|
|
143
|
+
}
|
|
144
|
+
/** RGBA as a PNG, each row filtered Up, which suits a page's long runs of flat colour. */
|
|
145
|
+
export function encodePng(image) {
|
|
146
|
+
const stride = image.width * 4;
|
|
147
|
+
const raw = Buffer.alloc((stride + 1) * image.height);
|
|
148
|
+
for (let y = 0; y < image.height; y++) {
|
|
149
|
+
const out = y * (stride + 1);
|
|
150
|
+
raw[out] = 2;
|
|
151
|
+
for (let x = 0; x < stride; x++) {
|
|
152
|
+
const value = image.data[y * stride + x];
|
|
153
|
+
const up = y > 0 ? image.data[(y - 1) * stride + x] : 0;
|
|
154
|
+
raw[out + 1 + x] = (value - up) & 0xff;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const header = Buffer.alloc(13);
|
|
158
|
+
header.writeUInt32BE(image.width, 0);
|
|
159
|
+
header.writeUInt32BE(image.height, 4);
|
|
160
|
+
header[8] = 8;
|
|
161
|
+
header[9] = 6;
|
|
162
|
+
return Buffer.concat([
|
|
163
|
+
SIGNATURE,
|
|
164
|
+
chunk('IHDR', header),
|
|
165
|
+
chunk('IDAT', deflateSync(raw)),
|
|
166
|
+
chunk('IEND', new Uint8Array()),
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=png.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"png.js","sourceRoot":"","sources":["../../src/diff/png.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What changed in a page's text between two captures (#60): the Markdown
|
|
3
|
+
* `scan --markdown` writes (#61), compared a line at a time.
|
|
4
|
+
*
|
|
5
|
+
* A line of that Markdown is a block of the page (a heading, a paragraph, a
|
|
6
|
+
* list item, a table row), so a line diff is a passage diff: a reworded
|
|
7
|
+
* sentence shows as its paragraph removed and added again, which is how a
|
|
8
|
+
* person reading a policy for changes wants it. The front matter is set
|
|
9
|
+
* aside first, because its capture time differs on every run and would make
|
|
10
|
+
* two identical pages look changed.
|
|
11
|
+
*
|
|
12
|
+
* Myers' algorithm, the one `diff` and git use, with the common head and tail
|
|
13
|
+
* trimmed first. Past `MAX_EDIT_DISTANCE` edits it stops looking for the
|
|
14
|
+
* shortest script and reports the rest as removed and added whole: two
|
|
15
|
+
* captures that different are a different page, and the search would
|
|
16
|
+
* otherwise cost memory in the square of the edits.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MAX_EDIT_DISTANCE = 2000;
|
|
19
|
+
/** Unchanged lines kept around each change, as `diff -u` does. */
|
|
20
|
+
export declare const CONTEXT_LINES = 3;
|
|
21
|
+
export type DiffOp = ' ' | '-' | '+';
|
|
22
|
+
export interface DiffLine {
|
|
23
|
+
op: DiffOp;
|
|
24
|
+
text: string;
|
|
25
|
+
}
|
|
26
|
+
export interface Hunk {
|
|
27
|
+
/** 1-based first line in the old text, and how many lines of it the hunk covers. */
|
|
28
|
+
oldStart: number;
|
|
29
|
+
oldLines: number;
|
|
30
|
+
newStart: number;
|
|
31
|
+
newLines: number;
|
|
32
|
+
lines: DiffLine[];
|
|
33
|
+
}
|
|
34
|
+
export interface TextDiff {
|
|
35
|
+
added: number;
|
|
36
|
+
removed: number;
|
|
37
|
+
hunks: Hunk[];
|
|
38
|
+
}
|
|
39
|
+
export interface FrontMatter {
|
|
40
|
+
/** Each `key: value` line, with a JSON-quoted value unquoted. */
|
|
41
|
+
fields: Record<string, string>;
|
|
42
|
+
body: string;
|
|
43
|
+
}
|
|
44
|
+
/** The YAML front matter a Markdown export starts with, taken off the text. */
|
|
45
|
+
export declare function splitFrontMatter(text: string): FrontMatter;
|
|
46
|
+
/** The edit script from `a` to `b`, as a line each: kept, removed or added. */
|
|
47
|
+
export declare function diffLines(a: readonly string[], b: readonly string[]): DiffLine[];
|
|
48
|
+
/** The script cut into hunks with `context` unchanged lines around each change. */
|
|
49
|
+
export declare function toHunks(lines: readonly DiffLine[], context?: number): Hunk[];
|
|
50
|
+
/** Two Markdown captures compared, front matter aside. */
|
|
51
|
+
export declare function diffText(before: string, after: string): TextDiff;
|
|
52
|
+
/** The hunks as `diff -u` prints them, under a header naming the two files. */
|
|
53
|
+
export declare function formatUnified(diff: TextDiff, oldLabel: string, newLabel: string): string;
|
|
54
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/diff/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,kEAAkE;AAClE,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B,MAAM,MAAM,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAErC,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,IAAI;IACnB,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAoB1D;AAED,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,EAAE,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,EAAE,CAiBhF;AA2ED,mFAAmF;AACnF,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,EAAE,OAAO,SAAgB,GAAG,IAAI,EAAE,CA+BnF;AAED,0DAA0D;AAC1D,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CAUhE;AAED,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAYxF"}
|