@doccloak/core 0.12.1 → 0.14.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +386 -0
  2. package/NOTICE +5 -0
  3. package/README.md +148 -9
  4. package/dist/dom/errors.d.ts +1 -1
  5. package/dist/dom/ocr.d.ts +33 -7
  6. package/dist/dom/ocr.js +98 -4
  7. package/dist/dom/office.d.ts +22 -6
  8. package/dist/dom/office.js +45 -11
  9. package/dist/engine.js +4 -5
  10. package/dist/env.d.ts +3 -3
  11. package/dist/pdf/cmap.d.ts +83 -0
  12. package/dist/pdf/cmap.js +659 -0
  13. package/dist/pdf/encodings.d.ts +49 -0
  14. package/dist/pdf/encodings.js +181 -0
  15. package/dist/pdf/extract.d.ts +108 -0
  16. package/dist/pdf/extract.js +1164 -0
  17. package/dist/pdf/fallback-fonts.d.ts +146 -0
  18. package/dist/pdf/fallback-fonts.js +312 -0
  19. package/dist/pdf/fit.d.ts +35 -0
  20. package/dist/pdf/fit.js +60 -0
  21. package/dist/pdf/fonts.d.ts +82 -0
  22. package/dist/pdf/fonts.js +465 -0
  23. package/dist/pdf/glyphlist.d.ts +37 -0
  24. package/dist/pdf/glyphlist.js +485 -0
  25. package/dist/pdf/index.d.ts +21 -0
  26. package/dist/pdf/index.js +7 -0
  27. package/dist/pdf/lexer.d.ts +61 -0
  28. package/dist/pdf/lexer.js +554 -0
  29. package/dist/pdf/objects.d.ts +57 -0
  30. package/dist/pdf/objects.js +217 -0
  31. package/dist/pdf/oracle.d.ts +149 -0
  32. package/dist/pdf/oracle.js +384 -0
  33. package/dist/pdf/raster.d.ts +119 -0
  34. package/dist/pdf/raster.js +405 -0
  35. package/dist/pdf/read.d.ts +36 -0
  36. package/dist/pdf/read.js +65 -0
  37. package/dist/pdf/serializer.d.ts +21 -0
  38. package/dist/pdf/serializer.js +106 -0
  39. package/dist/pdf/standard-metrics.d.ts +27 -0
  40. package/dist/pdf/standard-metrics.js +72 -0
  41. package/dist/pdf/surgery.d.ts +131 -0
  42. package/dist/pdf/surgery.js +847 -0
  43. package/dist/pdf/textstate.d.ts +88 -0
  44. package/dist/pdf/textstate.js +293 -0
  45. package/dist/pdf/types.d.ts +206 -0
  46. package/dist/pdf/types.js +11 -0
  47. package/dist/pdf/verify.d.ts +33 -0
  48. package/dist/pdf/verify.js +446 -0
  49. package/dist/pdf/write.d.ts +62 -0
  50. package/dist/pdf/write.js +750 -0
  51. package/dist/pipeline.d.ts +15 -0
  52. package/dist/pipeline.js +123 -8
  53. package/dist/providers/bardsai.d.ts +1 -1
  54. package/dist/providers/bardsai.js +5 -1
  55. package/dist/providers/gliner-base.d.ts +1 -1
  56. package/dist/providers/gliner-base.js +5 -1
  57. package/dist/providers/gliner.d.ts +1 -1
  58. package/dist/providers/gliner.js +5 -1
  59. package/dist/regex/rules.data.js +13 -8
  60. package/dist/types.d.ts +17 -2
  61. package/dist/types.js +17 -0
  62. package/dist/worker-protocol.d.ts +11 -0
  63. package/dist/worker-protocol.js +41 -8
  64. package/fonts/liberation/LICENSE +102 -0
  65. package/fonts/liberation/LiberationMono-Bold.ttf +0 -0
  66. package/fonts/liberation/LiberationMono-BoldItalic.ttf +0 -0
  67. package/fonts/liberation/LiberationMono-Italic.ttf +0 -0
  68. package/fonts/liberation/LiberationMono-Regular.ttf +0 -0
  69. package/fonts/liberation/LiberationSans-Bold.ttf +0 -0
  70. package/fonts/liberation/LiberationSans-BoldItalic.ttf +0 -0
  71. package/fonts/liberation/LiberationSans-Italic.ttf +0 -0
  72. package/fonts/liberation/LiberationSans-Regular.ttf +0 -0
  73. package/fonts/liberation/LiberationSerif-Bold.ttf +0 -0
  74. package/fonts/liberation/LiberationSerif-BoldItalic.ttf +0 -0
  75. package/fonts/liberation/LiberationSerif-Italic.ttf +0 -0
  76. package/fonts/liberation/LiberationSerif-Regular.ttf +0 -0
  77. package/package.json +11 -2
  78. package/rules/pl.json +7 -3
  79. package/rules/universal.json +6 -5
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @doccloak/core/pdf - graphics/text state machine (T210).
3
+ *
4
+ * Tracks the subset of the graphics state that positions glyphs (ISO 32000-1
5
+ * 8.4, 9.3, 9.4): CTM, text state parameters, text matrix and line matrix,
6
+ * fill colour (to spot white-on-white text), constant alpha from ExtGState
7
+ * (to spot fully transparent text, a common OCR layer), and the q/Q stack. The same
8
+ * class is used by the extractor (to compute glyph origins) and by the
9
+ * surgery (to know the state at any operator).
10
+ */
11
+ import type { ContentOp, Operand } from './lexer.js';
12
+ import type { Matrix } from './types.js';
13
+ import type { LoadedFont } from './fonts.js';
14
+ export declare const IDENTITY: Matrix;
15
+ /** a × b in PDF row-vector convention: apply a first, then b. */
16
+ export declare function mul(a: Matrix, b: Matrix): Matrix;
17
+ export declare function translate(tx: number, ty: number): Matrix;
18
+ export declare function apply(m: Matrix, x: number, y: number): [number, number];
19
+ export declare function invert(m: Matrix): Matrix | null;
20
+ export declare function matrixFromOperands(ops: Operand[]): Matrix | null;
21
+ export interface TextState {
22
+ font: LoadedFont | null;
23
+ fontName: string;
24
+ fontSize: number;
25
+ charSpacing: number;
26
+ wordSpacing: number;
27
+ /** Tz / 100 */
28
+ hscale: number;
29
+ leading: number;
30
+ rise: number;
31
+ renderMode: number;
32
+ }
33
+ export interface GraphicsState {
34
+ ctm: Matrix;
35
+ text: TextState;
36
+ /** Approximate fill luminance 0..1, or null when unknown (patterns, ICC spaces). */
37
+ fillLuminance: number | null;
38
+ /** Stroke luminance, for render modes that only stroke. */
39
+ strokeLuminance: number | null;
40
+ /** Constant alpha (ExtGState /ca) for filling, 0..1. */
41
+ fillAlpha: number;
42
+ /** Constant alpha (ExtGState /CA) for stroking, 0..1. */
43
+ strokeAlpha: number;
44
+ }
45
+ /** The alpha entries of a named ExtGState (absent when the dictionary does not set them). */
46
+ export interface ExtGStateAlpha {
47
+ fillAlpha?: number;
48
+ strokeAlpha?: number;
49
+ }
50
+ export declare function initialState(ctm?: Matrix): GraphicsState;
51
+ /**
52
+ * Walks operators and keeps the state current. Text-showing operators are
53
+ * NOT advanced here (the caller computes glyph advances, since they depend
54
+ * on the font); call `advanceText(tx)` after showing.
55
+ */
56
+ export declare class StateMachine {
57
+ state: GraphicsState;
58
+ private stack;
59
+ /** Text matrix and line matrix; only meaningful inside BT..ET. */
60
+ tm: Matrix;
61
+ tlm: Matrix;
62
+ inText: boolean;
63
+ /** Fill colour space family for sc/scn interpretation. */
64
+ private fillSpace;
65
+ private strokeSpace;
66
+ /** Nesting depth of q without Q (for diagnostics). */
67
+ get depth(): number;
68
+ constructor(initial: GraphicsState);
69
+ /**
70
+ * Apply one operator's effect on the state. `resolveFont` maps a resource
71
+ * name to a LoadedFont (undefined when unknown); `resolveGState` maps an
72
+ * ExtGState resource name to its alpha entries (without it `gs` is ignored).
73
+ * Text-showing operators are not handled here (caller handles them).
74
+ */
75
+ update(op: ContentOp, resolveFont: (name: string) => LoadedFont | undefined, resolveGState?: (name: string) => ExtGStateAlpha | undefined): void;
76
+ nextLine(): void;
77
+ /** Move the text matrix by an unscaled text-space advance (already includes Tfs, Tc, Tw, Th). */
78
+ advanceText(tx: number): void;
79
+ /** Text rendering matrix for the current glyph: [Tfs*Th 0 0 Tfs 0 Ts] × Tm × CTM. */
80
+ trm(): Matrix;
81
+ }
82
+ /**
83
+ * Advance of one glyph in unscaled text space (9.4.4): ((w0 - adj/1000) * Tfs + Tc + Tw?) * Th.
84
+ * `w0` in 1/1000 units; `adj` is the TJ number preceding the glyph (already applied by caller).
85
+ */
86
+ export declare function glyphAdvance(t: TextState, w0: number, isSpaceCode: boolean): number;
87
+ /** Displacement of a TJ adjustment number in unscaled text space: -adj/1000 * Tfs * Th. */
88
+ export declare function adjustmentAdvance(t: TextState, adj: number): number;
@@ -0,0 +1,293 @@
1
+ /**
2
+ * @doccloak/core/pdf - graphics/text state machine (T210).
3
+ *
4
+ * Tracks the subset of the graphics state that positions glyphs (ISO 32000-1
5
+ * 8.4, 9.3, 9.4): CTM, text state parameters, text matrix and line matrix,
6
+ * fill colour (to spot white-on-white text), constant alpha from ExtGState
7
+ * (to spot fully transparent text, a common OCR layer), and the q/Q stack. The same
8
+ * class is used by the extractor (to compute glyph origins) and by the
9
+ * surgery (to know the state at any operator).
10
+ */
11
+ export const IDENTITY = [1, 0, 0, 1, 0, 0];
12
+ /** a × b in PDF row-vector convention: apply a first, then b. */
13
+ export function mul(a, b) {
14
+ return [
15
+ a[0] * b[0] + a[1] * b[2],
16
+ a[0] * b[1] + a[1] * b[3],
17
+ a[2] * b[0] + a[3] * b[2],
18
+ a[2] * b[1] + a[3] * b[3],
19
+ a[4] * b[0] + a[5] * b[2] + b[4],
20
+ a[4] * b[1] + a[5] * b[3] + b[5],
21
+ ];
22
+ }
23
+ export function translate(tx, ty) {
24
+ return [1, 0, 0, 1, tx, ty];
25
+ }
26
+ export function apply(m, x, y) {
27
+ return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
28
+ }
29
+ export function invert(m) {
30
+ const det = m[0] * m[3] - m[1] * m[2];
31
+ if (!Number.isFinite(det) || Math.abs(det) < 1e-12)
32
+ return null;
33
+ const a = m[3] / det;
34
+ const b = -m[1] / det;
35
+ const c = -m[2] / det;
36
+ const d = m[0] / det;
37
+ const e = -(m[4] * a + m[5] * c);
38
+ const f = -(m[4] * b + m[5] * d);
39
+ return [a, b, c, d, e, f];
40
+ }
41
+ export function matrixFromOperands(ops) {
42
+ if (ops.length < 6)
43
+ return null;
44
+ const n = [];
45
+ for (let i = ops.length - 6; i < ops.length; i++) {
46
+ const o = ops[i];
47
+ if (o.kind !== 'number' || !Number.isFinite(o.value))
48
+ return null;
49
+ n.push(o.value);
50
+ }
51
+ return [n[0], n[1], n[2], n[3], n[4], n[5]];
52
+ }
53
+ function cloneState(s) {
54
+ return { ctm: s.ctm, text: { ...s.text }, fillLuminance: s.fillLuminance, strokeLuminance: s.strokeLuminance, fillAlpha: s.fillAlpha, strokeAlpha: s.strokeAlpha };
55
+ }
56
+ export function initialState(ctm = IDENTITY) {
57
+ return {
58
+ ctm,
59
+ text: { font: null, fontName: '', fontSize: 0, charSpacing: 0, wordSpacing: 0, hscale: 1, leading: 0, rise: 0, renderMode: 0 },
60
+ fillLuminance: 0,
61
+ strokeLuminance: 0,
62
+ fillAlpha: 1,
63
+ strokeAlpha: 1,
64
+ };
65
+ }
66
+ function num(o, fallback = 0) {
67
+ return o && o.kind === 'number' && Number.isFinite(o.value) ? o.value : fallback;
68
+ }
69
+ function luminanceOf(values, space) {
70
+ if (space === 'gray' && values.length >= 1)
71
+ return clamp01(values[0]);
72
+ if (space === 'rgb' && values.length >= 3)
73
+ return clamp01(0.2126 * values[0] + 0.7152 * values[1] + 0.0722 * values[2]);
74
+ if (space === 'cmyk' && values.length >= 4) {
75
+ const k = values[3];
76
+ const r = (1 - values[0]) * (1 - k);
77
+ const g = (1 - values[1]) * (1 - k);
78
+ const b = (1 - values[2]) * (1 - k);
79
+ return clamp01(0.2126 * r + 0.7152 * g + 0.0722 * b);
80
+ }
81
+ return null;
82
+ }
83
+ function clamp01(v) {
84
+ return Math.max(0, Math.min(1, v));
85
+ }
86
+ /**
87
+ * Walks operators and keeps the state current. Text-showing operators are
88
+ * NOT advanced here (the caller computes glyph advances, since they depend
89
+ * on the font); call `advanceText(tx)` after showing.
90
+ */
91
+ export class StateMachine {
92
+ /** Nesting depth of q without Q (for diagnostics). */
93
+ get depth() {
94
+ return this.stack.length;
95
+ }
96
+ constructor(initial) {
97
+ this.stack = [];
98
+ /** Text matrix and line matrix; only meaningful inside BT..ET. */
99
+ this.tm = IDENTITY;
100
+ this.tlm = IDENTITY;
101
+ this.inText = false;
102
+ /** Fill colour space family for sc/scn interpretation. */
103
+ this.fillSpace = 'gray';
104
+ this.strokeSpace = 'gray';
105
+ this.state = initial;
106
+ }
107
+ /**
108
+ * Apply one operator's effect on the state. `resolveFont` maps a resource
109
+ * name to a LoadedFont (undefined when unknown); `resolveGState` maps an
110
+ * ExtGState resource name to its alpha entries (without it `gs` is ignored).
111
+ * Text-showing operators are not handled here (caller handles them).
112
+ */
113
+ update(op, resolveFont, resolveGState) {
114
+ const s = this.state;
115
+ const o = op.operands;
116
+ switch (op.op) {
117
+ case 'q':
118
+ this.stack.push(cloneState(s));
119
+ if (this.stack.length > 256)
120
+ this.stack.shift();
121
+ break;
122
+ case 'Q': {
123
+ const prev = this.stack.pop();
124
+ if (prev)
125
+ this.state = prev;
126
+ break;
127
+ }
128
+ case 'cm': {
129
+ const m = matrixFromOperands(o);
130
+ if (m)
131
+ s.ctm = mul(m, s.ctm);
132
+ break;
133
+ }
134
+ case 'BT':
135
+ this.tm = IDENTITY;
136
+ this.tlm = IDENTITY;
137
+ this.inText = true;
138
+ break;
139
+ case 'ET':
140
+ this.inText = false;
141
+ break;
142
+ case 'Tc':
143
+ s.text.charSpacing = num(o[0]);
144
+ break;
145
+ case 'Tw':
146
+ s.text.wordSpacing = num(o[0]);
147
+ break;
148
+ case 'Tz':
149
+ s.text.hscale = num(o[0], 100) / 100;
150
+ break;
151
+ case 'TL':
152
+ s.text.leading = num(o[0]);
153
+ break;
154
+ case 'Ts':
155
+ s.text.rise = num(o[0]);
156
+ break;
157
+ case 'Tr':
158
+ s.text.renderMode = Math.trunc(num(o[0]));
159
+ break;
160
+ case 'Tf': {
161
+ const nameOp = o[0];
162
+ s.text.fontName = nameOp && nameOp.kind === 'name' ? nameOp.value : '';
163
+ s.text.fontSize = num(o[1]);
164
+ s.text.font = s.text.fontName ? resolveFont(s.text.fontName) ?? null : null;
165
+ break;
166
+ }
167
+ case 'Td': {
168
+ this.tlm = mul(translate(num(o[0]), num(o[1])), this.tlm);
169
+ this.tm = this.tlm;
170
+ break;
171
+ }
172
+ case 'TD': {
173
+ s.text.leading = -num(o[1]);
174
+ this.tlm = mul(translate(num(o[0]), num(o[1])), this.tlm);
175
+ this.tm = this.tlm;
176
+ break;
177
+ }
178
+ case 'Tm': {
179
+ const m = matrixFromOperands(o);
180
+ if (m) {
181
+ this.tlm = m;
182
+ this.tm = m;
183
+ }
184
+ break;
185
+ }
186
+ case 'T*':
187
+ this.nextLine();
188
+ break;
189
+ case "'":
190
+ this.nextLine();
191
+ break;
192
+ case '"':
193
+ s.text.wordSpacing = num(o[0]);
194
+ s.text.charSpacing = num(o[1]);
195
+ this.nextLine();
196
+ break;
197
+ case 'g':
198
+ s.fillLuminance = luminanceOf([num(o[0])], 'gray');
199
+ this.fillSpace = 'gray';
200
+ break;
201
+ case 'G':
202
+ s.strokeLuminance = luminanceOf([num(o[0])], 'gray');
203
+ this.strokeSpace = 'gray';
204
+ break;
205
+ case 'rg':
206
+ s.fillLuminance = luminanceOf(o.map((x) => num(x)), 'rgb');
207
+ this.fillSpace = 'rgb';
208
+ break;
209
+ case 'RG':
210
+ s.strokeLuminance = luminanceOf(o.map((x) => num(x)), 'rgb');
211
+ this.strokeSpace = 'rgb';
212
+ break;
213
+ case 'k':
214
+ s.fillLuminance = luminanceOf(o.map((x) => num(x)), 'cmyk');
215
+ this.fillSpace = 'cmyk';
216
+ break;
217
+ case 'K':
218
+ s.strokeLuminance = luminanceOf(o.map((x) => num(x)), 'cmyk');
219
+ this.strokeSpace = 'cmyk';
220
+ break;
221
+ case 'cs':
222
+ this.fillSpace = spaceFamily(o[0]);
223
+ s.fillLuminance = this.fillSpace ? 0 : null;
224
+ break;
225
+ case 'CS':
226
+ this.strokeSpace = spaceFamily(o[0]);
227
+ s.strokeLuminance = this.strokeSpace ? 0 : null;
228
+ break;
229
+ case 'sc':
230
+ case 'scn': {
231
+ const nums = o.filter((x) => x.kind === 'number').map((x) => num(x));
232
+ s.fillLuminance = this.fillSpace && nums.length === o.length ? luminanceOf(nums, this.fillSpace) : null;
233
+ break;
234
+ }
235
+ case 'SC':
236
+ case 'SCN': {
237
+ const nums = o.filter((x) => x.kind === 'number').map((x) => num(x));
238
+ s.strokeLuminance = this.strokeSpace && nums.length === o.length ? luminanceOf(nums, this.strokeSpace) : null;
239
+ break;
240
+ }
241
+ case 'gs': {
242
+ const nameOp = o[0];
243
+ const alpha = nameOp && nameOp.kind === 'name' ? resolveGState?.(nameOp.value) : undefined;
244
+ if (alpha?.fillAlpha !== undefined)
245
+ s.fillAlpha = clamp01(alpha.fillAlpha);
246
+ if (alpha?.strokeAlpha !== undefined)
247
+ s.strokeAlpha = clamp01(alpha.strokeAlpha);
248
+ break;
249
+ }
250
+ default:
251
+ break;
252
+ }
253
+ }
254
+ nextLine() {
255
+ this.tlm = mul(translate(0, -this.state.text.leading), this.tlm);
256
+ this.tm = this.tlm;
257
+ }
258
+ /** Move the text matrix by an unscaled text-space advance (already includes Tfs, Tc, Tw, Th). */
259
+ advanceText(tx) {
260
+ this.tm = mul(translate(tx, 0), this.tm);
261
+ }
262
+ /** Text rendering matrix for the current glyph: [Tfs*Th 0 0 Tfs 0 Ts] × Tm × CTM. */
263
+ trm() {
264
+ const t = this.state.text;
265
+ return mul(mul([t.fontSize * t.hscale, 0, 0, t.fontSize, 0, t.rise], this.tm), this.state.ctm);
266
+ }
267
+ }
268
+ function spaceFamily(o) {
269
+ if (!o || o.kind !== 'name')
270
+ return null;
271
+ switch (o.value) {
272
+ case 'DeviceGray':
273
+ case 'CalGray':
274
+ case 'G': return 'gray';
275
+ case 'DeviceRGB':
276
+ case 'CalRGB':
277
+ case 'RGB': return 'rgb';
278
+ case 'DeviceCMYK':
279
+ case 'CMYK': return 'cmyk';
280
+ default: return null; // ICCBased / Indexed / Separation / Pattern: unknown luminance
281
+ }
282
+ }
283
+ /**
284
+ * Advance of one glyph in unscaled text space (9.4.4): ((w0 - adj/1000) * Tfs + Tc + Tw?) * Th.
285
+ * `w0` in 1/1000 units; `adj` is the TJ number preceding the glyph (already applied by caller).
286
+ */
287
+ export function glyphAdvance(t, w0, isSpaceCode) {
288
+ return ((w0 / 1000) * t.fontSize + t.charSpacing + (isSpaceCode ? t.wordSpacing : 0)) * t.hscale;
289
+ }
290
+ /** Displacement of a TJ adjustment number in unscaled text space: -adj/1000 * Tfs * Th. */
291
+ export function adjustmentAdvance(t, adj) {
292
+ return (-adj / 1000) * t.fontSize * t.hscale;
293
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * @doccloak/core/pdf - shared types (T206).
3
+ *
4
+ * PDF redaction that keeps the text layer: the redacted glyphs are cut out of
5
+ * the content streams, a placeholder is written back as real text (in the
6
+ * document's own font when it can show the placeholder, else in a matched
7
+ * fallback face), everything else on the page stays live text. Pages that
8
+ * cannot be edited safely are rasterized and reported, never shipped with a
9
+ * box drawn over live text.
10
+ */
11
+ import type { UnredactablePart } from '../dom/errors.js';
12
+ /**
13
+ * Runtime assets the host serves. Everything is optional: in Node tests the
14
+ * fallback fonts are injected through `loadFont` and pdf.js runs without a
15
+ * worker; browsers pass same-origin URLs (no CDN, see the hosting CSP).
16
+ */
17
+ export interface PdfAssetPaths {
18
+ /** URL of the pdf.js worker module (pdf.worker.mjs). Omit to run pdf.js on the calling thread. */
19
+ pdfjsWorkerUrl?: string;
20
+ /** Base URL of the pdf.js binary CMaps (bcmap files), trailing slash. */
21
+ cMapUrl?: string;
22
+ /** Base URL of the pdf.js standard font files, trailing slash. */
23
+ standardFontDataUrl?: string;
24
+ /** Base URL of the pdf.js wasm decoders (openjpeg.wasm, jbig2.wasm, qcms_bg.wasm), trailing slash; needed to rasterize pages with JPX/JBIG2 images. */
25
+ wasmUrl?: string;
26
+ /** Base URL of the Liberation TTFs (`${fontsUrl}LiberationSans-Regular.ttf`), trailing slash. */
27
+ fontsUrl?: string;
28
+ /** Alternative to fontsUrl: load a fallback font file by its file name. */
29
+ loadFont?: (fileName: string) => Promise<Uint8Array>;
30
+ }
31
+ /** Broad family class used to pick a fallback face. */
32
+ export type FontFamilyClass = 'sans' | 'serif' | 'mono';
33
+ /** Style facts derived from a font dictionary, enough to pick a fallback face. */
34
+ export interface FontStyle {
35
+ family: FontFamilyClass;
36
+ bold: boolean;
37
+ italic: boolean;
38
+ }
39
+ /** Axis-aligned rectangle in PDF user space (points, origin bottom-left). */
40
+ export interface Rect {
41
+ x0: number;
42
+ y0: number;
43
+ x1: number;
44
+ y1: number;
45
+ }
46
+ /** 2D affine matrix [a b c d e f] as in the PDF `cm` / `Tm` operators. */
47
+ export type Matrix = readonly [number, number, number, number, number, number];
48
+ /** One shown glyph with its geometry in user space. */
49
+ export interface Glyph {
50
+ /** Character code as encoded in the string operand. */
51
+ code: number;
52
+ /** Number of bytes the code occupies in the operand (1..4). */
53
+ codeLength: number;
54
+ /** Unicode text of the glyph ('' when the font maps it to nothing). May be several code units (ligatures). */
55
+ unicode: string;
56
+ /** Glyph-space width from the font metrics, in 1/1000 text space units (Type3: already scaled by FontMatrix * 1000). */
57
+ width: number;
58
+ /** Horizontal displacement applied after this glyph, in unscaled text space units (font size, Tc, Tw, Tz, TJ adjustment included). */
59
+ advance: number;
60
+ /** Origin of the glyph in user space. */
61
+ x: number;
62
+ y: number;
63
+ /** Effective font size in user space (Tfs scaled by Tm and CTM), for fitting placeholders. */
64
+ fontSize: number;
65
+ /** Index of the operand item (for TJ arrays) this glyph came from; 0 for Tj / ' / ". */
66
+ item: number;
67
+ /** Byte offset of the code inside its string item. */
68
+ byteOffset: number;
69
+ }
70
+ /** One text-showing operator (Tj, TJ, ', ") and every glyph it painted. */
71
+ export interface GlyphRun {
72
+ /** 0-based page index. */
73
+ page: number;
74
+ /** Identifies the content stream: 'p<page>:c<index>' for page contents, 'x<objnum>' for a form XObject. */
75
+ streamKey: string;
76
+ /** Operator index within that stream (as returned by the lexer). */
77
+ opIndex: number;
78
+ /** Resource name of the font (as used with Tf) and its loaded model key. */
79
+ fontName: string;
80
+ fontKey: string;
81
+ glyphs: Glyph[];
82
+ /** Text render mode at the time of showing (3 and 7 paint nothing: invisible OCR layers). */
83
+ renderMode: number;
84
+ /** True when the run is not visible (render mode 3/7, white, fully transparent, tiny, or painted under an opaque image). */
85
+ hidden: boolean;
86
+ /**
87
+ * Hidden text over an image: an OCR layer, the reading of what a scanned picture shows. A
88
+ * redaction that touches it is painted into the picture (the page is exported as an image).
89
+ */
90
+ ocr?: boolean;
91
+ /** Flat-text offset of each glyph's first character (parallel to glyphs). */
92
+ textOffsets: number[];
93
+ /** User-space vector of one em (1000 glyph units) along the baseline, at this run's transform. */
94
+ emX: readonly [number, number];
95
+ /** User-space vector of one em upwards. */
96
+ emY: readonly [number, number];
97
+ /**
98
+ * Stream-local text state at the operator, for content-stream surgery
99
+ * (independent of the CTM at which a shared form XObject was drawn).
100
+ */
101
+ local: RunLocalState;
102
+ /** True for runs that came from the pdf.js oracle on a raster-only page (no stream to edit). */
103
+ oracle?: boolean;
104
+ /**
105
+ * Set for text that lives in a tiling pattern cell or an ExtGState soft-mask group: its
106
+ * user-space geometry is that of the cell/group, not of the page, so it never shares a line
107
+ * with page text and cannot be blacked out by a raster (a pattern is tiled over its fill area).
108
+ */
109
+ space?: string;
110
+ /** Oracle run of a vertical (top-to-bottom) pdf.js item: glyphs advance along emX = (c, d), the item width lies along emY. */
111
+ vertical?: boolean;
112
+ /** Flat-text range covered by the run (inserted separators excluded from glyph coverage but inside the range). */
113
+ textStart: number;
114
+ textEnd: number;
115
+ }
116
+ /** Text state captured at a text-showing operator, in the coordinates of its own stream. */
117
+ export interface RunLocalState {
118
+ /** CTM inside the stream (identity for a page stream at top level, the form's accumulated cm inside a form). */
119
+ ctm: Matrix;
120
+ /** Text matrix before the first glyph and the line matrix. */
121
+ tm: Matrix;
122
+ tlm: Matrix;
123
+ fontSize: number;
124
+ charSpacing: number;
125
+ wordSpacing: number;
126
+ /** Tz / 100 */
127
+ hscale: number;
128
+ leading: number;
129
+ rise: number;
130
+ /** Accumulated advance (unscaled text-space x, already including Tc/Tw/Tz) before each glyph. */
131
+ advances: number[];
132
+ }
133
+ export interface PdfPageInfo {
134
+ index: number;
135
+ /** MediaBox-derived size in points, after /Rotate is ignored (rotation is reported separately). */
136
+ width: number;
137
+ height: number;
138
+ rotate: number;
139
+ hasImages: boolean;
140
+ /** True when the page shows images but no glyph at all (scanned page). */
141
+ imageOnly: boolean;
142
+ /**
143
+ * True when hidden text lies over an image on the page (a scan with an OCR text layer).
144
+ * Redacted values in it are painted over in the picture, and the page is exported as an image.
145
+ */
146
+ ocrText: boolean;
147
+ /** Flat-text range of the page. */
148
+ textStart: number;
149
+ textEnd: number;
150
+ }
151
+ /** A document part the writer drops instead of redacting (reported to the user). */
152
+ export interface RemovedPart {
153
+ kind: 'annotations' | 'form-fields' | 'outlines' | 'attachments' | 'javascript' | 'metadata' | 'structure-tree' | 'page-labels' | 'named-destinations' | 'optional-content' | 'signatures';
154
+ label: string;
155
+ count?: number;
156
+ }
157
+ /** Kinds of unredactable parts specific to PDF, in addition to the shared ones. */
158
+ export type PdfUnredactableKind = UnredactablePart['kind'];
159
+ export interface PdfReadOptions {
160
+ assets?: PdfAssetPaths;
161
+ /** Byte cap for the input; default 50 MB (browser heap). */
162
+ maxBytes?: number;
163
+ /** Page cap; default 500. */
164
+ maxPages?: number;
165
+ /** Cap on glyphs in the whole document (default 1.5 million; each glyph costs memory in the host). */
166
+ maxGlyphs?: number;
167
+ /** User password for encrypted files. Without it an encrypted file is refused. */
168
+ password?: string;
169
+ }
170
+ export interface PlaceholderFit {
171
+ /** Lowest font-size ratio (placeholder size / original size) before condensing kicks in. Default 0.65. */
172
+ minSizeRatio?: number;
173
+ /** Lowest horizontal scaling (Tz / 100) before the rest of the line is shifted. Default 0.8. */
174
+ minHorizontalScale?: number;
175
+ /** Let the placeholder overflow into the following text instead of shifting the line. Default false. */
176
+ allowOverflow?: boolean;
177
+ }
178
+ export interface PdfWriteOptions {
179
+ assets?: PdfAssetPaths;
180
+ /** Export even though the file holds parts DocCloak cannot redact (images with text). Default false. */
181
+ allowUnredactable?: boolean;
182
+ /** Placeholder fitting policy. */
183
+ fit?: PlaceholderFit;
184
+ /** Rasterize pages whose text cannot be edited safely instead of refusing the file. Default true. Scan pages are always rendered. */
185
+ rasterizeUnsafePages?: boolean;
186
+ /** DPI for rasterized pages. Default 150; scan pages use the scan's own resolution (at least this, at most 300). */
187
+ rasterDpi?: number;
188
+ /** Skip the post-write verification (tests only; never in hosts). */
189
+ skipVerify?: boolean;
190
+ }
191
+ export interface PdfWriteResult {
192
+ blob: Blob;
193
+ /** Writer notes the host must show, one line each. */
194
+ warnings: string[];
195
+ /** Parts dropped from the output (annotations, forms, ...). */
196
+ removed: RemovedPart[];
197
+ /** 0-based indexes of pages that were rasterized because their text could not be edited safely (black boxes). */
198
+ rasterizedPages: number[];
199
+ /**
200
+ * 0-based indexes of scan pages with an OCR text layer whose values were painted over in the
201
+ * picture (placeholder labels at the values' places); each is exported as an image.
202
+ */
203
+ scanPages: number[];
204
+ /** Placeholders whose fit needed a line shift (informational). */
205
+ shiftedLines: number;
206
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @doccloak/core/pdf - shared types (T206).
3
+ *
4
+ * PDF redaction that keeps the text layer: the redacted glyphs are cut out of
5
+ * the content streams, a placeholder is written back as real text (in the
6
+ * document's own font when it can show the placeholder, else in a matched
7
+ * fallback face), everything else on the page stays live text. Pages that
8
+ * cannot be edited safely are rasterized and reported, never shipped with a
9
+ * box drawn over live text.
10
+ */
11
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @doccloak/core/pdf - post-write verification (T213).
3
+ *
4
+ * The output is re-read by two independent extractors (ours and pdf.js) and
5
+ * scanned byte-wise; any surviving needle refuses the export. Nothing this
6
+ * module reports is advisory: the writer throws on a non-empty finding list.
7
+ */
8
+ import type { OracleDocument } from './oracle.js';
9
+ export interface VerifyFinding {
10
+ pass: 'own-extractor' | 'oracle' | 'stream-bytes' | 'string-bytes' | 'code-sequence';
11
+ needle: string;
12
+ where: string;
13
+ }
14
+ export declare class PdfVerifyError extends Error {
15
+ readonly findings: VerifyFinding[];
16
+ constructor(findings: VerifyFinding[]);
17
+ }
18
+ export interface VerifyInput {
19
+ bytes: Uint8Array;
20
+ /** Original values that must not survive (case-insensitive, whitespace-insensitive in text passes). */
21
+ needles: string[];
22
+ /** Original code byte sequences removed from content streams, per edited font key (searched in content streams only). */
23
+ removedSequences?: Array<{
24
+ bytes: Uint8Array;
25
+ codeLength?: number;
26
+ }>;
27
+ oracle?: OracleDocument;
28
+ /** Placeholder texts written into the output: a value found only inside one of them ("We" in "[WE]") is not a trace. */
29
+ placeholders?: string[];
30
+ }
31
+ /** Run every pass; returns the findings (empty when clean). */
32
+ export declare function verifyPdf(input: VerifyInput): Promise<VerifyFinding[]>;
33
+ export declare function indexOfBytes(hay: Uint8Array, needle: Uint8Array, from?: number): number;