@doccloak/core 0.12.1 → 0.13.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/CHANGELOG.md +333 -0
- package/NOTICE +5 -0
- package/README.md +131 -9
- package/dist/dom/errors.d.ts +1 -1
- package/dist/dom/ocr.d.ts +33 -7
- package/dist/dom/ocr.js +98 -4
- package/dist/dom/office.d.ts +20 -6
- package/dist/dom/office.js +44 -11
- package/dist/engine.js +4 -5
- package/dist/env.d.ts +3 -3
- package/dist/pdf/cmap.d.ts +83 -0
- package/dist/pdf/cmap.js +659 -0
- package/dist/pdf/encodings.d.ts +49 -0
- package/dist/pdf/encodings.js +181 -0
- package/dist/pdf/extract.d.ts +106 -0
- package/dist/pdf/extract.js +1064 -0
- package/dist/pdf/fallback-fonts.d.ts +146 -0
- package/dist/pdf/fallback-fonts.js +312 -0
- package/dist/pdf/fit.d.ts +35 -0
- package/dist/pdf/fit.js +60 -0
- package/dist/pdf/fonts.d.ts +82 -0
- package/dist/pdf/fonts.js +465 -0
- package/dist/pdf/glyphlist.d.ts +37 -0
- package/dist/pdf/glyphlist.js +485 -0
- package/dist/pdf/index.d.ts +21 -0
- package/dist/pdf/index.js +7 -0
- package/dist/pdf/lexer.d.ts +61 -0
- package/dist/pdf/lexer.js +554 -0
- package/dist/pdf/objects.d.ts +57 -0
- package/dist/pdf/objects.js +217 -0
- package/dist/pdf/oracle.d.ts +149 -0
- package/dist/pdf/oracle.js +384 -0
- package/dist/pdf/raster.d.ts +45 -0
- package/dist/pdf/raster.js +89 -0
- package/dist/pdf/read.d.ts +36 -0
- package/dist/pdf/read.js +65 -0
- package/dist/pdf/serializer.d.ts +21 -0
- package/dist/pdf/serializer.js +106 -0
- package/dist/pdf/standard-metrics.d.ts +27 -0
- package/dist/pdf/standard-metrics.js +72 -0
- package/dist/pdf/surgery.d.ts +131 -0
- package/dist/pdf/surgery.js +847 -0
- package/dist/pdf/textstate.d.ts +77 -0
- package/dist/pdf/textstate.js +280 -0
- package/dist/pdf/types.d.ts +191 -0
- package/dist/pdf/types.js +11 -0
- package/dist/pdf/verify.d.ts +33 -0
- package/dist/pdf/verify.js +446 -0
- package/dist/pdf/write.d.ts +52 -0
- package/dist/pdf/write.js +644 -0
- package/dist/pipeline.d.ts +15 -0
- package/dist/pipeline.js +123 -8
- package/dist/providers/bardsai.d.ts +1 -1
- package/dist/providers/bardsai.js +5 -1
- package/dist/providers/gliner-base.d.ts +1 -1
- package/dist/providers/gliner-base.js +5 -1
- package/dist/providers/gliner.d.ts +1 -1
- package/dist/providers/gliner.js +5 -1
- package/dist/regex/rules.data.js +13 -8
- package/dist/types.d.ts +17 -2
- package/dist/types.js +17 -0
- package/dist/worker-protocol.d.ts +11 -0
- package/dist/worker-protocol.js +41 -8
- package/fonts/liberation/LICENSE +102 -0
- package/fonts/liberation/LiberationMono-Bold.ttf +0 -0
- package/fonts/liberation/LiberationMono-BoldItalic.ttf +0 -0
- package/fonts/liberation/LiberationMono-Italic.ttf +0 -0
- package/fonts/liberation/LiberationMono-Regular.ttf +0 -0
- package/fonts/liberation/LiberationSans-Bold.ttf +0 -0
- package/fonts/liberation/LiberationSans-BoldItalic.ttf +0 -0
- package/fonts/liberation/LiberationSans-Italic.ttf +0 -0
- package/fonts/liberation/LiberationSans-Regular.ttf +0 -0
- package/fonts/liberation/LiberationSerif-Bold.ttf +0 -0
- package/fonts/liberation/LiberationSerif-BoldItalic.ttf +0 -0
- package/fonts/liberation/LiberationSerif-Italic.ttf +0 -0
- package/fonts/liberation/LiberationSerif-Regular.ttf +0 -0
- package/package.json +11 -2
- package/rules/pl.json +7 -3
- package/rules/universal.json +6 -5
package/dist/dom/ocr.js
CHANGED
|
@@ -174,17 +174,111 @@ export function buildTextFromBlocks(blocks) {
|
|
|
174
174
|
}
|
|
175
175
|
return { text: text.trimEnd(), words };
|
|
176
176
|
}
|
|
177
|
+
/** Share of the smaller box height two boxes must overlap to sit on one line. */
|
|
178
|
+
const SAME_LINE_OVERLAP = 0.5;
|
|
179
|
+
/**
|
|
180
|
+
* A gap at most this share of the line's typical word gap is not a space:
|
|
181
|
+
* OCR split one token there and the lost glyph went into a neighbour's box.
|
|
182
|
+
*/
|
|
183
|
+
const TIGHT_GAP_SHARE = 0.3;
|
|
184
|
+
/** Typical word gap as a share of the line's box height, for lines too short to measure. */
|
|
185
|
+
const FALLBACK_GAP_SHARE = 0.4;
|
|
186
|
+
/** Lines with fewer measured gaps than this use the height-based fallback. */
|
|
187
|
+
const MIN_GAPS_FOR_MEDIAN = 3;
|
|
188
|
+
/**
|
|
189
|
+
* Boxes taller than this multiple of the line's median height are OCR noise
|
|
190
|
+
* (a stray mark or a punctuation box spanning lines); they never join, so a
|
|
191
|
+
* split-token fix does not paint a bar across the neighbouring lines.
|
|
192
|
+
*/
|
|
193
|
+
const MAX_JOIN_HEIGHT_RATIO = 1.6;
|
|
194
|
+
/** Whether `right` continues `left` on the same OCR line (adjacent in the text, overlapping vertically). */
|
|
195
|
+
function continuesLine(left, right) {
|
|
196
|
+
if (right.start !== left.end + 1)
|
|
197
|
+
return false;
|
|
198
|
+
const a = left.bbox;
|
|
199
|
+
const b = right.bbox;
|
|
200
|
+
const minHeight = Math.min(a.y1 - a.y0, b.y1 - b.y0);
|
|
201
|
+
if (minHeight <= 0)
|
|
202
|
+
return false;
|
|
203
|
+
const overlap = Math.min(a.y1, b.y1) - Math.max(a.y0, b.y0);
|
|
204
|
+
return overlap >= minHeight * SAME_LINE_OVERLAP && b.x1 > a.x1;
|
|
205
|
+
}
|
|
206
|
+
function median(values) {
|
|
207
|
+
const sorted = [...values].sort((x, y) => x - y);
|
|
208
|
+
return sorted[Math.floor(sorted.length / 2)];
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* For each pair of consecutive OCR words, whether they are really one token
|
|
212
|
+
* that OCR split by dropping a character. Tesseract reads the dot in
|
|
213
|
+
* "t.zielinski@firma.pl" as a space and returns "t" + "zielinski@firma.pl",
|
|
214
|
+
* with the dot's pixels inside the second box; the e-mail rule then matches
|
|
215
|
+
* only the second word, and without this the "t" would stay visible. Such a
|
|
216
|
+
* split leaves a sliver between the boxes, far narrower than a real space: a
|
|
217
|
+
* pair on the same line is one token when its gap is at most
|
|
218
|
+
* TIGHT_GAP_SHARE of the line's typical word gap (boxes that touch or
|
|
219
|
+
* overlap included). Boxes much taller than the line (MAX_JOIN_HEIGHT_RATIO)
|
|
220
|
+
* are OCR noise and never join, so the fix cannot paint a bar across
|
|
221
|
+
* neighbouring lines.
|
|
222
|
+
*
|
|
223
|
+
* Not covered: a lost glyph that stays in a normal-width gap (seen only on
|
|
224
|
+
* low-resolution images, around 120 dpi). Telling it from a glyph edge that
|
|
225
|
+
* Tesseract cut off its own box needs pixels and misfired on real scans
|
|
226
|
+
* ("w terminie", "nr wpisu"), so gap width alone decides.
|
|
227
|
+
*
|
|
228
|
+
* Returns an array of length words.length - 1: entry i joins words i and i+1.
|
|
229
|
+
* Exported for tests.
|
|
230
|
+
*/
|
|
231
|
+
export function findSplitTokens(words) {
|
|
232
|
+
const joined = new Array(Math.max(0, words.length - 1)).fill(false);
|
|
233
|
+
const height = (w) => w.bbox.y1 - w.bbox.y0;
|
|
234
|
+
let i = 0;
|
|
235
|
+
while (i < words.length - 1) {
|
|
236
|
+
if (!continuesLine(words[i], words[i + 1])) {
|
|
237
|
+
i++;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
let end = i + 1; // the line runs from word i to word end
|
|
241
|
+
while (end < words.length - 1 && continuesLine(words[end], words[end + 1]))
|
|
242
|
+
end++;
|
|
243
|
+
const gaps = [];
|
|
244
|
+
for (let k = i; k < end; k++)
|
|
245
|
+
gaps.push(words[k + 1].bbox.x0 - words[k].bbox.x1);
|
|
246
|
+
const lineHeight = median(words.slice(i, end + 1).map(height));
|
|
247
|
+
const typicalGap = gaps.length >= MIN_GAPS_FOR_MEDIAN ? median(gaps) : lineHeight * FALLBACK_GAP_SHARE;
|
|
248
|
+
const tightGap = Math.max(1, typicalGap * TIGHT_GAP_SHARE);
|
|
249
|
+
const maxHeight = lineHeight * MAX_JOIN_HEIGHT_RATIO;
|
|
250
|
+
for (let k = i; k < end; k++) {
|
|
251
|
+
joined[k] = gaps[k - i] <= tightGap
|
|
252
|
+
&& height(words[k]) <= maxHeight
|
|
253
|
+
&& height(words[k + 1]) <= maxHeight;
|
|
254
|
+
}
|
|
255
|
+
i = end;
|
|
256
|
+
}
|
|
257
|
+
return joined;
|
|
258
|
+
}
|
|
177
259
|
/**
|
|
178
260
|
* Pick the bounding boxes of every OCR word that overlaps one of the given
|
|
179
261
|
* character ranges. A word partially covered by a range is fully redacted
|
|
180
|
-
* (over-redaction is safer than leaking half an identifier).
|
|
262
|
+
* (over-redaction is safer than leaking half an identifier). A selected
|
|
263
|
+
* word also pulls in the same-line neighbours OCR split off by dropping a
|
|
264
|
+
* character (findSplitTokens), so "t" + "zielinski@firma.pl" is redacted
|
|
265
|
+
* as one token.
|
|
181
266
|
*
|
|
182
267
|
* Exported for tests.
|
|
183
268
|
*/
|
|
184
269
|
export function selectRedactionBoxes(words, ranges) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
270
|
+
const selected = words.map((w) => ranges.some((r) => w.start < r.end && w.end > r.start));
|
|
271
|
+
if (selected.some(Boolean)) {
|
|
272
|
+
const joined = findSplitTokens(words);
|
|
273
|
+
// Spread each selection across its split pieces, left to right then back.
|
|
274
|
+
for (let i = 0; i < joined.length; i++)
|
|
275
|
+
if (joined[i] && selected[i])
|
|
276
|
+
selected[i + 1] = true;
|
|
277
|
+
for (let i = joined.length - 1; i >= 0; i--)
|
|
278
|
+
if (joined[i] && selected[i + 1])
|
|
279
|
+
selected[i] = true;
|
|
280
|
+
}
|
|
281
|
+
return words.filter((_, i) => selected[i]).map((w) => w.bbox);
|
|
188
282
|
}
|
|
189
283
|
/** Encode a canvas as a PNG blob, whichever canvas flavor it is. */
|
|
190
284
|
function canvasToPngBlob(canvas) {
|
package/dist/dom/office.d.ts
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @doccloak/core/dom - detection-driven office file redaction (T110).
|
|
3
3
|
*
|
|
4
|
-
* Ties the format modules (docx.ts, xlsx.ts
|
|
4
|
+
* Ties the format modules (docx.ts, xlsx.ts, and the PDF module behind
|
|
5
|
+
* '@doccloak/core/pdf', T215) to the detection pipeline and an
|
|
5
6
|
* AnonymizationSession, so a host can hand over a File plus its live session
|
|
6
7
|
* and get back a redacted copy whose placeholders are consistent with every
|
|
7
8
|
* prompt redacted in the same session: an AI reply that mentions the file's
|
|
8
9
|
* placeholders restores through the same session map.
|
|
9
10
|
*
|
|
10
|
-
* PDF is
|
|
11
|
-
*
|
|
11
|
+
* The PDF module is imported lazily so hosts that never see a PDF do not
|
|
12
|
+
* load pdf-lib, pdf.js and fontkit.
|
|
12
13
|
*/
|
|
13
14
|
import type { DetectedEntity } from '../types.js';
|
|
14
15
|
import type { AnonymizationSession } from '../session.js';
|
|
15
16
|
import type { UnredactablePart } from './errors.js';
|
|
17
|
+
import type { PdfReadOptions, PdfWriteOptions, RemovedPart } from '../pdf/types.js';
|
|
16
18
|
/** File kinds the office redaction flow can regenerate. */
|
|
17
|
-
export type OfficeFileKind = 'docx' | 'xlsx';
|
|
19
|
+
export type OfficeFileKind = 'docx' | 'xlsx' | 'pdf';
|
|
18
20
|
/**
|
|
19
21
|
* Classify a filename into a supported office kind, or null when the format
|
|
20
22
|
* cannot be regenerated by this module (.doc has its own binary writer in
|
|
21
|
-
* src/doc.ts
|
|
23
|
+
* src/doc.ts).
|
|
22
24
|
*/
|
|
23
25
|
export declare function officeFileKind(filename: string): OfficeFileKind | null;
|
|
24
26
|
/** A detect function, typically DocCloakEngine.detect or detectEntities. */
|
|
@@ -54,6 +56,12 @@ export interface OfficeAnalysis {
|
|
|
54
56
|
unredactable: UnredactablePart[];
|
|
55
57
|
/** Reader notes: parts skipped by the extractor (malformed XML). */
|
|
56
58
|
warnings: string[];
|
|
59
|
+
/** PDF only: parts the writer will drop (annotations, forms, bookmarks, ...). */
|
|
60
|
+
removed?: RemovedPart[];
|
|
61
|
+
}
|
|
62
|
+
export interface AnalyzeOfficeFileOptions {
|
|
63
|
+
/** PDF reader options (asset URLs for pdf.js and the fallback fonts, password, caps). */
|
|
64
|
+
pdf?: PdfReadOptions;
|
|
57
65
|
}
|
|
58
66
|
/**
|
|
59
67
|
* Extract a file's text and run detection WITHOUT touching the session. Hosts
|
|
@@ -62,7 +70,7 @@ export interface OfficeAnalysis {
|
|
|
62
70
|
* Extraction is deterministic, so entities returned here can be passed to
|
|
63
71
|
* redactOfficeFile later and land on identical offsets.
|
|
64
72
|
*/
|
|
65
|
-
export declare function analyzeOfficeFile(file: File, detect: DetectFn): Promise<OfficeAnalysis>;
|
|
73
|
+
export declare function analyzeOfficeFile(file: File, detect: DetectFn, options?: AnalyzeOfficeFileOptions): Promise<OfficeAnalysis>;
|
|
66
74
|
export interface RedactOfficeFileOptions {
|
|
67
75
|
/** The live session; file placeholders join the same map as prompt ones. */
|
|
68
76
|
session: AnonymizationSession;
|
|
@@ -92,6 +100,8 @@ export interface RedactOfficeFileOptions {
|
|
|
92
100
|
* analyzeOfficeFile.
|
|
93
101
|
*/
|
|
94
102
|
allowUnredactable?: boolean;
|
|
103
|
+
/** PDF only: reader options (assets, password) and writer options (fit policy, raster DPI). */
|
|
104
|
+
pdf?: PdfReadOptions & PdfWriteOptions;
|
|
95
105
|
}
|
|
96
106
|
export interface OfficeRedactionResult {
|
|
97
107
|
kind: OfficeFileKind;
|
|
@@ -113,6 +123,10 @@ export interface OfficeRedactionResult {
|
|
|
113
123
|
warnings: string[];
|
|
114
124
|
/** Parts copied verbatim because allowUnredactable was passed (T177). */
|
|
115
125
|
unredactable: UnredactablePart[];
|
|
126
|
+
/** PDF only: parts dropped from the output. */
|
|
127
|
+
removed?: RemovedPart[];
|
|
128
|
+
/** PDF only: 0-based pages that were rasterized because they could not be edited safely. */
|
|
129
|
+
rasterizedPages?: number[];
|
|
116
130
|
}
|
|
117
131
|
/** "report.docx" -> "report.redacted.docx"; extensionless names get a suffix. */
|
|
118
132
|
export declare function redactedFileName(filename: string): string;
|
package/dist/dom/office.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @doccloak/core/dom - detection-driven office file redaction (T110).
|
|
3
3
|
*
|
|
4
|
-
* Ties the format modules (docx.ts, xlsx.ts
|
|
4
|
+
* Ties the format modules (docx.ts, xlsx.ts, and the PDF module behind
|
|
5
|
+
* '@doccloak/core/pdf', T215) to the detection pipeline and an
|
|
5
6
|
* AnonymizationSession, so a host can hand over a File plus its live session
|
|
6
7
|
* and get back a redacted copy whose placeholders are consistent with every
|
|
7
8
|
* prompt redacted in the same session: an AI reply that mentions the file's
|
|
8
9
|
* placeholders restores through the same session map.
|
|
9
10
|
*
|
|
10
|
-
* PDF is
|
|
11
|
-
*
|
|
11
|
+
* The PDF module is imported lazily so hosts that never see a PDF do not
|
|
12
|
+
* load pdf-lib, pdf.js and fontkit.
|
|
12
13
|
*/
|
|
13
14
|
import { getFileExtension, readDocx, writeAnonymizedDocxWithReport } from "./docx.js";
|
|
14
15
|
import { readXlsx, writeAnonymizedXlsxWithReport } from "./xlsx.js";
|
|
@@ -16,7 +17,7 @@ import { layerZeroValueReplacements } from "./opc.js";
|
|
|
16
17
|
/**
|
|
17
18
|
* Classify a filename into a supported office kind, or null when the format
|
|
18
19
|
* cannot be regenerated by this module (.doc has its own binary writer in
|
|
19
|
-
* src/doc.ts
|
|
20
|
+
* src/doc.ts).
|
|
20
21
|
*/
|
|
21
22
|
export function officeFileKind(filename) {
|
|
22
23
|
const ext = getFileExtension(filename);
|
|
@@ -24,8 +25,26 @@ export function officeFileKind(filename) {
|
|
|
24
25
|
return 'docx';
|
|
25
26
|
if (ext === 'xlsx')
|
|
26
27
|
return 'xlsx';
|
|
28
|
+
if (ext === 'pdf')
|
|
29
|
+
return 'pdf';
|
|
27
30
|
return null;
|
|
28
31
|
}
|
|
32
|
+
/** Lazy loader of the PDF module (keeps pdf-lib/pdf.js/fontkit out of hosts that never see a PDF). */
|
|
33
|
+
async function pdfModule() {
|
|
34
|
+
return import("../pdf/index.js");
|
|
35
|
+
}
|
|
36
|
+
async function readByKind(kind, file, pdf) {
|
|
37
|
+
if (kind === 'docx')
|
|
38
|
+
return readDocx(file);
|
|
39
|
+
if (kind === 'xlsx')
|
|
40
|
+
return readXlsx(file);
|
|
41
|
+
// A host that has not wired the PDF assets (pdf.js worker, fallback fonts) cannot redact PDFs; refuse
|
|
42
|
+
// the way an unsupported format is refused instead of failing deep inside pdf.js.
|
|
43
|
+
if (!pdf)
|
|
44
|
+
throw new Error(`Unsupported office file: ${file.name} (this host has no PDF support configured)`);
|
|
45
|
+
const { readPdf } = await pdfModule();
|
|
46
|
+
return readPdf(file, pdf);
|
|
47
|
+
}
|
|
29
48
|
/**
|
|
30
49
|
* Raised by redactOfficeFile (T179, finding M4) when supplied entities do
|
|
31
50
|
* not match the file's freshly extracted text: the analysis is stale or came
|
|
@@ -58,11 +77,11 @@ export function isStaleAnalysisError(err) {
|
|
|
58
77
|
* Extraction is deterministic, so entities returned here can be passed to
|
|
59
78
|
* redactOfficeFile later and land on identical offsets.
|
|
60
79
|
*/
|
|
61
|
-
export async function analyzeOfficeFile(file, detect) {
|
|
80
|
+
export async function analyzeOfficeFile(file, detect, options = {}) {
|
|
62
81
|
const kind = officeFileKind(file.name);
|
|
63
82
|
if (!kind)
|
|
64
83
|
throw new Error(`Unsupported office file: ${file.name}`);
|
|
65
|
-
const extraction =
|
|
84
|
+
const extraction = await readByKind(kind, file, options.pdf);
|
|
66
85
|
const entities = await detect(extraction.plainText);
|
|
67
86
|
return {
|
|
68
87
|
kind,
|
|
@@ -70,6 +89,7 @@ export async function analyzeOfficeFile(file, detect) {
|
|
|
70
89
|
entities,
|
|
71
90
|
unredactable: extraction.unredactable,
|
|
72
91
|
warnings: extraction.warnings,
|
|
92
|
+
removed: kind === 'pdf' ? extraction.removed : undefined,
|
|
73
93
|
};
|
|
74
94
|
}
|
|
75
95
|
/** "report.docx" -> "report.redacted.docx"; extensionless names get a suffix. */
|
|
@@ -102,7 +122,7 @@ export async function redactOfficeFile(file, options) {
|
|
|
102
122
|
const kind = officeFileKind(file.name);
|
|
103
123
|
if (!kind)
|
|
104
124
|
throw new Error(`Unsupported office file: ${file.name}`);
|
|
105
|
-
const extraction =
|
|
125
|
+
const extraction = await readByKind(kind, file, options.pdf);
|
|
106
126
|
const plainText = extraction.plainText;
|
|
107
127
|
let entities = options.entities;
|
|
108
128
|
if (!entities) {
|
|
@@ -148,14 +168,25 @@ export async function redactOfficeFile(file, options) {
|
|
|
148
168
|
for (const repl of [...replacements].sort((a, b) => b.start - a.start)) {
|
|
149
169
|
redactedText = redactedText.slice(0, repl.start) + repl.replacement + redactedText.slice(repl.end);
|
|
150
170
|
}
|
|
151
|
-
|
|
152
|
-
|
|
171
|
+
let written;
|
|
172
|
+
if (kind === 'docx') {
|
|
173
|
+
written = await writeAnonymizedDocxWithReport(extraction, replacements, valueReplacements, {
|
|
153
174
|
acceptTrackedChanges: options.acceptTrackedChanges ?? true,
|
|
154
175
|
allowUnredactable: options.allowUnredactable ?? false,
|
|
155
|
-
})
|
|
156
|
-
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else if (kind === 'xlsx') {
|
|
179
|
+
written = await writeAnonymizedXlsxWithReport(extraction, replacements, valueReplacements, {
|
|
180
|
+
allowUnredactable: options.allowUnredactable ?? false,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const { writeAnonymizedPdfWithReport } = await pdfModule();
|
|
185
|
+
written = await writeAnonymizedPdfWithReport(extraction, replacements, valueReplacements, {
|
|
186
|
+
...(options.pdf ?? {}),
|
|
157
187
|
allowUnredactable: options.allowUnredactable ?? false,
|
|
158
188
|
});
|
|
189
|
+
}
|
|
159
190
|
return {
|
|
160
191
|
kind,
|
|
161
192
|
blob: written.blob,
|
|
@@ -165,5 +196,7 @@ export async function redactOfficeFile(file, options) {
|
|
|
165
196
|
redactedText,
|
|
166
197
|
warnings: written.warnings,
|
|
167
198
|
unredactable: extraction.unredactable,
|
|
199
|
+
removed: written.removed,
|
|
200
|
+
rasterizedPages: written.rasterizedPages,
|
|
168
201
|
};
|
|
169
202
|
}
|
package/dist/engine.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* PortLike transport (Web Worker postMessage, MessageChannel, extension
|
|
14
14
|
* ports).
|
|
15
15
|
*/
|
|
16
|
+
import { throwIfAborted } from "./types.js";
|
|
16
17
|
import { detectWithRegex, detectEntities } from "./pipeline.js";
|
|
17
18
|
import { GlinerProvider } from "./providers/gliner.js";
|
|
18
19
|
import { GlinerBaseProvider } from "./providers/gliner-base.js";
|
|
@@ -264,8 +265,7 @@ export function createEngine(env, initial, options) {
|
|
|
264
265
|
}
|
|
265
266
|
async function detect(text, signal, onProgress) {
|
|
266
267
|
await ready;
|
|
267
|
-
|
|
268
|
-
throw new Error('Detection aborted');
|
|
268
|
+
throwIfAborted(signal);
|
|
269
269
|
if (!text.trim())
|
|
270
270
|
return [];
|
|
271
271
|
const p = ensureProvider();
|
|
@@ -282,9 +282,8 @@ export function createEngine(env, initial, options) {
|
|
|
282
282
|
onProgress?.(progress);
|
|
283
283
|
for (const cb of detectionListeners)
|
|
284
284
|
cb(progress);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
throw new Error('Detection aborted');
|
|
285
|
+
}, signal);
|
|
286
|
+
throwIfAborted(signal);
|
|
288
287
|
const regexResults = settings.regexEnabled ? detectWithRegex(text, settings.regexRegion) : [];
|
|
289
288
|
return detectEntities(text, mlResults, regexResults);
|
|
290
289
|
}
|
package/dist/env.d.ts
CHANGED
|
@@ -41,12 +41,12 @@ export interface CoreEnv {
|
|
|
41
41
|
* `new PreTrainedTokenizer(tokenizerJson, tokenizerConfig)` (or the class
|
|
42
42
|
* named by `tokenizerConfig.tokenizer_class`). May return a promise.
|
|
43
43
|
*
|
|
44
|
-
* Optional
|
|
45
|
-
*
|
|
44
|
+
* Optional so hosts still on loadTokenizer keep compiling; required once
|
|
45
|
+
* loadTokenizer is removed (planned for 0.14.0).
|
|
46
46
|
*/
|
|
47
47
|
buildTokenizer?(tokenizerJson: unknown, tokenizerConfig: unknown): unknown | Promise<unknown>;
|
|
48
48
|
/**
|
|
49
|
-
* @deprecated since 0.12.0,
|
|
49
|
+
* @deprecated since 0.12.0, removal planned for 0.14.0. Used only when
|
|
50
50
|
* buildTokenizer is absent. Loading by Hugging Face model id lets the
|
|
51
51
|
* host library probe mutable `resolve/main` and keep an unverified
|
|
52
52
|
* second cache (security report S1/S3); implement buildTokenizer.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @doccloak/core/pdf - CMap parser, decoder and ToUnicode serializer (T208).
|
|
3
|
+
*
|
|
4
|
+
* Handles the CMap syntax of ISO 32000-1 section 9.7.5 (embedded CMap
|
|
5
|
+
* streams) and 9.10.3 (ToUnicode CMaps): codespace ranges, cidrange /
|
|
6
|
+
* cidchar, bfchar / bfrange (both the incrementing and the array form,
|
|
7
|
+
* multi-code-unit UTF-16BE destinations with surrogates), /WMode, /CMapName.
|
|
8
|
+
* `usecmap` and predefined CMaps other than Identity-H/V are refused: the
|
|
9
|
+
* caller reports the font as unredactable rather than guessing.
|
|
10
|
+
*
|
|
11
|
+
* The parser is strict: malformed input throws CMapParseError, it never
|
|
12
|
+
* returns a partial map silently. A missing codespace section is the one
|
|
13
|
+
* tolerated defect (common in real ToUnicode CMaps): the ranges are then
|
|
14
|
+
* derived from the byte lengths of the codes seen.
|
|
15
|
+
*/
|
|
16
|
+
export interface CodespaceRange {
|
|
17
|
+
/** Code length in bytes (1..4). */
|
|
18
|
+
numBytes: number;
|
|
19
|
+
/** Inclusive bounds as big-endian integers of `numBytes` bytes. */
|
|
20
|
+
low: number;
|
|
21
|
+
high: number;
|
|
22
|
+
}
|
|
23
|
+
export interface CMap {
|
|
24
|
+
codespace: CodespaceRange[];
|
|
25
|
+
/** code -> CID for CID CMaps. null means identity (Identity-H/V or a CMap without cid entries). */
|
|
26
|
+
cidMap: Map<number, number> | null;
|
|
27
|
+
/** code -> unicode string for ToUnicode CMaps. null when the CMap has no bf entries. */
|
|
28
|
+
unicodeMap: Map<number, string> | null;
|
|
29
|
+
/** True for vertical writing mode (/WMode 1, Identity-V). */
|
|
30
|
+
vertical: boolean;
|
|
31
|
+
/** /CMapName when present. */
|
|
32
|
+
name?: string;
|
|
33
|
+
/** Code lengths (in bytes, 1..4) seen in codespace ranges and mapping entries. */
|
|
34
|
+
codeLengths: Set<number>;
|
|
35
|
+
}
|
|
36
|
+
export declare class CMapParseError extends Error {
|
|
37
|
+
constructor(message: string);
|
|
38
|
+
}
|
|
39
|
+
/** Largest number of entries one range (cidrange / bfrange) may expand to. Guards against memory bombs. */
|
|
40
|
+
export declare const MAX_RANGE_ENTRIES = 65536;
|
|
41
|
+
/** Parse an embedded CMap / ToUnicode stream. Throws CMapParseError on malformed input or `usecmap`. */
|
|
42
|
+
export declare function parseCMap(bytes: Uint8Array): CMap;
|
|
43
|
+
/** The 2-byte Identity CMap: every code is its own CID. */
|
|
44
|
+
export declare function identityCMap(vertical: boolean): CMap;
|
|
45
|
+
/** Only Identity-H / Identity-V are built in; any other predefined name is refused by the caller. */
|
|
46
|
+
export declare function predefinedCMap(name: string): CMap | undefined;
|
|
47
|
+
export interface DecodedCode {
|
|
48
|
+
/** Big-endian value of the code bytes. */
|
|
49
|
+
code: number;
|
|
50
|
+
/** Number of bytes consumed (1..4). */
|
|
51
|
+
length: number;
|
|
52
|
+
/** Byte offset of the code inside the string operand. */
|
|
53
|
+
offset: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Split a string operand into character codes (ISO 32000-1 9.7.6.3). For each
|
|
57
|
+
* position the shortest byte length whose codespace range contains the value
|
|
58
|
+
* wins; with no full match, the shortest range that matches on the first byte
|
|
59
|
+
* decides the length; otherwise 1 byte. Unmapped codes are still returned, the
|
|
60
|
+
* caller treats them as notdef. A code cut short by the end of the string takes
|
|
61
|
+
* the remaining bytes.
|
|
62
|
+
*/
|
|
63
|
+
export declare function splitCodes(cmap: CMap, bytes: Uint8Array): DecodedCode[];
|
|
64
|
+
/** CID of a code: identity when the CMap has no cid entries, 0 (notdef) when the code is unmapped. */
|
|
65
|
+
export declare function cidOf(cmap: CMap, code: number): number;
|
|
66
|
+
/** Unicode text of a code from a ToUnicode CMap; undefined when unmapped (or when the CMap has no bf entries). */
|
|
67
|
+
export declare function unicodeOf(cmap: CMap, code: number): string | undefined;
|
|
68
|
+
export interface ToUnicodeEntry {
|
|
69
|
+
code: number;
|
|
70
|
+
/** Code length in bytes (1..4). */
|
|
71
|
+
length: number;
|
|
72
|
+
unicode: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Write a ToUnicode CMap holding exactly the given codes. One full-width
|
|
76
|
+
* codespace range per code length present, bfchar entries in groups of at most
|
|
77
|
+
* 100, UTF-16BE destinations. Output is deterministic (sorted by length, then
|
|
78
|
+
* code; later duplicates win) and reads back with parseCMap to the same map.
|
|
79
|
+
*/
|
|
80
|
+
export declare function serializeToUnicode(entries: ToUnicodeEntry[], options?: {
|
|
81
|
+
name?: string;
|
|
82
|
+
codespace?: readonly CodespaceRange[];
|
|
83
|
+
}): Uint8Array;
|