@pdf-testkit/core 0.1.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/dist/diff/diff.d.ts +9 -0
- package/dist/diff/diff.d.ts.map +1 -0
- package/dist/diff/diff.js +139 -0
- package/dist/diff/diff.js.map +1 -0
- package/dist/diff/match.d.ts +21 -0
- package/dist/diff/match.d.ts.map +1 -0
- package/dist/diff/match.js +133 -0
- package/dist/diff/match.js.map +1 -0
- package/dist/events.d.ts +79 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +11 -0
- package/dist/events.js.map +1 -0
- package/dist/extract/finalize.d.ts +16 -0
- package/dist/extract/finalize.d.ts.map +1 -0
- package/dist/extract/finalize.js +88 -0
- package/dist/extract/finalize.js.map +1 -0
- package/dist/extract/fromFormeLayout.d.ts +82 -0
- package/dist/extract/fromFormeLayout.d.ts.map +1 -0
- package/dist/extract/fromFormeLayout.js +324 -0
- package/dist/extract/fromFormeLayout.js.map +1 -0
- package/dist/extract/fromPdf.d.ts +15 -0
- package/dist/extract/fromPdf.d.ts.map +1 -0
- package/dist/extract/fromPdf.js +120 -0
- package/dist/extract/fromPdf.js.map +1 -0
- package/dist/extract/pdfjs/headings.d.ts +16 -0
- package/dist/extract/pdfjs/headings.d.ts.map +1 -0
- package/dist/extract/pdfjs/headings.js +39 -0
- package/dist/extract/pdfjs/headings.js.map +1 -0
- package/dist/extract/pdfjs/loadDocument.d.ts +32 -0
- package/dist/extract/pdfjs/loadDocument.d.ts.map +1 -0
- package/dist/extract/pdfjs/loadDocument.js +45 -0
- package/dist/extract/pdfjs/loadDocument.js.map +1 -0
- package/dist/extract/pdfjs/overflow.d.ts +16 -0
- package/dist/extract/pdfjs/overflow.d.ts.map +1 -0
- package/dist/extract/pdfjs/overflow.js +35 -0
- package/dist/extract/pdfjs/overflow.js.map +1 -0
- package/dist/extract/pdfjs/tables.d.ts +31 -0
- package/dist/extract/pdfjs/tables.d.ts.map +1 -0
- package/dist/extract/pdfjs/tables.js +142 -0
- package/dist/extract/pdfjs/tables.js.map +1 -0
- package/dist/extract/pdfjs/textRuns.d.ts +26 -0
- package/dist/extract/pdfjs/textRuns.d.ts.map +1 -0
- package/dist/extract/pdfjs/textRuns.js +69 -0
- package/dist/extract/pdfjs/textRuns.js.map +1 -0
- package/dist/extract/raw.d.ts +29 -0
- package/dist/extract/raw.d.ts.map +1 -0
- package/dist/extract/raw.js +2 -0
- package/dist/extract/raw.js.map +1 -0
- package/dist/geometry.d.ts +13 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +41 -0
- package/dist/geometry.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/snapshot/io.d.ts +4 -0
- package/dist/snapshot/io.d.ts.map +1 -0
- package/dist/snapshot/io.js +12 -0
- package/dist/snapshot/io.js.map +1 -0
- package/dist/snapshot/load.d.ts +9 -0
- package/dist/snapshot/load.d.ts.map +1 -0
- package/dist/snapshot/load.js +25 -0
- package/dist/snapshot/load.js.map +1 -0
- package/dist/snapshot/serialize.d.ts +14 -0
- package/dist/snapshot/serialize.d.ts.map +1 -0
- package/dist/snapshot/serialize.js +31 -0
- package/dist/snapshot/serialize.js.map +1 -0
- package/dist/text/headingLevels.d.ts +11 -0
- package/dist/text/headingLevels.d.ts.map +1 -0
- package/dist/text/headingLevels.js +40 -0
- package/dist/text/headingLevels.js.map +1 -0
- package/dist/text/normalize.d.ts +17 -0
- package/dist/text/normalize.d.ts.map +1 -0
- package/dist/text/normalize.js +67 -0
- package/dist/text/normalize.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DiffOptions, DiffResult } from '../events.js';
|
|
2
|
+
import type { StructuralSnapshot } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Compare two structural snapshots and emit typed semantic events. Never emits a
|
|
5
|
+
* raw JSON blob or a similarity score — only named events, which is the entire
|
|
6
|
+
* differentiation of the tool.
|
|
7
|
+
*/
|
|
8
|
+
export declare function diffSnapshots(baseline: StructuralSnapshot, next: StructuralSnapshot, opts?: DiffOptions): DiffResult;
|
|
9
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/diff/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAA8C,MAAM,cAAc,CAAC;AAExG,OAAO,KAAK,EAA4B,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAKhF;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,kBAAkB,EAC5B,IAAI,EAAE,kBAAkB,EACxB,IAAI,GAAE,WAAgB,GACrB,UAAU,CAwHZ"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { DEFAULT_POSITION_THRESHOLD_PTS, DEFAULT_SEVERITY } from '../events.js';
|
|
2
|
+
import { centerDistance } from '../geometry.js';
|
|
3
|
+
import { textPreview } from '../text/normalize.js';
|
|
4
|
+
import { matchNodes } from './match.js';
|
|
5
|
+
/**
|
|
6
|
+
* Compare two structural snapshots and emit typed semantic events. Never emits a
|
|
7
|
+
* raw JSON blob or a similarity score — only named events, which is the entire
|
|
8
|
+
* differentiation of the tool.
|
|
9
|
+
*/
|
|
10
|
+
export function diffSnapshots(baseline, next, opts = {}) {
|
|
11
|
+
// Identical structure -> no work. This is also what keeps re-serializing the
|
|
12
|
+
// same document from ever producing a spurious diff.
|
|
13
|
+
if (baseline.contentHash === next.contentHash) {
|
|
14
|
+
return { changed: false, events: [], baselineHash: baseline.contentHash, newHash: next.contentHash };
|
|
15
|
+
}
|
|
16
|
+
const threshold = opts.positionThresholdPts ?? DEFAULT_POSITION_THRESHOLD_PTS;
|
|
17
|
+
const ignore = new Set(opts.ignoreRoles ?? []);
|
|
18
|
+
const minConfidence = opts.minConfidence ?? 0;
|
|
19
|
+
const severityOf = (type) => opts.severityOverrides?.[type] ?? DEFAULT_SEVERITY[type];
|
|
20
|
+
const baseNodes = baseline.nodes.filter((n) => !ignore.has(n.role));
|
|
21
|
+
const nextNodes = next.nodes.filter((n) => !ignore.has(n.role));
|
|
22
|
+
const events = [];
|
|
23
|
+
if (baseline.pageCount !== next.pageCount) {
|
|
24
|
+
events.push({
|
|
25
|
+
type: 'page-count-changed',
|
|
26
|
+
severity: severityOf('page-count-changed'),
|
|
27
|
+
confidence: 1,
|
|
28
|
+
from: baseline.pageCount,
|
|
29
|
+
to: next.pageCount,
|
|
30
|
+
message: `page count changed ${baseline.pageCount} → ${next.pageCount}`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const { pairs, added, removed } = matchNodes(baseNodes, nextNodes);
|
|
34
|
+
for (const { base, next: after } of pairs) {
|
|
35
|
+
const conf = Math.min(base.confidence, after.confidence);
|
|
36
|
+
if (after.role === 'table') {
|
|
37
|
+
const movedPage = base.pageIndex !== after.pageIndex;
|
|
38
|
+
const movedOnPage = centerDistance(base.bbox, after.bbox) > threshold;
|
|
39
|
+
if (movedPage || movedOnPage) {
|
|
40
|
+
events.push({
|
|
41
|
+
type: 'table-moved',
|
|
42
|
+
severity: severityOf('table-moved'),
|
|
43
|
+
confidence: conf,
|
|
44
|
+
nodeId: after.id,
|
|
45
|
+
fromPage: base.pageIndex,
|
|
46
|
+
toPage: after.pageIndex,
|
|
47
|
+
fromBBox: base.bbox,
|
|
48
|
+
toBBox: after.bbox,
|
|
49
|
+
message: movedPage
|
|
50
|
+
? `table moved from page ${base.pageIndex + 1} to page ${after.pageIndex + 1}`
|
|
51
|
+
: `table moved ${Math.round(centerDistance(base.bbox, after.bbox))}pt on page ${after.pageIndex + 1}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (base.pageIndex !== after.pageIndex) {
|
|
56
|
+
events.push({
|
|
57
|
+
type: 'element-moved-to-different-page',
|
|
58
|
+
severity: severityOf('element-moved-to-different-page'),
|
|
59
|
+
confidence: conf,
|
|
60
|
+
nodeId: after.id,
|
|
61
|
+
role: after.role,
|
|
62
|
+
textPreview: textPreview(after.text),
|
|
63
|
+
fromPage: base.pageIndex,
|
|
64
|
+
toPage: after.pageIndex,
|
|
65
|
+
message: `${describe(after)} moved from page ${base.pageIndex + 1} to page ${after.pageIndex + 1}`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (base.role === 'heading' && after.role === 'heading' && base.headingLevel !== after.headingLevel) {
|
|
69
|
+
events.push({
|
|
70
|
+
type: 'heading-hierarchy-changed',
|
|
71
|
+
severity: severityOf('heading-hierarchy-changed'),
|
|
72
|
+
confidence: conf,
|
|
73
|
+
nodeId: after.id,
|
|
74
|
+
textPreview: textPreview(after.text),
|
|
75
|
+
fromLevel: base.headingLevel,
|
|
76
|
+
toLevel: after.headingLevel,
|
|
77
|
+
message: `heading "${textPreview(after.text)}" hierarchy changed ${fmtLevel(base.headingLevel)} → ${fmtLevel(after.headingLevel)}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// Overflow is a regression only when it is newly present in this run.
|
|
81
|
+
if (after.overflow && !base.overflow) {
|
|
82
|
+
events.push(overflowEvent(after, severityOf('text-overflowed-container'), conf));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const node of added) {
|
|
86
|
+
events.push({
|
|
87
|
+
type: 'element-added',
|
|
88
|
+
severity: severityOf('element-added'),
|
|
89
|
+
confidence: node.confidence,
|
|
90
|
+
nodeId: node.id,
|
|
91
|
+
role: node.role,
|
|
92
|
+
textPreview: textPreview(node.text),
|
|
93
|
+
pageIndex: node.pageIndex,
|
|
94
|
+
message: `${describe(node)} added on page ${node.pageIndex + 1}`,
|
|
95
|
+
});
|
|
96
|
+
if (node.overflow) {
|
|
97
|
+
events.push(overflowEvent(node, severityOf('text-overflowed-container'), node.confidence));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const node of removed) {
|
|
101
|
+
events.push({
|
|
102
|
+
type: 'element-removed',
|
|
103
|
+
severity: severityOf('element-removed'),
|
|
104
|
+
confidence: node.confidence,
|
|
105
|
+
role: node.role,
|
|
106
|
+
textPreview: textPreview(node.text),
|
|
107
|
+
pageIndex: node.pageIndex,
|
|
108
|
+
message: `${describe(node)} removed from page ${node.pageIndex + 1}`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const filtered = events.filter((e) => e.confidence >= minConfidence);
|
|
112
|
+
return {
|
|
113
|
+
changed: filtered.length > 0,
|
|
114
|
+
events: filtered,
|
|
115
|
+
baselineHash: baseline.contentHash,
|
|
116
|
+
newHash: next.contentHash,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function overflowEvent(node, severity, confidence) {
|
|
120
|
+
const o = node.overflow;
|
|
121
|
+
return {
|
|
122
|
+
type: 'text-overflowed-container',
|
|
123
|
+
severity,
|
|
124
|
+
confidence,
|
|
125
|
+
nodeId: node.id,
|
|
126
|
+
textPreview: textPreview(node.text),
|
|
127
|
+
overflow: o,
|
|
128
|
+
pageIndex: node.pageIndex,
|
|
129
|
+
message: `${describe(node)} overflowed its ${o.container} box by ${o.overflowPts}pt (${o.axis})`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function describe(node) {
|
|
133
|
+
const preview = textPreview(node.text);
|
|
134
|
+
return preview ? `${node.role} "${preview}"` : node.role;
|
|
135
|
+
}
|
|
136
|
+
function fmtLevel(level) {
|
|
137
|
+
return level == null ? '—' : `H${level}`;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/diff/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { StructuralNode } from '../types.js';
|
|
2
|
+
export interface NodePair {
|
|
3
|
+
base: StructuralNode;
|
|
4
|
+
next: StructuralNode;
|
|
5
|
+
}
|
|
6
|
+
export interface MatchResult {
|
|
7
|
+
pairs: NodePair[];
|
|
8
|
+
added: StructuralNode[];
|
|
9
|
+
removed: StructuralNode[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Pair baseline nodes with new-run nodes so the diff engine can tell a *moved*
|
|
13
|
+
* element from a *removed + added* pair. Staged and greedy; each matched node
|
|
14
|
+
* leaves the pool.
|
|
15
|
+
*
|
|
16
|
+
* The stages are ordered from most to least certain so that identical content
|
|
17
|
+
* (stage 1) and minor edits (stage 2) never fall through to remove+add — the
|
|
18
|
+
* phantom-churn failure mode that makes a diff tool noisy.
|
|
19
|
+
*/
|
|
20
|
+
export declare function matchNodes(baseNodes: StructuralNode[], nextNodes: StructuralNode[]): MatchResult;
|
|
21
|
+
//# sourceMappingURL=match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/diff/match.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAQD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,cAAc,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,WAAW,CAmFhG"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { centerDistance } from '../geometry.js';
|
|
2
|
+
import { textSimilarity } from '../text/normalize.js';
|
|
3
|
+
const FUZZY_THRESHOLD = 0.85;
|
|
4
|
+
/** Boxes closer than this (center-to-center, points) are the same slot. Below a
|
|
5
|
+
* line height, so a deleted paragraph (which shifts others by a full line) never
|
|
6
|
+
* false-matches, while an in-place text edit always does. */
|
|
7
|
+
const POSITION_MATCH_TOL = 4;
|
|
8
|
+
/**
|
|
9
|
+
* Pair baseline nodes with new-run nodes so the diff engine can tell a *moved*
|
|
10
|
+
* element from a *removed + added* pair. Staged and greedy; each matched node
|
|
11
|
+
* leaves the pool.
|
|
12
|
+
*
|
|
13
|
+
* The stages are ordered from most to least certain so that identical content
|
|
14
|
+
* (stage 1) and minor edits (stage 2) never fall through to remove+add — the
|
|
15
|
+
* phantom-churn failure mode that makes a diff tool noisy.
|
|
16
|
+
*/
|
|
17
|
+
export function matchNodes(baseNodes, nextNodes) {
|
|
18
|
+
const baseLeft = new Set(baseNodes);
|
|
19
|
+
const nextLeft = new Set(nextNodes);
|
|
20
|
+
const pairs = [];
|
|
21
|
+
// Stage 1 — exact stable key (role + normText + headingLevel). Within a key,
|
|
22
|
+
// pair by (pageIndex, order) rank so identical repeated content stays aligned.
|
|
23
|
+
const nextByKey = groupBy([...nextLeft], exactKey);
|
|
24
|
+
for (const base of [...baseLeft].sort(byReadingOrder)) {
|
|
25
|
+
const bucket = nextByKey.get(exactKey(base));
|
|
26
|
+
if (!bucket || bucket.length === 0)
|
|
27
|
+
continue;
|
|
28
|
+
const next = bucket.shift();
|
|
29
|
+
pairs.push({ base, next });
|
|
30
|
+
baseLeft.delete(base);
|
|
31
|
+
nextLeft.delete(next);
|
|
32
|
+
}
|
|
33
|
+
// Stage 2 — fuzzy text for text/heading nodes (a changed number, a typo fix).
|
|
34
|
+
for (const base of [...baseLeft]) {
|
|
35
|
+
if (!isTexty(base) || base.normText == null)
|
|
36
|
+
continue;
|
|
37
|
+
let best = null;
|
|
38
|
+
let bestScore = FUZZY_THRESHOLD;
|
|
39
|
+
for (const cand of nextLeft) {
|
|
40
|
+
if (cand.role !== base.role || cand.normText == null)
|
|
41
|
+
continue;
|
|
42
|
+
const score = textSimilarity(base.normText, cand.normText);
|
|
43
|
+
if (score >= bestScore) {
|
|
44
|
+
bestScore = score;
|
|
45
|
+
best = cand;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (best) {
|
|
49
|
+
pairs.push({ base, next: best });
|
|
50
|
+
baseLeft.delete(base);
|
|
51
|
+
nextLeft.delete(best);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Stage 2.5 — positional fallback for text/heading nodes: same role, same
|
|
55
|
+
// page, and near-identical box is the same element with edited content. This
|
|
56
|
+
// is what stops a small text/punctuation edit from becoming remove+add churn
|
|
57
|
+
// (the fuzzy stage can't catch every rewrite).
|
|
58
|
+
for (const base of [...baseLeft]) {
|
|
59
|
+
if (!isTexty(base))
|
|
60
|
+
continue;
|
|
61
|
+
let best = null;
|
|
62
|
+
let bestD = POSITION_MATCH_TOL;
|
|
63
|
+
for (const cand of nextLeft) {
|
|
64
|
+
if (cand.role !== base.role || cand.pageIndex !== base.pageIndex)
|
|
65
|
+
continue;
|
|
66
|
+
const d = centerDistance(base.bbox, cand.bbox);
|
|
67
|
+
if (d <= bestD) {
|
|
68
|
+
bestD = d;
|
|
69
|
+
best = cand;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (best) {
|
|
73
|
+
pairs.push({ base, next: best });
|
|
74
|
+
baseLeft.delete(base);
|
|
75
|
+
nextLeft.delete(best);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Stage 3 — structural pairing for tables (and other non-text containers) by
|
|
79
|
+
// role + table signature, nearest in page order.
|
|
80
|
+
for (const base of [...baseLeft]) {
|
|
81
|
+
if (isTexty(base))
|
|
82
|
+
continue;
|
|
83
|
+
let best = null;
|
|
84
|
+
let bestCost = Infinity;
|
|
85
|
+
for (const cand of nextLeft) {
|
|
86
|
+
if (cand.role !== base.role)
|
|
87
|
+
continue;
|
|
88
|
+
if (!sameTableShape(base, cand))
|
|
89
|
+
continue;
|
|
90
|
+
const cost = Math.abs(cand.pageIndex - base.pageIndex) * 1000 + Math.abs(cand.order - base.order);
|
|
91
|
+
if (cost < bestCost) {
|
|
92
|
+
bestCost = cost;
|
|
93
|
+
best = cand;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (best) {
|
|
97
|
+
pairs.push({ base, next: best });
|
|
98
|
+
baseLeft.delete(base);
|
|
99
|
+
nextLeft.delete(best);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { pairs, added: [...nextLeft], removed: [...baseLeft] };
|
|
103
|
+
}
|
|
104
|
+
function exactKey(n) {
|
|
105
|
+
return `${n.role}|${n.normText ?? ''}|${n.headingLevel ?? ''}`;
|
|
106
|
+
}
|
|
107
|
+
function isTexty(n) {
|
|
108
|
+
return n.role === 'text' || n.role === 'heading';
|
|
109
|
+
}
|
|
110
|
+
function sameTableShape(a, b) {
|
|
111
|
+
if (!a.table || !b.table)
|
|
112
|
+
return true; // non-tables: shape is irrelevant
|
|
113
|
+
return a.table.rows === b.table.rows && a.table.cols === b.table.cols;
|
|
114
|
+
}
|
|
115
|
+
function byReadingOrder(a, b) {
|
|
116
|
+
return a.pageIndex - b.pageIndex || a.order - b.order;
|
|
117
|
+
}
|
|
118
|
+
function groupBy(items, key) {
|
|
119
|
+
const map = new Map();
|
|
120
|
+
for (const item of items) {
|
|
121
|
+
const k = key(item);
|
|
122
|
+
const bucket = map.get(k);
|
|
123
|
+
if (bucket)
|
|
124
|
+
bucket.push(item);
|
|
125
|
+
else
|
|
126
|
+
map.set(k, [item]);
|
|
127
|
+
}
|
|
128
|
+
// Keep each bucket in reading order for stable stage-1 pairing.
|
|
129
|
+
for (const bucket of map.values())
|
|
130
|
+
bucket.sort(byReadingOrder);
|
|
131
|
+
return map;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/diff/match.ts"],"names":[],"mappings":"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"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { BBox, NodeRole } from './types.js';
|
|
2
|
+
export type SemanticEventType = 'page-count-changed' | 'element-moved-to-different-page' | 'table-moved' | 'heading-hierarchy-changed' | 'text-overflowed-container' | 'element-added' | 'element-removed';
|
|
3
|
+
export type Severity = 'info' | 'warn' | 'error';
|
|
4
|
+
export interface BaseEvent {
|
|
5
|
+
type: SemanticEventType;
|
|
6
|
+
severity: Severity;
|
|
7
|
+
/** Human-readable, used in matcher/CLI failure output. */
|
|
8
|
+
message: string;
|
|
9
|
+
/** Min confidence of the involved nodes (0..1). */
|
|
10
|
+
confidence: number;
|
|
11
|
+
}
|
|
12
|
+
export interface PageCountChangedEvent extends BaseEvent {
|
|
13
|
+
type: 'page-count-changed';
|
|
14
|
+
from: number;
|
|
15
|
+
to: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ElementMovedToPageEvent extends BaseEvent {
|
|
18
|
+
type: 'element-moved-to-different-page';
|
|
19
|
+
nodeId: string;
|
|
20
|
+
role: NodeRole;
|
|
21
|
+
textPreview: string;
|
|
22
|
+
fromPage: number;
|
|
23
|
+
toPage: number;
|
|
24
|
+
}
|
|
25
|
+
export interface TableMovedEvent extends BaseEvent {
|
|
26
|
+
type: 'table-moved';
|
|
27
|
+
nodeId: string;
|
|
28
|
+
fromPage: number;
|
|
29
|
+
toPage: number;
|
|
30
|
+
fromBBox: BBox;
|
|
31
|
+
toBBox: BBox;
|
|
32
|
+
}
|
|
33
|
+
export interface HeadingHierarchyChangedEvent extends BaseEvent {
|
|
34
|
+
type: 'heading-hierarchy-changed';
|
|
35
|
+
nodeId: string;
|
|
36
|
+
textPreview: string;
|
|
37
|
+
fromLevel: number | null;
|
|
38
|
+
toLevel: number | null;
|
|
39
|
+
}
|
|
40
|
+
export interface TextOverflowedEvent extends BaseEvent {
|
|
41
|
+
type: 'text-overflowed-container';
|
|
42
|
+
nodeId: string;
|
|
43
|
+
textPreview: string;
|
|
44
|
+
overflow: import('./types.js').OverflowInfo;
|
|
45
|
+
pageIndex: number;
|
|
46
|
+
}
|
|
47
|
+
export interface ElementAddedEvent extends BaseEvent {
|
|
48
|
+
type: 'element-added';
|
|
49
|
+
nodeId: string;
|
|
50
|
+
role: NodeRole;
|
|
51
|
+
textPreview: string;
|
|
52
|
+
pageIndex: number;
|
|
53
|
+
}
|
|
54
|
+
export interface ElementRemovedEvent extends BaseEvent {
|
|
55
|
+
type: 'element-removed';
|
|
56
|
+
role: NodeRole;
|
|
57
|
+
textPreview: string;
|
|
58
|
+
pageIndex: number;
|
|
59
|
+
}
|
|
60
|
+
export type SemanticEvent = PageCountChangedEvent | ElementMovedToPageEvent | TableMovedEvent | HeadingHierarchyChangedEvent | TextOverflowedEvent | ElementAddedEvent | ElementRemovedEvent;
|
|
61
|
+
export interface DiffResult {
|
|
62
|
+
changed: boolean;
|
|
63
|
+
events: SemanticEvent[];
|
|
64
|
+
baselineHash: string;
|
|
65
|
+
newHash: string;
|
|
66
|
+
}
|
|
67
|
+
export interface DiffOptions {
|
|
68
|
+
/** Override the default severity for any event type. */
|
|
69
|
+
severityOverrides?: Partial<Record<SemanticEventType, Severity>>;
|
|
70
|
+
/** On-page movement beyond this many points counts as a table move. Default 24. */
|
|
71
|
+
positionThresholdPts?: number;
|
|
72
|
+
/** Roles to ignore entirely when diffing (e.g. ['image']). */
|
|
73
|
+
ignoreRoles?: NodeRole[];
|
|
74
|
+
/** Drop events whose confidence is below this (0..1). Default 0. */
|
|
75
|
+
minConfidence?: number;
|
|
76
|
+
}
|
|
77
|
+
export declare const DEFAULT_SEVERITY: Record<SemanticEventType, Severity>;
|
|
78
|
+
export declare const DEFAULT_POSITION_THRESHOLD_PTS = 24;
|
|
79
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const DEFAULT_SEVERITY = {
|
|
2
|
+
'page-count-changed': 'error',
|
|
3
|
+
'heading-hierarchy-changed': 'error',
|
|
4
|
+
'text-overflowed-container': 'error',
|
|
5
|
+
'element-moved-to-different-page': 'warn',
|
|
6
|
+
'table-moved': 'warn',
|
|
7
|
+
'element-added': 'warn',
|
|
8
|
+
'element-removed': 'warn',
|
|
9
|
+
};
|
|
10
|
+
export const DEFAULT_POSITION_THRESHOLD_PTS = 24;
|
|
11
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAwGA,MAAM,CAAC,MAAM,gBAAgB,GAAwC;IACnE,oBAAoB,EAAE,OAAO;IAC7B,2BAA2B,EAAE,OAAO;IACpC,2BAA2B,EAAE,OAAO;IACpC,iCAAiC,EAAE,MAAM;IACzC,aAAa,EAAE,MAAM;IACrB,eAAe,EAAE,MAAM;IACvB,iBAAiB,EAAE,MAAM;CAC1B,CAAC;AAEF,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Producer, StructuralSnapshot } from '../types.js';
|
|
2
|
+
import type { RawNode, RawPage } from './raw.js';
|
|
3
|
+
/**
|
|
4
|
+
* Shared post-processing that turns raw extractor output into a canonical
|
|
5
|
+
* `StructuralSnapshot`: assigns reading order, stable ids, normalized text,
|
|
6
|
+
* rounds all coordinates, links parents, and computes the content hash. Both
|
|
7
|
+
* `fromFormeLayout` and `fromPdf` converge here so downstream code is identical
|
|
8
|
+
* regardless of producer.
|
|
9
|
+
*/
|
|
10
|
+
export declare function finalize(producer: Producer, rawPages: RawPage[], rawNodes: RawNode[], opts?: {
|
|
11
|
+
source?: {
|
|
12
|
+
name?: string;
|
|
13
|
+
};
|
|
14
|
+
createdAt?: string;
|
|
15
|
+
}): StructuralSnapshot;
|
|
16
|
+
//# sourceMappingURL=finalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../src/extract/finalize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAgC,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAI9F,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEjD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,OAAO,EAAE,EACnB,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD,kBAAkB,CA0EpB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { median, round1, roundBBox } from '../geometry.js';
|
|
2
|
+
import { normalizeText } from '../text/normalize.js';
|
|
3
|
+
import { computeContentHash } from '../snapshot/serialize.js';
|
|
4
|
+
/**
|
|
5
|
+
* Shared post-processing that turns raw extractor output into a canonical
|
|
6
|
+
* `StructuralSnapshot`: assigns reading order, stable ids, normalized text,
|
|
7
|
+
* rounds all coordinates, links parents, and computes the content hash. Both
|
|
8
|
+
* `fromFormeLayout` and `fromPdf` converge here so downstream code is identical
|
|
9
|
+
* regardless of producer.
|
|
10
|
+
*/
|
|
11
|
+
export function finalize(producer, rawPages, rawNodes, opts) {
|
|
12
|
+
const pages = [...rawPages].sort((a, b) => a.index - b.index);
|
|
13
|
+
// Reading order + a per-page ordinal counter keyed by role, computed on nodes
|
|
14
|
+
// sorted by y-band then x. y-band tolerance derives from the page's median
|
|
15
|
+
// line height so items on the same visual line share a band.
|
|
16
|
+
const finalIdByTemp = new Map();
|
|
17
|
+
const ordered = [];
|
|
18
|
+
const orderByTemp = new Map();
|
|
19
|
+
const nodeIdsByPage = new Map();
|
|
20
|
+
for (const page of pages) {
|
|
21
|
+
const pageNodes = rawNodes.filter((n) => n.pageIndex === page.index);
|
|
22
|
+
const rowTol = readingRowTolerance(pageNodes);
|
|
23
|
+
const band = (y) => Math.round(y / rowTol);
|
|
24
|
+
pageNodes.sort((a, b) => band(a.bbox.y) - band(b.bbox.y) || a.bbox.x - b.bbox.x);
|
|
25
|
+
const ordinalByRole = new Map();
|
|
26
|
+
const ids = [];
|
|
27
|
+
pageNodes.forEach((node, i) => {
|
|
28
|
+
const ordinal = ordinalByRole.get(node.role) ?? 0;
|
|
29
|
+
ordinalByRole.set(node.role, ordinal + 1);
|
|
30
|
+
const id = `${page.index}:${node.role}:${ordinal}`;
|
|
31
|
+
finalIdByTemp.set(node.tempId, id);
|
|
32
|
+
orderByTemp.set(node.tempId, i);
|
|
33
|
+
ids.push(id);
|
|
34
|
+
ordered.push(node);
|
|
35
|
+
});
|
|
36
|
+
nodeIdsByPage.set(page.index, ids);
|
|
37
|
+
}
|
|
38
|
+
const nodes = ordered.map((raw) => {
|
|
39
|
+
const id = finalIdByTemp.get(raw.tempId);
|
|
40
|
+
const parentId = raw.parentTempId != null ? finalIdByTemp.get(raw.parentTempId) ?? null : null;
|
|
41
|
+
const node = {
|
|
42
|
+
id,
|
|
43
|
+
role: raw.role,
|
|
44
|
+
pageIndex: raw.pageIndex,
|
|
45
|
+
bbox: roundBBox(raw.bbox),
|
|
46
|
+
order: orderByTemp.get(raw.tempId) ?? 0,
|
|
47
|
+
parentId,
|
|
48
|
+
text: raw.text,
|
|
49
|
+
normText: normalizeText(raw.text),
|
|
50
|
+
font: raw.font
|
|
51
|
+
? { ...raw.font, size: raw.font.size == null ? null : round1(raw.font.size) }
|
|
52
|
+
: null,
|
|
53
|
+
headingLevel: raw.headingLevel,
|
|
54
|
+
overflow: raw.overflow,
|
|
55
|
+
confidence: raw.confidence,
|
|
56
|
+
};
|
|
57
|
+
if (raw.table)
|
|
58
|
+
node.table = raw.table;
|
|
59
|
+
return node;
|
|
60
|
+
});
|
|
61
|
+
const pageSnapshots = pages.map((p) => ({
|
|
62
|
+
index: p.index,
|
|
63
|
+
width: round1(p.width),
|
|
64
|
+
height: round1(p.height),
|
|
65
|
+
contentBox: roundBBox(p.contentBox),
|
|
66
|
+
nodeIds: nodeIdsByPage.get(p.index) ?? [],
|
|
67
|
+
}));
|
|
68
|
+
const withoutHash = {
|
|
69
|
+
version: 1,
|
|
70
|
+
producer,
|
|
71
|
+
createdAt: opts?.createdAt ?? new Date().toISOString(),
|
|
72
|
+
...(opts?.source ? { source: opts.source } : {}),
|
|
73
|
+
pageCount: pageSnapshots.length,
|
|
74
|
+
pages: pageSnapshots,
|
|
75
|
+
nodes,
|
|
76
|
+
};
|
|
77
|
+
return { ...withoutHash, contentHash: computeContentHash(withoutHash) };
|
|
78
|
+
}
|
|
79
|
+
function readingRowTolerance(nodes) {
|
|
80
|
+
const heights = nodes
|
|
81
|
+
.filter((n) => n.role === 'text' || n.role === 'heading' || n.role === 'cell')
|
|
82
|
+
.map((n) => n.font?.size ?? n.bbox.height)
|
|
83
|
+
.filter((h) => h > 0);
|
|
84
|
+
if (heights.length === 0)
|
|
85
|
+
return 6;
|
|
86
|
+
return Math.max(2, 0.5 * median(heights));
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=finalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finalize.js","sourceRoot":"","sources":["../../src/extract/finalize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { NodeRole, StructuralSnapshot } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural mirror of `@formepdf/core`'s `LayoutInfo`. Declared locally
|
|
4
|
+
* so `@pdf-testkit/core` never has to depend on `@formepdf/core` — any object of
|
|
5
|
+
* this shape (e.g. the result of `renderPdfWithLayout(...).layout`) works.
|
|
6
|
+
*
|
|
7
|
+
* Real FormePDF output (verified by dogfooding, not just the published types):
|
|
8
|
+
* - headings are `H1`..`H6` (level encoded in the tag), not a generic `Heading`
|
|
9
|
+
* - block text nodes have `textContent: undefined`; the actual text lives on
|
|
10
|
+
* nested `TextLine` children
|
|
11
|
+
* - there is no `Table` wrapper node — `TableRow`s are direct children of the
|
|
12
|
+
* page (or a container), so a table is synthesized from contiguous rows
|
|
13
|
+
* This extractor also accepts the simpler documented shape (`Heading`, `Table`
|
|
14
|
+
* with `textContent` set directly), so both are supported.
|
|
15
|
+
*/
|
|
16
|
+
export interface FormeLayoutInfo {
|
|
17
|
+
pages: FormePageInfo[];
|
|
18
|
+
}
|
|
19
|
+
export interface FormePageInfo {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
contentX: number;
|
|
23
|
+
contentY: number;
|
|
24
|
+
contentWidth: number;
|
|
25
|
+
contentHeight: number;
|
|
26
|
+
elements: FormeElementInfo[];
|
|
27
|
+
}
|
|
28
|
+
export interface FormeElementInfo {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
/** e.g. "H1".."H6", "Text", "TextLine", "Table", "TableRow", "TableCell", "View", "Image". */
|
|
34
|
+
nodeType: string;
|
|
35
|
+
style?: {
|
|
36
|
+
fontSize?: number;
|
|
37
|
+
fontWeight?: number;
|
|
38
|
+
fontFamily?: string;
|
|
39
|
+
fontStyle?: string;
|
|
40
|
+
};
|
|
41
|
+
children?: FormeElementInfo[];
|
|
42
|
+
textContent?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Explicit disposition for every value of `@formepdf/core`'s `ElementNodeType`
|
|
46
|
+
* union. Every union member has a deliberate role here — none rely on the
|
|
47
|
+
* fallthrough. Audited against @formepdf/core 0.12.1; re-review whenever that
|
|
48
|
+
* dependency version bumps (the coverage test in formeNodeTypeCoverage.test.ts
|
|
49
|
+
* enforces that this table + FORME_INTENTIONALLY_UNMAPPED cover the whole union).
|
|
50
|
+
*
|
|
51
|
+
* Judgment calls (confirmed): charts + all graphics → opaque `image` regions;
|
|
52
|
+
* form controls → structural `container`; `Lbl` (list marker) → `text`.
|
|
53
|
+
*/
|
|
54
|
+
export declare const FORME_ROLE_BY_NODE_TYPE: Record<string, NodeRole>;
|
|
55
|
+
/**
|
|
56
|
+
* Union members intentionally given no semantic role (out of scope for v0.1),
|
|
57
|
+
* with a one-line reason each. Currently empty — every ElementNodeType maps to a
|
|
58
|
+
* deliberate role above. Kept as the explicit opt-out mechanism and referenced
|
|
59
|
+
* by the coverage test so a future "don't test this" decision is visible, never
|
|
60
|
+
* a silent default.
|
|
61
|
+
*/
|
|
62
|
+
export declare const FORME_INTENTIONALLY_UNMAPPED: ReadonlySet<string>;
|
|
63
|
+
/**
|
|
64
|
+
* Authoritative, heuristic-free extraction from FormePDF layout metadata. Roles,
|
|
65
|
+
* heading levels, and the content box are explicit, so every node is emitted at
|
|
66
|
+
* confidence 1.0. This is the reliable path the whole product is validated on
|
|
67
|
+
* before the fragile pdfjs path is touched.
|
|
68
|
+
*/
|
|
69
|
+
export declare function fromFormeLayout(layout: FormeLayoutInfo, opts?: {
|
|
70
|
+
source?: {
|
|
71
|
+
name?: string;
|
|
72
|
+
};
|
|
73
|
+
createdAt?: string;
|
|
74
|
+
assertShape?: boolean;
|
|
75
|
+
}): StructuralSnapshot;
|
|
76
|
+
/** Thrown when FormePDF's layout shape no longer matches what this extractor
|
|
77
|
+
* understands — a broken contract on the authoritative fast path, which must
|
|
78
|
+
* fail loudly rather than silently produce a wrong diff. */
|
|
79
|
+
export declare class FormeShapeError extends Error {
|
|
80
|
+
constructor(message: string);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=fromFormeLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fromFormeLayout.d.ts","sourceRoot":"","sources":["../../src/extract/fromFormeLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,QAAQ,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAMhF;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CA4C5D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,EAAE,WAAW,CAAC,MAAM,CAE3D,CAAC;AA4BH;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,eAAe,EACvB,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAC/E,kBAAkB,CA8GpB;AAED;;4DAE4D;AAC5D,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B"}
|