@embedpdf/engine-services 3.0.0-next.1 → 3.0.0-next.10
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/index.cjs +901 -422
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -20
- package/dist/index.d.ts +138 -20
- package/dist/index.js +901 -424
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -226,8 +226,10 @@ var EventHub = class {
|
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
228
|
publish(event) {
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
if ("origin" in event) {
|
|
230
|
+
const { serverId } = event.origin;
|
|
231
|
+
if (serverId !== null) this.serverId = this.serverId === null ? serverId : Math.max(this.serverId, serverId);
|
|
232
|
+
}
|
|
231
233
|
for (const listener of [...this.listeners]) try {
|
|
232
234
|
listener(event);
|
|
233
235
|
} catch (err) {
|
|
@@ -1164,13 +1166,34 @@ var MetadataMutator = class {
|
|
|
1164
1166
|
};
|
|
1165
1167
|
//#endregion
|
|
1166
1168
|
//#region src/runtime/memory/structs.ts
|
|
1169
|
+
/** Generated from `EPDF_CHAR_GEOMETRY`; pinned by native static_asserts. */
|
|
1170
|
+
const EPDF_CHAR_GEOMETRY_LAYOUT = {
|
|
1171
|
+
bytes: 124,
|
|
1172
|
+
offsets: {
|
|
1173
|
+
looseBox: 0,
|
|
1174
|
+
tightBox: 16,
|
|
1175
|
+
looseQuad: 32,
|
|
1176
|
+
tightQuad: 64,
|
|
1177
|
+
matrix: 96,
|
|
1178
|
+
flags: 120
|
|
1179
|
+
},
|
|
1180
|
+
flags: {
|
|
1181
|
+
hasTightBox: 1,
|
|
1182
|
+
hasLooseQuad: 2,
|
|
1183
|
+
hasTightQuad: 4,
|
|
1184
|
+
upright: 8,
|
|
1185
|
+
space: 16,
|
|
1186
|
+
empty: 32,
|
|
1187
|
+
synthesized: 64
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1167
1190
|
/** Decode an `FS_RECTF` struct that a native call wrote into `ptr`. */
|
|
1168
|
-
function readRectF(mem, ptr) {
|
|
1191
|
+
function readRectF(mem, ptr, byteOffset = 0) {
|
|
1169
1192
|
return {
|
|
1170
|
-
left: readF32(mem, ptr,
|
|
1171
|
-
top: readF32(mem, ptr, 4),
|
|
1172
|
-
right: readF32(mem, ptr, 8),
|
|
1173
|
-
bottom: readF32(mem, ptr, 12)
|
|
1193
|
+
left: readF32(mem, ptr, byteOffset),
|
|
1194
|
+
top: readF32(mem, ptr, byteOffset + 4),
|
|
1195
|
+
right: readF32(mem, ptr, byteOffset + 8),
|
|
1196
|
+
bottom: readF32(mem, ptr, byteOffset + 12)
|
|
1174
1197
|
};
|
|
1175
1198
|
}
|
|
1176
1199
|
/** Decode an `FS_SIZEF` struct that a native call wrote into `ptr`. */
|
|
@@ -1706,6 +1729,42 @@ var PagesInserter = class {
|
|
|
1706
1729
|
cache: null
|
|
1707
1730
|
};
|
|
1708
1731
|
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Create `count` blank pages of `size` at `destIndex`. Same mutation
|
|
1734
|
+
* contract as `insert`, but native creation (`FPDFPage_New`) instead of a
|
|
1735
|
+
* deep copy: no source document, so none of the retain-until-close
|
|
1736
|
+
* lifetime hazard above. Each page gets an empty content stream
|
|
1737
|
+
* (`FPDFPage_GenerateContent`) so the saved PDF renders identically in
|
|
1738
|
+
* third-party viewers.
|
|
1739
|
+
*/
|
|
1740
|
+
insertBlank(spec, destIndex, signal) {
|
|
1741
|
+
throwIfAborted(signal);
|
|
1742
|
+
const { size } = spec;
|
|
1743
|
+
const count = spec.count ?? 1;
|
|
1744
|
+
if (!Number.isFinite(size?.width) || !Number.isFinite(size?.height) || size.width <= 0 || size.height <= 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `pages.insertBlank size must have positive finite dimensions, got ${size?.width}x${size?.height}`);
|
|
1745
|
+
if (!Number.isInteger(count) || count < 1 || count > _embedpdf_engine_core_runtime.PAGE_INSERT_BLANK_MAX_COUNT) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `pages.insertBlank count ${count} out of range [1, ${_embedpdf_engine_core_runtime.PAGE_INSERT_BLANK_MAX_COUNT}]`);
|
|
1746
|
+
const { fn } = this.runtime;
|
|
1747
|
+
const destPtr = this.session.requireDocPtr();
|
|
1748
|
+
const beforeCount = fn.FPDF_GetPageCount(destPtr);
|
|
1749
|
+
const at = destIndex ?? beforeCount;
|
|
1750
|
+
if (!Number.isInteger(at) || at < 0 || at > beforeCount) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `pages.insertBlank destIndex ${at} out of range [0, ${beforeCount}]`);
|
|
1751
|
+
for (let i = 0; i < count; i++) {
|
|
1752
|
+
const pagePtr = fn.FPDFPage_New(destPtr, at + i, size.width, size.height);
|
|
1753
|
+
if (!pagePtr) {
|
|
1754
|
+
for (let j = 0; j < i; j++) fn.FPDFPage_Delete(destPtr, at);
|
|
1755
|
+
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `FPDFPage_New rejected page ${i + 1}/${count} at index ${at + i}`);
|
|
1756
|
+
}
|
|
1757
|
+
fn.FPDFPage_GenerateContent(pagePtr);
|
|
1758
|
+
fn.FPDF_ClosePage(pagePtr);
|
|
1759
|
+
}
|
|
1760
|
+
this.session.refreshPageRegistry();
|
|
1761
|
+
const layout = new PagesReader(this.runtime, this.session).read(signal);
|
|
1762
|
+
return {
|
|
1763
|
+
insertedPageObjectNumbers: layout.pages.slice(at, at + count).map((page) => page.pageObjectNumber),
|
|
1764
|
+
layout,
|
|
1765
|
+
cache: null
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1709
1768
|
};
|
|
1710
1769
|
//#endregion
|
|
1711
1770
|
//#region src/features/annotations/internal/read/annotationReadContext.ts
|
|
@@ -2154,6 +2213,49 @@ function readQuadPoints(fn, mem, annotPtr) {
|
|
|
2154
2213
|
});
|
|
2155
2214
|
}
|
|
2156
2215
|
//#endregion
|
|
2216
|
+
//#region src/features/annotations/internal/read/readAnnotationTransformMetadata.ts
|
|
2217
|
+
/**
|
|
2218
|
+
* Read-side twin of `writers/.../writeAnnotationTransformMetadata.ts`. Decodes
|
|
2219
|
+
* the EmbedPDF rotation keys from /EMBD_Metadata back onto the DTO:
|
|
2220
|
+
*
|
|
2221
|
+
* - `Rotation` — degrees, PDF convention. Returned for EVERY rotatable
|
|
2222
|
+
* kind (box AND vertex); the plugin converts it back to its CW-content
|
|
2223
|
+
* `rot` once at the repository seam.
|
|
2224
|
+
* - `UnrotatedRect` — the logical box. BOX kinds only (square/circle/
|
|
2225
|
+
* free-text); a vertex annotation never carries one (its points are the
|
|
2226
|
+
* visual), so vertex readers don't ask for it.
|
|
2227
|
+
*
|
|
2228
|
+
* Both are absent on un-rotated annotations (and on everything authored before
|
|
2229
|
+
* rotation shipped), so both reads return `undefined` and the DTO field stays
|
|
2230
|
+
* unset.
|
|
2231
|
+
*/
|
|
2232
|
+
const KEY_ROTATION$1 = "Rotation";
|
|
2233
|
+
const KEY_UNROTATED_RECT$1 = "UnrotatedRect";
|
|
2234
|
+
/** The advisory/portable rotation scalar (degrees, PDF convention), or
|
|
2235
|
+
* `undefined` when absent or zero. */
|
|
2236
|
+
function readAnnotationRotation(fn, mem, annotPtr) {
|
|
2237
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
|
|
2238
|
+
return withScratch(mem, 4, (buf) => {
|
|
2239
|
+
if (!fn.EPDFAnnot_GetEmbedMetadataNumber(annotPtr, KEY_ROTATION$1, buf)) return void 0;
|
|
2240
|
+
const v = readF32(mem, buf);
|
|
2241
|
+
return v ? v : void 0;
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
/** The logical (unrotated) box of a rotated BOX annotation, or `undefined`. */
|
|
2245
|
+
function readAnnotationUnrotatedRect(fn, mem, annotPtr) {
|
|
2246
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
|
|
2247
|
+
return withScratch(mem, 16, (buf) => {
|
|
2248
|
+
if (!fn.EPDFAnnot_GetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT$1, buf)) return void 0;
|
|
2249
|
+
const r = readRectF(mem, buf);
|
|
2250
|
+
return {
|
|
2251
|
+
left: r.left,
|
|
2252
|
+
bottom: r.bottom,
|
|
2253
|
+
right: r.right,
|
|
2254
|
+
top: r.top
|
|
2255
|
+
};
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
//#endregion
|
|
2157
2259
|
//#region src/features/annotations/internal/textEditIntent.ts
|
|
2158
2260
|
/** Adobe-compatible `/IT` names, normalized at the engine-core boundary. */
|
|
2159
2261
|
const IT_REPLACE = "Replace";
|
|
@@ -2188,13 +2290,17 @@ function readCaret(fn, mem, annotPtr, base) {
|
|
|
2188
2290
|
const opacity = ca == null ? 1 : Math.max(0, Math.min(1, ca));
|
|
2189
2291
|
const rd = readRectangleDifferences(fn, mem, annotPtr);
|
|
2190
2292
|
const intent = caretIntentFromName(readIntent(fn, mem, annotPtr));
|
|
2293
|
+
const rotation = readAnnotationRotation(fn, mem, annotPtr);
|
|
2294
|
+
const unrotatedRect = readAnnotationUnrotatedRect(fn, mem, annotPtr);
|
|
2191
2295
|
return {
|
|
2192
2296
|
...base,
|
|
2193
2297
|
subtype: "caret",
|
|
2194
2298
|
intent,
|
|
2195
2299
|
color,
|
|
2196
2300
|
opacity,
|
|
2197
|
-
rectDifferences: rd
|
|
2301
|
+
rectDifferences: rd,
|
|
2302
|
+
...rotation != null ? { rotation } : {},
|
|
2303
|
+
...unrotatedRect ? { unrotatedRect } : {}
|
|
2198
2304
|
};
|
|
2199
2305
|
}
|
|
2200
2306
|
//#endregion
|
|
@@ -2612,49 +2718,6 @@ function readFilledStyleExtras(fn, mem, annotPtr) {
|
|
|
2612
2718
|
};
|
|
2613
2719
|
}
|
|
2614
2720
|
//#endregion
|
|
2615
|
-
//#region src/features/annotations/internal/read/readAnnotationTransformMetadata.ts
|
|
2616
|
-
/**
|
|
2617
|
-
* Read-side twin of `writers/.../writeAnnotationTransformMetadata.ts`. Decodes
|
|
2618
|
-
* the EmbedPDF rotation keys from /EMBD_Metadata back onto the DTO:
|
|
2619
|
-
*
|
|
2620
|
-
* - `Rotation` — degrees, PDF convention. Returned for EVERY rotatable
|
|
2621
|
-
* kind (box AND vertex); the plugin converts it back to its CW-content
|
|
2622
|
-
* `rot` once at the repository seam.
|
|
2623
|
-
* - `UnrotatedRect` — the logical box. BOX kinds only (square/circle/
|
|
2624
|
-
* free-text); a vertex annotation never carries one (its points are the
|
|
2625
|
-
* visual), so vertex readers don't ask for it.
|
|
2626
|
-
*
|
|
2627
|
-
* Both are absent on un-rotated annotations (and on everything authored before
|
|
2628
|
-
* rotation shipped), so both reads return `undefined` and the DTO field stays
|
|
2629
|
-
* unset.
|
|
2630
|
-
*/
|
|
2631
|
-
const KEY_ROTATION$1 = "Rotation";
|
|
2632
|
-
const KEY_UNROTATED_RECT$1 = "UnrotatedRect";
|
|
2633
|
-
/** The advisory/portable rotation scalar (degrees, PDF convention), or
|
|
2634
|
-
* `undefined` when absent or zero. */
|
|
2635
|
-
function readAnnotationRotation(fn, mem, annotPtr) {
|
|
2636
|
-
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
|
|
2637
|
-
return withScratch(mem, 4, (buf) => {
|
|
2638
|
-
if (!fn.EPDFAnnot_GetEmbedMetadataNumber(annotPtr, KEY_ROTATION$1, buf)) return void 0;
|
|
2639
|
-
const v = readF32(mem, buf);
|
|
2640
|
-
return v ? v : void 0;
|
|
2641
|
-
});
|
|
2642
|
-
}
|
|
2643
|
-
/** The logical (unrotated) box of a rotated BOX annotation, or `undefined`. */
|
|
2644
|
-
function readAnnotationUnrotatedRect(fn, mem, annotPtr) {
|
|
2645
|
-
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
|
|
2646
|
-
return withScratch(mem, 16, (buf) => {
|
|
2647
|
-
if (!fn.EPDFAnnot_GetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT$1, buf)) return void 0;
|
|
2648
|
-
const r = readRectF(mem, buf);
|
|
2649
|
-
return {
|
|
2650
|
-
left: r.left,
|
|
2651
|
-
bottom: r.bottom,
|
|
2652
|
-
right: r.right,
|
|
2653
|
-
top: r.top
|
|
2654
|
-
};
|
|
2655
|
-
});
|
|
2656
|
-
}
|
|
2657
|
-
//#endregion
|
|
2658
2721
|
//#region src/features/annotations/internal/read/readFreeTextAnnotation.ts
|
|
2659
2722
|
/** Default `/DA` colour (black) when an annotation has no default appearance. */
|
|
2660
2723
|
const DEFAULT_FREETEXT_COLOR$1 = {
|
|
@@ -3107,6 +3170,47 @@ function readStampName(fn, mem, annotPtr) {
|
|
|
3107
3170
|
return readAnnotString(fn, mem, annotPtr, "Name");
|
|
3108
3171
|
}
|
|
3109
3172
|
//#endregion
|
|
3173
|
+
//#region src/features/annotations/internal/annotationState.ts
|
|
3174
|
+
/**
|
|
3175
|
+
* `/State` + `/StateModel` spelling maps — ISO 32000 §12.5.6.3, Table 174.
|
|
3176
|
+
*
|
|
3177
|
+
* Known values are wire-stable lowercase (the house vocabulary — same
|
|
3178
|
+
* rationale as `AnnotationReplyType` / `AnnotationBorderStyle`); the PDF
|
|
3179
|
+
* stores the capitalized Table 174 spellings. Unknown values — custom
|
|
3180
|
+
* Acrobat state models and their states — pass through VERBATIM in both
|
|
3181
|
+
* directions, so foreign review workflows survive a round trip.
|
|
3182
|
+
*/
|
|
3183
|
+
const STATE_MODEL_TO_PDF = {
|
|
3184
|
+
review: "Review",
|
|
3185
|
+
marked: "Marked"
|
|
3186
|
+
};
|
|
3187
|
+
const STATE_TO_PDF = {
|
|
3188
|
+
accepted: "Accepted",
|
|
3189
|
+
rejected: "Rejected",
|
|
3190
|
+
cancelled: "Cancelled",
|
|
3191
|
+
completed: "Completed",
|
|
3192
|
+
none: "None",
|
|
3193
|
+
marked: "Marked",
|
|
3194
|
+
unmarked: "Unmarked"
|
|
3195
|
+
};
|
|
3196
|
+
const PDF_TO_STATE_MODEL = invert(STATE_MODEL_TO_PDF);
|
|
3197
|
+
const PDF_TO_STATE = invert(STATE_TO_PDF);
|
|
3198
|
+
function stateModelToPdf(value) {
|
|
3199
|
+
return STATE_MODEL_TO_PDF[value] ?? value;
|
|
3200
|
+
}
|
|
3201
|
+
function stateModelFromPdf(value) {
|
|
3202
|
+
return PDF_TO_STATE_MODEL[value] ?? value;
|
|
3203
|
+
}
|
|
3204
|
+
function stateToPdf(value) {
|
|
3205
|
+
return STATE_TO_PDF[value] ?? value;
|
|
3206
|
+
}
|
|
3207
|
+
function stateFromPdf(value) {
|
|
3208
|
+
return PDF_TO_STATE[value] ?? value;
|
|
3209
|
+
}
|
|
3210
|
+
function invert(map) {
|
|
3211
|
+
return Object.fromEntries(Object.entries(map).map(([wire, pdf]) => [pdf, wire]));
|
|
3212
|
+
}
|
|
3213
|
+
//#endregion
|
|
3110
3214
|
//#region src/features/annotations/internal/read/readTextAnnotation.ts
|
|
3111
3215
|
/** Default `/C` — matches the generator's yellow note fill and the writer default. */
|
|
3112
3216
|
const DEFAULT_NOTE_COLOR$1 = {
|
|
@@ -3121,12 +3225,16 @@ function readText(fn, mem, annotPtr, base) {
|
|
|
3121
3225
|
const ca = readAnnotOpacity(fn, mem, annotPtr);
|
|
3122
3226
|
const opacity = ca == null ? 1 : Math.max(0, Math.min(1, ca));
|
|
3123
3227
|
const icon = NOTE_CODE_TO_ICON[fn.EPDFAnnot_GetName(annotPtr)] ?? DEFAULT_NOTE_ICON;
|
|
3228
|
+
const stateRaw = readAnnotString(fn, mem, annotPtr, "State");
|
|
3229
|
+
const stateModelRaw = readAnnotString(fn, mem, annotPtr, "StateModel");
|
|
3124
3230
|
return {
|
|
3125
3231
|
...base,
|
|
3126
3232
|
subtype: "text",
|
|
3127
3233
|
icon,
|
|
3128
3234
|
color,
|
|
3129
|
-
opacity
|
|
3235
|
+
opacity,
|
|
3236
|
+
state: stateRaw === null ? null : stateFromPdf(stateRaw),
|
|
3237
|
+
stateModel: stateModelRaw === null ? null : stateModelFromPdf(stateModelRaw)
|
|
3130
3238
|
};
|
|
3131
3239
|
}
|
|
3132
3240
|
//#endregion
|
|
@@ -3841,6 +3949,7 @@ function readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision
|
|
|
3841
3949
|
const rect = readAnnotRect(fn, mem, annotPtr);
|
|
3842
3950
|
const flags = readAnnotFlags(fn, annotPtr);
|
|
3843
3951
|
const contents = readAnnotString(fn, mem, annotPtr, "Contents");
|
|
3952
|
+
const subject = readAnnotString(fn, mem, annotPtr, "Subj");
|
|
3844
3953
|
const author = readAnnotString(fn, mem, annotPtr, "T");
|
|
3845
3954
|
const createdRaw = readAnnotString(fn, mem, annotPtr, "CreationDate");
|
|
3846
3955
|
const modifiedRaw = readAnnotString(fn, mem, annotPtr, "M");
|
|
@@ -3857,6 +3966,7 @@ function readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision
|
|
|
3857
3966
|
flags,
|
|
3858
3967
|
rect,
|
|
3859
3968
|
contents,
|
|
3969
|
+
subject,
|
|
3860
3970
|
author,
|
|
3861
3971
|
created: createdRaw ? pdfDateToIso(createdRaw) : null,
|
|
3862
3972
|
modified: modifiedRaw ? pdfDateToIso(modifiedRaw) : null,
|
|
@@ -4131,19 +4241,31 @@ const ANNOT_SUBTYPE_WIDGET = 20;
|
|
|
4131
4241
|
/** `FPDF_ANNOT_FREETEXT` — free-text annotation subtype code. */
|
|
4132
4242
|
const ANNOT_SUBTYPE_FREETEXT = 3;
|
|
4133
4243
|
/**
|
|
4134
|
-
* BOX-family subtypes (free-text 3, square 5, circle 6, stamp 13):
|
|
4135
|
-
* whose v3 writers put rotation in the AP `/Matrix` +
|
|
4136
|
-
* `/UnrotatedRect`. Only these are eligible for
|
|
4137
|
-
* rendering — vertex kinds
|
|
4138
|
-
* geometry, so their rasters must
|
|
4244
|
+
* BOX-family subtypes (free-text 3, square 5, circle 6, stamp 13, caret 14):
|
|
4245
|
+
* the kinds whose v3 writers put rotation in the AP `/Matrix` +
|
|
4246
|
+
* `/EMBD_Metadata` `/UnrotatedRect`. Only these are eligible for
|
|
4247
|
+
* rotation-stripped appearance rendering — vertex kinds
|
|
4248
|
+
* (line/polyline/polygon/ink) pre-rotate their geometry, so their rasters must
|
|
4249
|
+
* stay on the classic path. This set MUST cover every kind whose reader
|
|
4250
|
+
* surfaces the rotation pair: `fromDTO` (plugin-annotation repository) mirrors
|
|
4251
|
+
* this exact condition to re-apply the stripped rotation as `apRot`.
|
|
4139
4252
|
*/
|
|
4140
4253
|
const BOX_FAMILY_SUBTYPES = /* @__PURE__ */ new Set([
|
|
4141
4254
|
3,
|
|
4142
4255
|
5,
|
|
4143
4256
|
6,
|
|
4144
|
-
13
|
|
4257
|
+
13,
|
|
4258
|
+
14
|
|
4145
4259
|
]);
|
|
4146
4260
|
/**
|
|
4261
|
+
* Per-appearance output-pixel ceiling (see the clamp in `renderOne`).
|
|
4262
|
+
* 16 M px ≈ a 4000² raster ≈ 64 MB of transient RGBA — safely inside the
|
|
4263
|
+
* wasm heap for the one-at-a-time batch loop, while a page-sized annotation
|
|
4264
|
+
* stays crisp to roughly 5–6× before its appearance starts stretching.
|
|
4265
|
+
* Small annotations (the overwhelming majority) never come near it.
|
|
4266
|
+
*/
|
|
4267
|
+
const APPEARANCE_PIXEL_CLAMP = 16e6;
|
|
4268
|
+
/**
|
|
4147
4269
|
* Maps an `AnnotationAppearanceMode` onto the PDFium appearance-mode int and
|
|
4148
4270
|
* the `EPDFAnnot_GetAvailableAppearanceModes` bit it occupies.
|
|
4149
4271
|
* N -> mode 0 / bit 1, R -> mode 1 / bit 2, D -> mode 2 / bit 4.
|
|
@@ -4242,13 +4364,15 @@ var AnnotationAppearanceReader = class {
|
|
|
4242
4364
|
fn.EPDFAnnot_GenerateFormFieldAP(annotPtr);
|
|
4243
4365
|
if (!fn.EPDFAnnot_HasAppearanceStream(annotPtr, modeInt)) return null;
|
|
4244
4366
|
} else return null;
|
|
4367
|
+
const rectArea = Math.max(1, (rect.right - rect.left) * (rect.top - rect.bottom));
|
|
4368
|
+
const effScale = Math.min(scale, Math.sqrt(APPEARANCE_PIXEL_CLAMP / rectArea));
|
|
4245
4369
|
return rasterize(this.runtime, {
|
|
4246
4370
|
rect,
|
|
4247
4371
|
page,
|
|
4248
4372
|
rotation,
|
|
4249
4373
|
viewport: {
|
|
4250
4374
|
kind: "scale",
|
|
4251
|
-
scale
|
|
4375
|
+
scale: effScale
|
|
4252
4376
|
},
|
|
4253
4377
|
...maxOutputPixels !== void 0 ? { maxOutputPixels } : {},
|
|
4254
4378
|
background: "transparent",
|
|
@@ -4363,7 +4487,9 @@ function setAnnotFlags(fn, annotPtr, partial) {
|
|
|
4363
4487
|
if (!fn.FPDFAnnot_SetFlags(annotPtr, next)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "FPDFAnnot_SetFlags returned false");
|
|
4364
4488
|
}
|
|
4365
4489
|
/**
|
|
4366
|
-
* Write `/Rect`
|
|
4490
|
+
* Write `/Rect` without touching `/AP`. Appearance preservation/regeneration
|
|
4491
|
+
* belongs to AnnotationMutator; a geometry primitive must not silently rewrite
|
|
4492
|
+
* an existing appearance `/BBox`. FS_RECTF layout is
|
|
4367
4493
|
* `{ left, top, right, bottom }` (top > bottom in PDF coords).
|
|
4368
4494
|
*/
|
|
4369
4495
|
function setAnnotRect(fn, mem, annotPtr, rect) {
|
|
@@ -4373,12 +4499,40 @@ function setAnnotRect(fn, mem, annotPtr, rect) {
|
|
|
4373
4499
|
mem.poke(buf, "f32", rect.top, 4);
|
|
4374
4500
|
mem.poke(buf, "f32", rect.right, 8);
|
|
4375
4501
|
mem.poke(buf, "f32", rect.bottom, 12);
|
|
4376
|
-
if (!fn.
|
|
4502
|
+
if (!fn.EPDFAnnot_SetRect(annotPtr, buf)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "EPDFAnnot_SetRect returned false");
|
|
4377
4503
|
} finally {
|
|
4378
4504
|
mem.free(buf);
|
|
4379
4505
|
}
|
|
4380
4506
|
}
|
|
4381
4507
|
/**
|
|
4508
|
+
* Write a UTF-16 string entry into an annotation dictionary. The write
|
|
4509
|
+
* side of `readAnnotString`.
|
|
4510
|
+
*/
|
|
4511
|
+
function writeAnnotString(fn, mem, annotPtr, key, value) {
|
|
4512
|
+
const ptr = mem.writeU16String(value);
|
|
4513
|
+
try {
|
|
4514
|
+
fn.FPDFAnnot_SetStringValue(annotPtr, key, ptr);
|
|
4515
|
+
} finally {
|
|
4516
|
+
mem.free(ptr);
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
/**
|
|
4520
|
+
* Three-state string write: `null` REMOVES the dictionary entry.
|
|
4521
|
+
*
|
|
4522
|
+
* Clearing goes through `EPDFAnnot_RemoveKey` because
|
|
4523
|
+
* `FPDFAnnot_SetStringValue` with a NULL value writes an EMPTY string —
|
|
4524
|
+
* it never removes. True removal is what keeps the read side honest:
|
|
4525
|
+
* `readAnnotString` returns `null` iff the key is absent, so a cleared
|
|
4526
|
+
* field reads back as `null`, not `''`.
|
|
4527
|
+
*/
|
|
4528
|
+
function writeAnnotStringOrClear(fn, mem, annotPtr, key, value) {
|
|
4529
|
+
if (value === null) {
|
|
4530
|
+
fn.EPDFAnnot_RemoveKey(annotPtr, key);
|
|
4531
|
+
return;
|
|
4532
|
+
}
|
|
4533
|
+
writeAnnotString(fn, mem, annotPtr, key, value);
|
|
4534
|
+
}
|
|
4535
|
+
/**
|
|
4382
4536
|
* Set `/BS /S` (border style code) and `/BS /W` (width) in one call via
|
|
4383
4537
|
* `EPDFAnnot_SetBorderStyle`. `styleCode` is a raw
|
|
4384
4538
|
* `FPDF_ANNOT_BORDER_STYLE` enum value — the string<->code mapping lives
|
|
@@ -4577,8 +4731,8 @@ function setCalloutLine(fn, mem, annotPtr, points) {
|
|
|
4577
4731
|
//#region src/features/annotations/internal/write/writeAnnotationBase.ts
|
|
4578
4732
|
/**
|
|
4579
4733
|
* Write the annotation-wide base fields shared by every Draft
|
|
4580
|
-
* (contents/nm). The kind-specific writer calls this BEFORE its
|
|
4581
|
-
* field writes; order doesn't actually matter at the PDF level, but
|
|
4734
|
+
* (contents/subject/nm). The kind-specific writer calls this BEFORE its
|
|
4735
|
+
* own field writes; order doesn't actually matter at the PDF level, but
|
|
4582
4736
|
* keeping the base first makes the writers symmetric with the readers.
|
|
4583
4737
|
*
|
|
4584
4738
|
* `/T` (author display) is stamped by the mutator from
|
|
@@ -4589,13 +4743,14 @@ function setCalloutLine(fn, mem, annotPtr, points) {
|
|
|
4589
4743
|
* annotation; the writer does not.
|
|
4590
4744
|
*/
|
|
4591
4745
|
function applyAnnotationBaseDraft(fn, mem, annotPtr, draft) {
|
|
4592
|
-
if (draft.contents !== void 0)
|
|
4593
|
-
if (draft.
|
|
4746
|
+
if (draft.contents !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Contents", draft.contents);
|
|
4747
|
+
if (draft.subject !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Subj", draft.subject);
|
|
4748
|
+
if (draft.nm !== void 0 && draft.nm.length > 0) writeAnnotString(fn, mem, annotPtr, "NM", draft.nm);
|
|
4594
4749
|
if (draft.flags !== void 0) setAnnotFlags(fn, annotPtr, draft.flags);
|
|
4595
4750
|
}
|
|
4596
4751
|
/**
|
|
4597
4752
|
* Write the annotation-wide base fields shared by every Patch
|
|
4598
|
-
* (contents). /T is bound at creation and not patchable — see
|
|
4753
|
+
* (contents/subject). /T is bound at creation and not patchable — see
|
|
4599
4754
|
* `AnnotationPatchBase`. /NM is monotonic per annotation; the mutator
|
|
4600
4755
|
* may stamp /NM opportunistically on a weak annotation via
|
|
4601
4756
|
* `writeAnnotationNm`.
|
|
@@ -4606,7 +4761,8 @@ function applyAnnotationBaseDraft(fn, mem, annotPtr, draft) {
|
|
|
4606
4761
|
* "..." -> set the entry to that value
|
|
4607
4762
|
*/
|
|
4608
4763
|
function applyAnnotationBasePatch(fn, mem, annotPtr, patch) {
|
|
4609
|
-
if (patch.contents !== void 0)
|
|
4764
|
+
if (patch.contents !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Contents", patch.contents);
|
|
4765
|
+
if (patch.subject !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Subj", patch.subject);
|
|
4610
4766
|
if (patch.flags !== void 0) setAnnotFlags(fn, annotPtr, patch.flags);
|
|
4611
4767
|
}
|
|
4612
4768
|
/**
|
|
@@ -4617,7 +4773,7 @@ function applyAnnotationBasePatch(fn, mem, annotPtr, patch) {
|
|
|
4617
4773
|
*/
|
|
4618
4774
|
function writeAnnotationAuthor(fn, mem, annotPtr, displayName) {
|
|
4619
4775
|
if (displayName.length === 0) return;
|
|
4620
|
-
|
|
4776
|
+
writeAnnotString(fn, mem, annotPtr, "T", displayName);
|
|
4621
4777
|
}
|
|
4622
4778
|
/**
|
|
4623
4779
|
* Stamp /NM on an annotation that didn't have one. Used by the mutator
|
|
@@ -4626,7 +4782,7 @@ function writeAnnotationAuthor(fn, mem, annotPtr, displayName) {
|
|
|
4626
4782
|
*/
|
|
4627
4783
|
function writeAnnotationNm(fn, mem, annotPtr, nm) {
|
|
4628
4784
|
if (nm.length === 0) return;
|
|
4629
|
-
|
|
4785
|
+
writeAnnotString(fn, mem, annotPtr, "NM", nm);
|
|
4630
4786
|
}
|
|
4631
4787
|
/**
|
|
4632
4788
|
* Stamp /M (modification date) on an annotation. /M is a standard
|
|
@@ -4643,22 +4799,7 @@ function writeAnnotationNm(fn, mem, annotPtr, nm) {
|
|
|
4643
4799
|
* Format: `D:YYYYMMDDHHmmSSOHH'mm'` (see formatPdfDate).
|
|
4644
4800
|
*/
|
|
4645
4801
|
function writeAnnotationModified(fn, mem, annotPtr, now = /* @__PURE__ */ new Date()) {
|
|
4646
|
-
|
|
4647
|
-
}
|
|
4648
|
-
function writeString(fn, mem, annotPtr, key, value) {
|
|
4649
|
-
const ptr = mem.writeU16String(value);
|
|
4650
|
-
try {
|
|
4651
|
-
fn.FPDFAnnot_SetStringValue(annotPtr, key, ptr);
|
|
4652
|
-
} finally {
|
|
4653
|
-
mem.free(ptr);
|
|
4654
|
-
}
|
|
4655
|
-
}
|
|
4656
|
-
function writeStringOrClear(fn, mem, annotPtr, key, value) {
|
|
4657
|
-
if (value === null) {
|
|
4658
|
-
fn.FPDFAnnot_SetStringValue(annotPtr, key, _embedpdf_engine_runtime.NULL_PTR);
|
|
4659
|
-
return;
|
|
4660
|
-
}
|
|
4661
|
-
writeString(fn, mem, annotPtr, key, value);
|
|
4802
|
+
writeAnnotString(fn, mem, annotPtr, "M", formatPdfDate(now));
|
|
4662
4803
|
}
|
|
4663
4804
|
//#endregion
|
|
4664
4805
|
//#region src/features/annotations/internal/identity/captureOrStampStableId.ts
|
|
@@ -4830,50 +4971,229 @@ function readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, index, revis
|
|
|
4830
4971
|
return reader(fn, mem, annotPtr, base, subtypeCode, ctx);
|
|
4831
4972
|
}
|
|
4832
4973
|
//#endregion
|
|
4833
|
-
//#region src/features/annotations/internal/write/
|
|
4834
|
-
/** Default `/C` colour when a caret draft omits it (engine-wide default mark). */
|
|
4835
|
-
const DEFAULT_CARET_COLOR = {
|
|
4836
|
-
r: 255,
|
|
4837
|
-
g: 0,
|
|
4838
|
-
b: 0
|
|
4839
|
-
};
|
|
4840
|
-
/** Default opacity, set explicitly so reads always round-trip the same value. */
|
|
4841
|
-
const DEFAULT_OPACITY$4 = 1;
|
|
4974
|
+
//#region src/features/annotations/internal/write/writeEmbedMetadata.ts
|
|
4842
4975
|
/**
|
|
4843
|
-
*
|
|
4844
|
-
*
|
|
4845
|
-
*
|
|
4846
|
-
*
|
|
4847
|
-
*
|
|
4848
|
-
*
|
|
4976
|
+
* Write the EmbedPDF-namespaced /EMBD_Metadata annotation dictionary.
|
|
4977
|
+
*
|
|
4978
|
+
* This module owns ONLY the vendor-extension dictionary. Standard PDF
|
|
4979
|
+
* base fields (/T, /M, /NM, /Contents) are handled by
|
|
4980
|
+
* `writers/annotations/base.ts` — /EMBD_Metadata is a separate
|
|
4981
|
+
* dictionary nested under the annotation that carries cloud-side
|
|
4982
|
+
* identity bookkeeping which has no PDF-spec equivalent.
|
|
4983
|
+
*
|
|
4984
|
+
* Schema (v1):
|
|
4985
|
+
* /EMBD_Metadata <<
|
|
4986
|
+
* /SchemaVersion 1 % EmbedPDF metadata schema version
|
|
4987
|
+
* /UserID (acme/alice) % identity that created the annotation
|
|
4988
|
+
* /GroupID (engineering) % group the annotation belongs to
|
|
4989
|
+
* /CreatedBy (acme/alice) % same as UserID on create
|
|
4990
|
+
* /UpdatedBy (acme/alice) % last editor; refreshed on every update
|
|
4991
|
+
* >>
|
|
4992
|
+
*
|
|
4993
|
+
* Read side: see `readers/annotations/embed-metadata.ts` (commit 12).
|
|
4994
|
+
*
|
|
4995
|
+
* PDFium bindings used: the `EPDFAnnot_*EmbedMetadata*` extension API
|
|
4996
|
+
* shipped in the EmbedPDF PDFium fork. Values are written as UTF-16LE
|
|
4997
|
+
* strings via the same `mem.writeU16String` pattern the base writer
|
|
4998
|
+
* uses for /T.
|
|
4849
4999
|
*/
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
if (draft.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(draft.intent));
|
|
4856
|
-
if (draft.rectDifferences != null) setRectangleDifferences(fn, annotPtr, draft.rectDifferences);
|
|
4857
|
-
}
|
|
5000
|
+
const KEY_SCHEMA_VERSION = "SchemaVersion";
|
|
5001
|
+
const KEY_USER_ID = "UserID";
|
|
5002
|
+
const KEY_GROUP_ID = "GroupID";
|
|
5003
|
+
const KEY_CREATED_BY = "CreatedBy";
|
|
5004
|
+
const KEY_UPDATED_BY = "UpdatedBy";
|
|
4858
5005
|
/**
|
|
4859
|
-
*
|
|
4860
|
-
*
|
|
5006
|
+
* Stamp /EMBD_Metadata on a freshly created annotation. No-op if the
|
|
5007
|
+
* actor has no identity fields — the worker still emits the standard
|
|
5008
|
+
* /M timestamp via the base writer; only the vendor dictionary is
|
|
5009
|
+
* skipped.
|
|
5010
|
+
*
|
|
5011
|
+
* actor.userId → /UserID, /CreatedBy, /UpdatedBy
|
|
5012
|
+
* actor.groupId → /GroupID
|
|
5013
|
+
*
|
|
5014
|
+
* /SchemaVersion is written whenever any field is written.
|
|
4861
5015
|
*/
|
|
4862
|
-
function
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
if (
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
5016
|
+
function applyEmbedMetadataOnCreate(fn, mem, annotPtr, actor) {
|
|
5017
|
+
if (!actor || !actor.userId && !actor.groupId) return;
|
|
5018
|
+
fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
|
|
5019
|
+
if (actor.userId) {
|
|
5020
|
+
setMetadataString(fn, mem, annotPtr, KEY_USER_ID, actor.userId);
|
|
5021
|
+
setMetadataString(fn, mem, annotPtr, KEY_CREATED_BY, actor.userId);
|
|
5022
|
+
setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
|
|
5023
|
+
}
|
|
5024
|
+
if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
|
|
4870
5025
|
}
|
|
4871
5026
|
/**
|
|
4872
|
-
*
|
|
4873
|
-
*
|
|
5027
|
+
* Apply /EMBD_Metadata refreshes on update. Two fields can change on
|
|
5028
|
+
* update; everything else (UserID, CreatedBy) is immutable.
|
|
5029
|
+
*
|
|
5030
|
+
* actor.userId → /UpdatedBy (refreshed every update where the
|
|
5031
|
+
* caller has an identity)
|
|
5032
|
+
* actor.groupId → /GroupID (reassigned only when the patch
|
|
5033
|
+
* explicitly contains a new groupId; route runs
|
|
5034
|
+
* `checkSetGroup` first)
|
|
5035
|
+
*
|
|
5036
|
+
* If the target annotation has no EMBD_Metadata yet (created before
|
|
5037
|
+
* the cloud started stamping, or by a non-cloud writer), we seed
|
|
5038
|
+
* /SchemaVersion so subsequent readers can find the marker.
|
|
5039
|
+
*
|
|
5040
|
+
* No-op when the actor has neither userId nor groupId.
|
|
4874
5041
|
*/
|
|
4875
|
-
function
|
|
4876
|
-
|
|
5042
|
+
function applyEmbedMetadataOnUpdate(fn, mem, annotPtr, actor) {
|
|
5043
|
+
if (!actor || !actor.userId && !actor.groupId) return;
|
|
5044
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
|
|
5045
|
+
if (actor.userId) setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
|
|
5046
|
+
if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
|
|
5047
|
+
}
|
|
5048
|
+
function setMetadataString(fn, mem, annotPtr, key, value) {
|
|
5049
|
+
const ptr = mem.writeU16String(value);
|
|
5050
|
+
try {
|
|
5051
|
+
fn.EPDFAnnot_SetEmbedMetadataString(annotPtr, key, ptr);
|
|
5052
|
+
} finally {
|
|
5053
|
+
mem.free(ptr);
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5056
|
+
//#endregion
|
|
5057
|
+
//#region src/features/annotations/internal/write/writeAnnotationTransformMetadata.ts
|
|
5058
|
+
/**
|
|
5059
|
+
* Write the EmbedPDF transform keys under /EMBD_Metadata. This is the seam
|
|
5060
|
+
* PDFium's native AP generator reads to bake a ROTATED appearance:
|
|
5061
|
+
*
|
|
5062
|
+
* /EMBD_Metadata <<
|
|
5063
|
+
* /Rotation 45 % degrees, PDF convention (CCW)
|
|
5064
|
+
* /UnrotatedRect [x0 y0 x1 y1] % BOX kinds only — the logical box
|
|
5065
|
+
* >>
|
|
5066
|
+
*
|
|
5067
|
+
* The family split (see the v3 plan):
|
|
5068
|
+
* - BOX kinds (square/circle/free-text): `/Rotation` + `/UnrotatedRect`.
|
|
5069
|
+
* With BOTH present the AP generator emits an `/AP /Matrix` that rotates a
|
|
5070
|
+
* box-sized appearance about the box centre, and `/Rect` is the enclosing
|
|
5071
|
+
* AABB. This is the portable, externally-correct rotation.
|
|
5072
|
+
* - VERTEX kinds (line/polyline/polygon/ink): `/Rotation` ONLY (advisory).
|
|
5073
|
+
* The points are already rotated (they are the visual), so a lone
|
|
5074
|
+
* `/Rotation` is INERT for the AP generator (it ignores `/Rotation` with no
|
|
5075
|
+
* `/UnrotatedRect`) — it just records the applied angle so EmbedPDF can
|
|
5076
|
+
* reconstruct an oriented selection box + offer reset.
|
|
5077
|
+
*
|
|
5078
|
+
* Both fields follow the engine-wide tri-state law ("a patch touches what it
|
|
5079
|
+
* states, preserves what it omits"):
|
|
5080
|
+
* - `undefined` → the key is UNTOUCHED (a rect-only move keeps its rotation).
|
|
5081
|
+
* - `null` or `0` → CLEAR just that key via `EPDFAnnot_ClearEmbedMetadataKey`
|
|
5082
|
+
* (0 ≡ no rotation is the canonical identity, not a sentinel; never clear
|
|
5083
|
+
* the whole dict — identity fields UserID/GroupID/CreatedBy/UpdatedBy must
|
|
5084
|
+
* survive).
|
|
5085
|
+
* - a value → SET the key. Setting a nonzero rotation with no
|
|
5086
|
+
* `/UnrotatedRect` (neither in the patch nor already on the annotation)
|
|
5087
|
+
* is an unsatisfiable state for the AP generator and throws `InvalidArg`.
|
|
5088
|
+
*
|
|
5089
|
+
* MUST run BEFORE `EPDFAnnot_GenerateAppearance` so the bake sees the rotation.
|
|
5090
|
+
* `/SchemaVersion` is seeded (stays 1) if this is the first key in the dict.
|
|
5091
|
+
*/
|
|
5092
|
+
const KEY_ROTATION = "Rotation";
|
|
5093
|
+
const KEY_UNROTATED_RECT = "UnrotatedRect";
|
|
5094
|
+
/** Seed `/SchemaVersion` when we are about to create the dict by writing the
|
|
5095
|
+
* first transform key, so the marker readers look for is always present. */
|
|
5096
|
+
function ensureSchemaVersion(fn, annotPtr) {
|
|
5097
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, "SchemaVersion", 1);
|
|
5098
|
+
}
|
|
5099
|
+
function setRotation(fn, annotPtr, rotation) {
|
|
5100
|
+
ensureSchemaVersion(fn, annotPtr);
|
|
5101
|
+
fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_ROTATION, rotation);
|
|
5102
|
+
}
|
|
5103
|
+
function setUnrotatedRect(fn, mem, annotPtr, rect) {
|
|
5104
|
+
ensureSchemaVersion(fn, annotPtr);
|
|
5105
|
+
const buf = mem.alloc(16);
|
|
5106
|
+
try {
|
|
5107
|
+
mem.poke(buf, "f32", rect.left, 0);
|
|
5108
|
+
mem.poke(buf, "f32", rect.top, 4);
|
|
5109
|
+
mem.poke(buf, "f32", rect.right, 8);
|
|
5110
|
+
mem.poke(buf, "f32", rect.bottom, 12);
|
|
5111
|
+
fn.EPDFAnnot_SetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT, buf);
|
|
5112
|
+
} finally {
|
|
5113
|
+
mem.free(buf);
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
/**
|
|
5117
|
+
* Write transform metadata for a BOX kind (square/circle/free-text/stamp),
|
|
5118
|
+
* tri-state per field: `undefined` never touches the document (safe to call on
|
|
5119
|
+
* every patch), `null`/`0` clears, a value sets. A nonzero rotation with no
|
|
5120
|
+
* unrotated box anywhere (patch or annotation) is unsatisfiable — the AP
|
|
5121
|
+
* generator needs the pair — and throws instead of writing a broken state.
|
|
5122
|
+
*/
|
|
5123
|
+
function writeBoxTransformMetadata(fn, mem, annotPtr, t) {
|
|
5124
|
+
if (t.rotation !== void 0) if (t.rotation !== null && t.rotation !== 0) {
|
|
5125
|
+
if (!(t.unrotatedRect != null || t.unrotatedRect === void 0 && readAnnotationUnrotatedRect(fn, mem, annotPtr) != null)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "rotation requires unrotatedRect: supply the pair together (or rely on an existing /EMBD_Metadata/UnrotatedRect)");
|
|
5126
|
+
setRotation(fn, annotPtr, t.rotation);
|
|
5127
|
+
} else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
|
|
5128
|
+
if (t.unrotatedRect !== void 0) if (t.unrotatedRect !== null) setUnrotatedRect(fn, mem, annotPtr, t.unrotatedRect);
|
|
5129
|
+
else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5130
|
+
}
|
|
5131
|
+
/**
|
|
5132
|
+
* Write the advisory `/Rotation` scalar for a VERTEX kind
|
|
5133
|
+
* (line/polyline/polygon/ink), tri-state: `undefined` never touches the
|
|
5134
|
+
* document, `null`/`0` clears, a value sets. Whenever it does write, any
|
|
5135
|
+
* `/UnrotatedRect` is defensively cleared — that key is an impossible state on
|
|
5136
|
+
* a vertex kind and must never accidentally drive the AP generator.
|
|
5137
|
+
*/
|
|
5138
|
+
function writeVertexTransformMetadata(fn, annotPtr, t) {
|
|
5139
|
+
if (t.rotation === void 0) return;
|
|
5140
|
+
if (t.rotation !== null && t.rotation !== 0) setRotation(fn, annotPtr, t.rotation);
|
|
5141
|
+
else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
|
|
5142
|
+
fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5143
|
+
}
|
|
5144
|
+
//#endregion
|
|
5145
|
+
//#region src/features/annotations/internal/write/writeCaretAnnotation.ts
|
|
5146
|
+
/** Default `/C` colour when a caret draft omits it (engine-wide default mark). */
|
|
5147
|
+
const DEFAULT_CARET_COLOR = {
|
|
5148
|
+
r: 255,
|
|
5149
|
+
g: 0,
|
|
5150
|
+
b: 0
|
|
5151
|
+
};
|
|
5152
|
+
/** Default opacity, set explicitly so reads always round-trip the same value. */
|
|
5153
|
+
const DEFAULT_OPACITY$4 = 1;
|
|
5154
|
+
/**
|
|
5155
|
+
* Apply a caret draft to a freshly-created annotation. Caret carries no
|
|
5156
|
+
* geometry of its own beyond `/Rect`. Order:
|
|
5157
|
+
* 1. base author-metadata (contents/nm/flags)
|
|
5158
|
+
* 2. `/Rect` (required — supplied by the caller; never derived)
|
|
5159
|
+
* 3. `/C` color + `/CA` opacity
|
|
5160
|
+
* 4. `/RD` rectangle differences (optional)
|
|
5161
|
+
*/
|
|
5162
|
+
function applyCaretDraft(fn, mem, annotPtr, draft) {
|
|
5163
|
+
applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
|
|
5164
|
+
setAnnotRect(fn, mem, annotPtr, draft.rect);
|
|
5165
|
+
writeBoxTransformMetadata(fn, mem, annotPtr, {
|
|
5166
|
+
rotation: draft.rotation,
|
|
5167
|
+
unrotatedRect: draft.unrotatedRect
|
|
5168
|
+
});
|
|
5169
|
+
setAnnotColor(fn, annotPtr, draft.color ?? DEFAULT_CARET_COLOR);
|
|
5170
|
+
setAnnotOpacity(fn, annotPtr, draft.opacity ?? DEFAULT_OPACITY$4);
|
|
5171
|
+
if (draft.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(draft.intent));
|
|
5172
|
+
if (draft.rectDifferences != null) setRectangleDifferences(fn, annotPtr, draft.rectDifferences);
|
|
5173
|
+
}
|
|
5174
|
+
/**
|
|
5175
|
+
* Apply a caret patch to an existing annotation. Only present fields are
|
|
5176
|
+
* touched.
|
|
5177
|
+
*/
|
|
5178
|
+
function applyCaretPatch(fn, mem, annotPtr, patch) {
|
|
5179
|
+
applyAnnotationBasePatch(fn, mem, annotPtr, patch);
|
|
5180
|
+
if (patch.rect !== void 0) setAnnotRect(fn, mem, annotPtr, patch.rect);
|
|
5181
|
+
if (patch.rotation !== void 0 || patch.unrotatedRect !== void 0) writeBoxTransformMetadata(fn, mem, annotPtr, {
|
|
5182
|
+
rotation: patch.rotation,
|
|
5183
|
+
unrotatedRect: patch.unrotatedRect
|
|
5184
|
+
});
|
|
5185
|
+
if (patch.color !== void 0) setAnnotColor(fn, annotPtr, patch.color);
|
|
5186
|
+
if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
|
|
5187
|
+
if (patch.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(patch.intent));
|
|
5188
|
+
if (patch.rectDifferences === null) clearRectangleDifferences(fn, annotPtr);
|
|
5189
|
+
else if (patch.rectDifferences !== void 0) setRectangleDifferences(fn, annotPtr, patch.rectDifferences);
|
|
5190
|
+
}
|
|
5191
|
+
/**
|
|
5192
|
+
* Type-narrowing predicate used by the writer registry to pick the caret
|
|
5193
|
+
* writer for a draft/patch's `subtype`.
|
|
5194
|
+
*/
|
|
5195
|
+
function isCaretSubtype(subtype) {
|
|
5196
|
+
return subtype === "caret";
|
|
4877
5197
|
}
|
|
4878
5198
|
//#endregion
|
|
4879
5199
|
//#region src/features/annotations/internal/write/writeFileAttachmentAnnotation.ts
|
|
@@ -5040,177 +5360,6 @@ function applyFilledStylePatch(fn, mem, annotPtr, patch) {
|
|
|
5040
5360
|
applyGeometryStylePatch(fn, mem, annotPtr, patch);
|
|
5041
5361
|
}
|
|
5042
5362
|
//#endregion
|
|
5043
|
-
//#region src/features/annotations/internal/write/writeEmbedMetadata.ts
|
|
5044
|
-
/**
|
|
5045
|
-
* Write the EmbedPDF-namespaced /EMBD_Metadata annotation dictionary.
|
|
5046
|
-
*
|
|
5047
|
-
* This module owns ONLY the vendor-extension dictionary. Standard PDF
|
|
5048
|
-
* base fields (/T, /M, /NM, /Contents) are handled by
|
|
5049
|
-
* `writers/annotations/base.ts` — /EMBD_Metadata is a separate
|
|
5050
|
-
* dictionary nested under the annotation that carries cloud-side
|
|
5051
|
-
* identity bookkeeping which has no PDF-spec equivalent.
|
|
5052
|
-
*
|
|
5053
|
-
* Schema (v1):
|
|
5054
|
-
* /EMBD_Metadata <<
|
|
5055
|
-
* /SchemaVersion 1 % EmbedPDF metadata schema version
|
|
5056
|
-
* /UserID (acme/alice) % identity that created the annotation
|
|
5057
|
-
* /GroupID (engineering) % group the annotation belongs to
|
|
5058
|
-
* /CreatedBy (acme/alice) % same as UserID on create
|
|
5059
|
-
* /UpdatedBy (acme/alice) % last editor; refreshed on every update
|
|
5060
|
-
* >>
|
|
5061
|
-
*
|
|
5062
|
-
* Read side: see `readers/annotations/embed-metadata.ts` (commit 12).
|
|
5063
|
-
*
|
|
5064
|
-
* PDFium bindings used: the `EPDFAnnot_*EmbedMetadata*` extension API
|
|
5065
|
-
* shipped in the EmbedPDF PDFium fork. Values are written as UTF-16LE
|
|
5066
|
-
* strings via the same `mem.writeU16String` pattern the base writer
|
|
5067
|
-
* uses for /T.
|
|
5068
|
-
*/
|
|
5069
|
-
const KEY_SCHEMA_VERSION = "SchemaVersion";
|
|
5070
|
-
const KEY_USER_ID = "UserID";
|
|
5071
|
-
const KEY_GROUP_ID = "GroupID";
|
|
5072
|
-
const KEY_CREATED_BY = "CreatedBy";
|
|
5073
|
-
const KEY_UPDATED_BY = "UpdatedBy";
|
|
5074
|
-
/**
|
|
5075
|
-
* Stamp /EMBD_Metadata on a freshly created annotation. No-op if the
|
|
5076
|
-
* actor has no identity fields — the worker still emits the standard
|
|
5077
|
-
* /M timestamp via the base writer; only the vendor dictionary is
|
|
5078
|
-
* skipped.
|
|
5079
|
-
*
|
|
5080
|
-
* actor.userId → /UserID, /CreatedBy, /UpdatedBy
|
|
5081
|
-
* actor.groupId → /GroupID
|
|
5082
|
-
*
|
|
5083
|
-
* /SchemaVersion is written whenever any field is written.
|
|
5084
|
-
*/
|
|
5085
|
-
function applyEmbedMetadataOnCreate(fn, mem, annotPtr, actor) {
|
|
5086
|
-
if (!actor || !actor.userId && !actor.groupId) return;
|
|
5087
|
-
fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
|
|
5088
|
-
if (actor.userId) {
|
|
5089
|
-
setMetadataString(fn, mem, annotPtr, KEY_USER_ID, actor.userId);
|
|
5090
|
-
setMetadataString(fn, mem, annotPtr, KEY_CREATED_BY, actor.userId);
|
|
5091
|
-
setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
|
|
5092
|
-
}
|
|
5093
|
-
if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
|
|
5094
|
-
}
|
|
5095
|
-
/**
|
|
5096
|
-
* Apply /EMBD_Metadata refreshes on update. Two fields can change on
|
|
5097
|
-
* update; everything else (UserID, CreatedBy) is immutable.
|
|
5098
|
-
*
|
|
5099
|
-
* actor.userId → /UpdatedBy (refreshed every update where the
|
|
5100
|
-
* caller has an identity)
|
|
5101
|
-
* actor.groupId → /GroupID (reassigned only when the patch
|
|
5102
|
-
* explicitly contains a new groupId; route runs
|
|
5103
|
-
* `checkSetGroup` first)
|
|
5104
|
-
*
|
|
5105
|
-
* If the target annotation has no EMBD_Metadata yet (created before
|
|
5106
|
-
* the cloud started stamping, or by a non-cloud writer), we seed
|
|
5107
|
-
* /SchemaVersion so subsequent readers can find the marker.
|
|
5108
|
-
*
|
|
5109
|
-
* No-op when the actor has neither userId nor groupId.
|
|
5110
|
-
*/
|
|
5111
|
-
function applyEmbedMetadataOnUpdate(fn, mem, annotPtr, actor) {
|
|
5112
|
-
if (!actor || !actor.userId && !actor.groupId) return;
|
|
5113
|
-
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
|
|
5114
|
-
if (actor.userId) setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
|
|
5115
|
-
if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
|
|
5116
|
-
}
|
|
5117
|
-
function setMetadataString(fn, mem, annotPtr, key, value) {
|
|
5118
|
-
const ptr = mem.writeU16String(value);
|
|
5119
|
-
try {
|
|
5120
|
-
fn.EPDFAnnot_SetEmbedMetadataString(annotPtr, key, ptr);
|
|
5121
|
-
} finally {
|
|
5122
|
-
mem.free(ptr);
|
|
5123
|
-
}
|
|
5124
|
-
}
|
|
5125
|
-
//#endregion
|
|
5126
|
-
//#region src/features/annotations/internal/write/writeAnnotationTransformMetadata.ts
|
|
5127
|
-
/**
|
|
5128
|
-
* Write the EmbedPDF transform keys under /EMBD_Metadata. This is the seam
|
|
5129
|
-
* PDFium's native AP generator reads to bake a ROTATED appearance:
|
|
5130
|
-
*
|
|
5131
|
-
* /EMBD_Metadata <<
|
|
5132
|
-
* /Rotation 45 % degrees, PDF convention (CCW)
|
|
5133
|
-
* /UnrotatedRect [x0 y0 x1 y1] % BOX kinds only — the logical box
|
|
5134
|
-
* >>
|
|
5135
|
-
*
|
|
5136
|
-
* The family split (see the v3 plan):
|
|
5137
|
-
* - BOX kinds (square/circle/free-text): `/Rotation` + `/UnrotatedRect`.
|
|
5138
|
-
* With BOTH present the AP generator emits an `/AP /Matrix` that rotates a
|
|
5139
|
-
* box-sized appearance about the box centre, and `/Rect` is the enclosing
|
|
5140
|
-
* AABB. This is the portable, externally-correct rotation.
|
|
5141
|
-
* - VERTEX kinds (line/polyline/polygon/ink): `/Rotation` ONLY (advisory).
|
|
5142
|
-
* The points are already rotated (they are the visual), so a lone
|
|
5143
|
-
* `/Rotation` is INERT for the AP generator (it ignores `/Rotation` with no
|
|
5144
|
-
* `/UnrotatedRect`) — it just records the applied angle so EmbedPDF can
|
|
5145
|
-
* reconstruct an oriented selection box + offer reset.
|
|
5146
|
-
*
|
|
5147
|
-
* Both fields follow the engine-wide tri-state law ("a patch touches what it
|
|
5148
|
-
* states, preserves what it omits"):
|
|
5149
|
-
* - `undefined` → the key is UNTOUCHED (a rect-only move keeps its rotation).
|
|
5150
|
-
* - `null` or `0` → CLEAR just that key via `EPDFAnnot_ClearEmbedMetadataKey`
|
|
5151
|
-
* (0 ≡ no rotation is the canonical identity, not a sentinel; never clear
|
|
5152
|
-
* the whole dict — identity fields UserID/GroupID/CreatedBy/UpdatedBy must
|
|
5153
|
-
* survive).
|
|
5154
|
-
* - a value → SET the key. Setting a nonzero rotation with no
|
|
5155
|
-
* `/UnrotatedRect` (neither in the patch nor already on the annotation)
|
|
5156
|
-
* is an unsatisfiable state for the AP generator and throws `InvalidArg`.
|
|
5157
|
-
*
|
|
5158
|
-
* MUST run BEFORE `EPDFAnnot_GenerateAppearance` so the bake sees the rotation.
|
|
5159
|
-
* `/SchemaVersion` is seeded (stays 1) if this is the first key in the dict.
|
|
5160
|
-
*/
|
|
5161
|
-
const KEY_ROTATION = "Rotation";
|
|
5162
|
-
const KEY_UNROTATED_RECT = "UnrotatedRect";
|
|
5163
|
-
/** Seed `/SchemaVersion` when we are about to create the dict by writing the
|
|
5164
|
-
* first transform key, so the marker readers look for is always present. */
|
|
5165
|
-
function ensureSchemaVersion(fn, annotPtr) {
|
|
5166
|
-
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, "SchemaVersion", 1);
|
|
5167
|
-
}
|
|
5168
|
-
function setRotation(fn, annotPtr, rotation) {
|
|
5169
|
-
ensureSchemaVersion(fn, annotPtr);
|
|
5170
|
-
fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_ROTATION, rotation);
|
|
5171
|
-
}
|
|
5172
|
-
function setUnrotatedRect(fn, mem, annotPtr, rect) {
|
|
5173
|
-
ensureSchemaVersion(fn, annotPtr);
|
|
5174
|
-
const buf = mem.alloc(16);
|
|
5175
|
-
try {
|
|
5176
|
-
mem.poke(buf, "f32", rect.left, 0);
|
|
5177
|
-
mem.poke(buf, "f32", rect.top, 4);
|
|
5178
|
-
mem.poke(buf, "f32", rect.right, 8);
|
|
5179
|
-
mem.poke(buf, "f32", rect.bottom, 12);
|
|
5180
|
-
fn.EPDFAnnot_SetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT, buf);
|
|
5181
|
-
} finally {
|
|
5182
|
-
mem.free(buf);
|
|
5183
|
-
}
|
|
5184
|
-
}
|
|
5185
|
-
/**
|
|
5186
|
-
* Write transform metadata for a BOX kind (square/circle/free-text/stamp),
|
|
5187
|
-
* tri-state per field: `undefined` never touches the document (safe to call on
|
|
5188
|
-
* every patch), `null`/`0` clears, a value sets. A nonzero rotation with no
|
|
5189
|
-
* unrotated box anywhere (patch or annotation) is unsatisfiable — the AP
|
|
5190
|
-
* generator needs the pair — and throws instead of writing a broken state.
|
|
5191
|
-
*/
|
|
5192
|
-
function writeBoxTransformMetadata(fn, mem, annotPtr, t) {
|
|
5193
|
-
if (t.rotation !== void 0) if (t.rotation !== null && t.rotation !== 0) {
|
|
5194
|
-
if (!(t.unrotatedRect != null || t.unrotatedRect === void 0 && readAnnotationUnrotatedRect(fn, mem, annotPtr) != null)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "rotation requires unrotatedRect: supply the pair together (or rely on an existing /EMBD_Metadata/UnrotatedRect)");
|
|
5195
|
-
setRotation(fn, annotPtr, t.rotation);
|
|
5196
|
-
} else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
|
|
5197
|
-
if (t.unrotatedRect !== void 0) if (t.unrotatedRect !== null) setUnrotatedRect(fn, mem, annotPtr, t.unrotatedRect);
|
|
5198
|
-
else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5199
|
-
}
|
|
5200
|
-
/**
|
|
5201
|
-
* Write the advisory `/Rotation` scalar for a VERTEX kind
|
|
5202
|
-
* (line/polyline/polygon/ink), tri-state: `undefined` never touches the
|
|
5203
|
-
* document, `null`/`0` clears, a value sets. Whenever it does write, any
|
|
5204
|
-
* `/UnrotatedRect` is defensively cleared — that key is an impossible state on
|
|
5205
|
-
* a vertex kind and must never accidentally drive the AP generator.
|
|
5206
|
-
*/
|
|
5207
|
-
function writeVertexTransformMetadata(fn, annotPtr, t) {
|
|
5208
|
-
if (t.rotation === void 0) return;
|
|
5209
|
-
if (t.rotation !== null && t.rotation !== 0) setRotation(fn, annotPtr, t.rotation);
|
|
5210
|
-
else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
|
|
5211
|
-
fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5212
|
-
}
|
|
5213
|
-
//#endregion
|
|
5214
5363
|
//#region src/features/annotations/internal/write/writeFreeTextAnnotation.ts
|
|
5215
5364
|
/**
|
|
5216
5365
|
* Default `/DA` colour for free text: black (border + default text). Unlike
|
|
@@ -5980,13 +6129,18 @@ const DEFAULT_OPACITY = 1;
|
|
|
5980
6129
|
* Apply a text (sticky-note) draft. The visual is entirely generator-owned:
|
|
5981
6130
|
* the mutator's closing `regenerateAppearance` bakes the 20×20 note icon
|
|
5982
6131
|
* from `/C` + `/Name` (GenerateTextAP), so this writer only records state.
|
|
6132
|
+
* `/State` + `/StateModel` are dictionary-only (ISO 32000 §12.5.6.3) and
|
|
6133
|
+
* never reach the generator.
|
|
5983
6134
|
*/
|
|
5984
6135
|
function applyTextDraft(fn, mem, annotPtr, draft) {
|
|
6136
|
+
if (draft.state !== void 0 && draft.stateModel === void 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "text draft: state requires stateModel (ISO 32000 §12.5.6.3)");
|
|
5985
6137
|
applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
|
|
5986
6138
|
setAnnotRect(fn, mem, annotPtr, draft.rect);
|
|
5987
6139
|
setAnnotColor(fn, annotPtr, draft.color ?? DEFAULT_NOTE_COLOR);
|
|
5988
6140
|
setAnnotOpacity(fn, annotPtr, draft.opacity ?? DEFAULT_OPACITY);
|
|
5989
6141
|
setNoteIcon(fn, annotPtr, draft.icon ?? "note");
|
|
6142
|
+
if (draft.stateModel !== void 0) writeAnnotString(fn, mem, annotPtr, "StateModel", stateModelToPdf(draft.stateModel));
|
|
6143
|
+
if (draft.state !== void 0) writeAnnotString(fn, mem, annotPtr, "State", stateToPdf(draft.state));
|
|
5990
6144
|
}
|
|
5991
6145
|
function applyTextPatch(fn, mem, annotPtr, patch) {
|
|
5992
6146
|
applyAnnotationBasePatch(fn, mem, annotPtr, patch);
|
|
@@ -5994,6 +6148,8 @@ function applyTextPatch(fn, mem, annotPtr, patch) {
|
|
|
5994
6148
|
if (patch.color !== void 0) setAnnotColor(fn, annotPtr, patch.color);
|
|
5995
6149
|
if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
|
|
5996
6150
|
if (patch.icon !== void 0) setNoteIcon(fn, annotPtr, patch.icon);
|
|
6151
|
+
if (patch.stateModel !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "StateModel", patch.stateModel === null ? null : stateModelToPdf(patch.stateModel));
|
|
6152
|
+
if (patch.state !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "State", patch.state === null ? null : stateToPdf(patch.state));
|
|
5997
6153
|
}
|
|
5998
6154
|
function isTextSubtype(subtype) {
|
|
5999
6155
|
return subtype === "text";
|
|
@@ -6971,17 +7127,35 @@ var RedactionApplier = class {
|
|
|
6971
7127
|
}
|
|
6972
7128
|
applyAllOnPage(pagePtr) {
|
|
6973
7129
|
const { fn, mem } = this.runtime;
|
|
7130
|
+
const hasRedactions = this.pageHasRedactAnnotations(pagePtr);
|
|
6974
7131
|
return withScratch(mem, 4, (countPtr) => {
|
|
6975
|
-
if (!fn.EPDFPage_ApplyRedactions(pagePtr, countPtr))
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
7132
|
+
if (!fn.EPDFPage_ApplyRedactions(pagePtr, countPtr)) {
|
|
7133
|
+
if (hasRedactions) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "native page redaction apply failed");
|
|
7134
|
+
return {
|
|
7135
|
+
status: "unchanged",
|
|
7136
|
+
removed: 0
|
|
7137
|
+
};
|
|
7138
|
+
}
|
|
6979
7139
|
return {
|
|
6980
7140
|
status: "applied",
|
|
6981
7141
|
removed: readI32(mem, countPtr) >>> 0
|
|
6982
7142
|
};
|
|
6983
7143
|
});
|
|
6984
7144
|
}
|
|
7145
|
+
pageHasRedactAnnotations(pagePtr) {
|
|
7146
|
+
const { fn } = this.runtime;
|
|
7147
|
+
const count = fn.FPDFPage_GetAnnotCount(pagePtr);
|
|
7148
|
+
for (let index = 0; index < count; index++) {
|
|
7149
|
+
const annotPtr = fn.FPDFPage_GetAnnot(pagePtr, index);
|
|
7150
|
+
if (!annotPtr) continue;
|
|
7151
|
+
try {
|
|
7152
|
+
if ((0, _embedpdf_engine_core_runtime.subtypeFromCode)(fn.FPDFAnnot_GetSubtype(annotPtr)) === "redact") return true;
|
|
7153
|
+
} finally {
|
|
7154
|
+
fn.FPDFPage_CloseAnnot(annotPtr);
|
|
7155
|
+
}
|
|
7156
|
+
}
|
|
7157
|
+
return false;
|
|
7158
|
+
}
|
|
6985
7159
|
applyRefsOnPage(pagePtr, refs) {
|
|
6986
7160
|
const { fn, mem } = this.runtime;
|
|
6987
7161
|
let removed = 0;
|
|
@@ -7007,23 +7181,26 @@ var RedactionApplier = class {
|
|
|
7007
7181
|
//#region src/features/text/PageTextReader.ts
|
|
7008
7182
|
/**
|
|
7009
7183
|
* Per-page slow-path text reader. Acquires a `pagePtr` from the
|
|
7010
|
-
* `PagePtrPool`, opens a PDFium text page (`FPDFText_LoadPage`),
|
|
7011
|
-
*
|
|
7012
|
-
*
|
|
7184
|
+
* `PagePtrPool`, opens a PDFium text page (`FPDFText_LoadPage`), extracts
|
|
7185
|
+
* the page text, and releases everything in reverse order.
|
|
7186
|
+
*
|
|
7187
|
+
* Index spaces (see engine-core `text/charmap.ts`): `charCount` is the
|
|
7188
|
+
* CHARACTER space (`FPDFText_CountChars` — the space geometry runs tile);
|
|
7189
|
+
* `text` is the TEXT projection. The two diverge when a character
|
|
7190
|
+
* contributes zero text units (non-printing) or two (supplementary plane);
|
|
7191
|
+
* `charMap` anchors encode exactly those deviations.
|
|
7013
7192
|
*
|
|
7014
|
-
*
|
|
7015
|
-
*
|
|
7016
|
-
*
|
|
7017
|
-
* trailing NUL terminator).
|
|
7018
|
-
* - `mem.readU16String` walks the buffer up to the NUL and decodes
|
|
7019
|
-
* to a JS string, faithfully preserving surrogate pairs.
|
|
7193
|
+
* Both come from the fork's batched extension calls, which share one
|
|
7194
|
+
* encoding authority (`FX_UTF16Encode`) so text and map can never
|
|
7195
|
+
* disagree:
|
|
7020
7196
|
*
|
|
7021
|
-
*
|
|
7022
|
-
*
|
|
7023
|
-
*
|
|
7024
|
-
*
|
|
7025
|
-
*
|
|
7026
|
-
*
|
|
7197
|
+
* - `EPDFText_GetTextFull` — true UTF-16LE extraction; supplementary
|
|
7198
|
+
* characters arrive as surrogate pairs instead of being silently
|
|
7199
|
+
* dropped by `FPDFText_GetText`'s UCS-2 encoder. Called once with an
|
|
7200
|
+
* upper-bound buffer (2 units per character + NUL), so no measure
|
|
7201
|
+
* round-trip.
|
|
7202
|
+
* - `EPDFText_GetCharToTextMap` — the anchors, measure-then-fill. Zero
|
|
7203
|
+
* anchors is the common (identity) page and skips the fill call.
|
|
7027
7204
|
*/
|
|
7028
7205
|
var PageTextReader = class {
|
|
7029
7206
|
constructor(runtime, session) {
|
|
@@ -7032,7 +7209,7 @@ var PageTextReader = class {
|
|
|
7032
7209
|
}
|
|
7033
7210
|
read(pageObjectNumber, signal) {
|
|
7034
7211
|
throwIfAborted(signal);
|
|
7035
|
-
const { fn
|
|
7212
|
+
const { fn } = this.runtime;
|
|
7036
7213
|
const pool = this.session.pagePool();
|
|
7037
7214
|
const pagePtr = pool.acquire(pageObjectNumber);
|
|
7038
7215
|
try {
|
|
@@ -7041,10 +7218,18 @@ var PageTextReader = class {
|
|
|
7041
7218
|
if (!textPagePtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `FPDFText_LoadPage returned null for page object ${pageObjectNumber}`);
|
|
7042
7219
|
try {
|
|
7043
7220
|
throwIfAborted(signal);
|
|
7044
|
-
const charCount = fn.FPDFText_CountChars(textPagePtr);
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7221
|
+
const charCount = Math.max(fn.FPDFText_CountChars(textPagePtr), 0);
|
|
7222
|
+
const text = readTextFull(this.runtime, textPagePtr, charCount);
|
|
7223
|
+
const charMap = readCharMap(this.runtime, textPagePtr, pageObjectNumber);
|
|
7224
|
+
const violation = (0, _embedpdf_engine_core_runtime.charMapViolation)(charCount, text.length, charMap);
|
|
7225
|
+
if (violation !== null) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `char map invariant violated for page object ${pageObjectNumber}: ${violation}`);
|
|
7226
|
+
return charMap.length > 0 ? {
|
|
7227
|
+
text,
|
|
7228
|
+
charCount,
|
|
7229
|
+
charMap
|
|
7230
|
+
} : {
|
|
7231
|
+
text,
|
|
7232
|
+
charCount
|
|
7048
7233
|
};
|
|
7049
7234
|
} finally {
|
|
7050
7235
|
fn.FPDFText_ClosePage(textPagePtr);
|
|
@@ -7054,21 +7239,65 @@ var PageTextReader = class {
|
|
|
7054
7239
|
}
|
|
7055
7240
|
}
|
|
7056
7241
|
};
|
|
7057
|
-
|
|
7242
|
+
/**
|
|
7243
|
+
* Full-fidelity page text in one call: the buffer is sized at the maximum
|
|
7244
|
+
* possible expansion (every character a surrogate pair, plus the NUL), so
|
|
7245
|
+
* the returned requirement always fits and the measure round-trip is
|
|
7246
|
+
* skipped. The return value counts UTF-16 units INCLUDING the terminator.
|
|
7247
|
+
*/
|
|
7248
|
+
function readTextFull(runtime, textPagePtr, charCount) {
|
|
7058
7249
|
if (charCount <= 0) return "";
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7250
|
+
const { fn, mem } = runtime;
|
|
7251
|
+
const bufUnits = charCount * 2 + 1;
|
|
7252
|
+
return withScratch(mem, bufUnits * 2, (buf) => {
|
|
7253
|
+
const written = fn.EPDFText_GetTextFull(textPagePtr, buf, bufUnits);
|
|
7254
|
+
if (written <= 1) return "";
|
|
7255
|
+
return decodeUtf16Le(mem.readBytes(buf, (written - 1) * 2));
|
|
7256
|
+
});
|
|
7257
|
+
}
|
|
7258
|
+
function decodeUtf16Le(bytes) {
|
|
7259
|
+
const units = new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength >> 1);
|
|
7260
|
+
let out = "";
|
|
7261
|
+
const CHUNK = 8192;
|
|
7262
|
+
for (let i = 0; i < units.length; i += CHUNK) out += String.fromCharCode(...units.subarray(i, Math.min(i + CHUNK, units.length)));
|
|
7263
|
+
return out;
|
|
7264
|
+
}
|
|
7265
|
+
/** Anchor struct layout: two little-endian i32s (see EPDF_CHAR_MAP_ANCHOR). */
|
|
7266
|
+
const ANCHOR_BYTES = 8;
|
|
7267
|
+
function readCharMap(runtime, textPagePtr, pageObjectNumber) {
|
|
7268
|
+
const { fn, mem } = runtime;
|
|
7269
|
+
const count = fn.EPDFText_GetCharToTextMap(textPagePtr, _embedpdf_engine_runtime.NULL_PTR, 0);
|
|
7270
|
+
if (count < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `EPDFText_GetCharToTextMap failed for page object ${pageObjectNumber}`);
|
|
7271
|
+
if (count === 0) return [];
|
|
7272
|
+
return withScratch(mem, count * ANCHOR_BYTES, (buf) => {
|
|
7273
|
+
fn.EPDFText_GetCharToTextMap(textPagePtr, buf, count);
|
|
7274
|
+
const bytes = mem.readBytes(buf, count * ANCHOR_BYTES);
|
|
7275
|
+
const words = new Int32Array(bytes.buffer, bytes.byteOffset, count * 2);
|
|
7276
|
+
const anchors = new Array(count);
|
|
7277
|
+
for (let i = 0; i < count; i++) anchors[i] = [words[i * 2], words[i * 2 + 1]];
|
|
7278
|
+
return anchors;
|
|
7062
7279
|
});
|
|
7063
7280
|
}
|
|
7064
7281
|
//#endregion
|
|
7065
7282
|
//#region src/features/geometry/PageGeometryReader.ts
|
|
7066
7283
|
/**
|
|
7284
|
+
* Mid-object orientation drift that forces a run split (~0.5°). Real content
|
|
7285
|
+
* keeps one char matrix per text object; this guards the exotic cases so a
|
|
7286
|
+
* run's `rotation` is always representative of every glyph in it.
|
|
7287
|
+
*/
|
|
7288
|
+
const ROTATION_SPLIT_TOLERANCE = .0087;
|
|
7289
|
+
/**
|
|
7067
7290
|
* Geometry-only text layout reader.
|
|
7068
7291
|
*
|
|
7069
7292
|
* Emits geometry in PDF user space (y-up edges) — the canonical engine
|
|
7070
7293
|
* geometry. The viewer converts to content/view space via the page geometry
|
|
7071
7294
|
* matrix; this reader applies NO Y-flip or device transform.
|
|
7295
|
+
*
|
|
7296
|
+
* One `EPDFText_GetCharGeometry` call per glyph supplies boxes, oriented
|
|
7297
|
+
* cells, and flags together; `buildRunsFromRawGlyphs` then groups glyphs
|
|
7298
|
+
* into the upright/rotated run union. Upright runs are byte-identical to the
|
|
7299
|
+
* pre-orientation reader (quads the native layer offers are deliberately
|
|
7300
|
+
* dropped — the dominant case never pays for orientation).
|
|
7072
7301
|
*/
|
|
7073
7302
|
var PageGeometryReader = class {
|
|
7074
7303
|
constructor(runtime, session) {
|
|
@@ -7077,7 +7306,7 @@ var PageGeometryReader = class {
|
|
|
7077
7306
|
}
|
|
7078
7307
|
read(pageObjectNumber, signal) {
|
|
7079
7308
|
throwIfAborted(signal);
|
|
7080
|
-
const { fn } = this.runtime;
|
|
7309
|
+
const { fn, mem } = this.runtime;
|
|
7081
7310
|
const pool = this.session.pagePool();
|
|
7082
7311
|
const pagePtr = pool.acquire(pageObjectNumber);
|
|
7083
7312
|
try {
|
|
@@ -7087,12 +7316,27 @@ var PageGeometryReader = class {
|
|
|
7087
7316
|
try {
|
|
7088
7317
|
throwIfAborted(signal);
|
|
7089
7318
|
const glyphCount = Math.max(fn.FPDFText_CountChars(textPagePtr), 0);
|
|
7090
|
-
const
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7319
|
+
const records = [];
|
|
7320
|
+
const geometryPtr = mem.alloc(EPDF_CHAR_GEOMETRY_LAYOUT.bytes);
|
|
7321
|
+
try {
|
|
7322
|
+
let prevObjectKey = null;
|
|
7323
|
+
for (let i = 0; i < glyphCount; i++) {
|
|
7324
|
+
throwIfAborted(signal);
|
|
7325
|
+
const objectKey = fn.FPDFText_GetTextObject(textPagePtr, i);
|
|
7326
|
+
const record = {
|
|
7327
|
+
objectKey,
|
|
7328
|
+
...this.readGlyphRaw(textPagePtr, i, geometryPtr)
|
|
7329
|
+
};
|
|
7330
|
+
if (objectKey !== prevObjectKey) {
|
|
7331
|
+
record.fontSize = fn.FPDFText_GetFontSize(textPagePtr, i);
|
|
7332
|
+
prevObjectKey = objectKey;
|
|
7333
|
+
}
|
|
7334
|
+
records.push(record);
|
|
7335
|
+
}
|
|
7336
|
+
} finally {
|
|
7337
|
+
mem.free(geometryPtr);
|
|
7094
7338
|
}
|
|
7095
|
-
return { runs:
|
|
7339
|
+
return { runs: buildRunsFromRawGlyphs(records) };
|
|
7096
7340
|
} finally {
|
|
7097
7341
|
fn.FPDFText_ClosePage(textPagePtr);
|
|
7098
7342
|
}
|
|
@@ -7100,96 +7344,206 @@ var PageGeometryReader = class {
|
|
|
7100
7344
|
pool.release(pageObjectNumber);
|
|
7101
7345
|
}
|
|
7102
7346
|
}
|
|
7103
|
-
|
|
7104
|
-
const { fn } = this.runtime;
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7347
|
+
readGlyphRaw(textPagePtr, charIndex, geometryPtr) {
|
|
7348
|
+
const { fn, mem } = this.runtime;
|
|
7349
|
+
if (!fn.EPDFText_GetCharGeometry(textPagePtr, charIndex, geometryPtr)) return emptyRawGlyph();
|
|
7350
|
+
const { offsets, flags: bits } = EPDF_CHAR_GEOMETRY_LAYOUT;
|
|
7351
|
+
const native = readI32(mem, geometryPtr, offsets.flags) >>> 0;
|
|
7352
|
+
if (native & bits.empty) return emptyRawGlyph();
|
|
7353
|
+
const upright = Boolean(native & bits.upright);
|
|
7354
|
+
const raw = {
|
|
7355
|
+
flags: native & bits.space ? 1 : 0,
|
|
7356
|
+
looseBox: (0, _embedpdf_engine_core_runtime.normalizePdfRect)(readRectF(mem, geometryPtr, offsets.looseBox)),
|
|
7357
|
+
upright,
|
|
7358
|
+
rotation: 0,
|
|
7359
|
+
ascentFlip: false
|
|
7360
|
+
};
|
|
7361
|
+
if (native & bits.hasTightBox) raw.tightBox = (0, _embedpdf_engine_core_runtime.normalizePdfRect)(readRectF(mem, geometryPtr, offsets.tightBox));
|
|
7362
|
+
if (native & bits.hasLooseQuad) {
|
|
7363
|
+
raw.looseQuad = readQuad(mem, geometryPtr, offsets.looseQuad);
|
|
7364
|
+
if (native & bits.hasTightQuad) raw.tightQuad = readQuad(mem, geometryPtr, offsets.tightQuad);
|
|
7365
|
+
if (!upright) {
|
|
7366
|
+
const a = readF32(mem, geometryPtr, offsets.matrix);
|
|
7367
|
+
const b = readF32(mem, geometryPtr, offsets.matrix + 4);
|
|
7368
|
+
const c = readF32(mem, geometryPtr, offsets.matrix + 8);
|
|
7369
|
+
const d = readF32(mem, geometryPtr, offsets.matrix + 12);
|
|
7370
|
+
raw.rotation = Math.atan2(b, a);
|
|
7371
|
+
raw.ascentFlip = a * d - b * c < 0;
|
|
7122
7372
|
}
|
|
7123
|
-
current.glyphs.push(toSlimGlyph(glyph));
|
|
7124
|
-
if (glyph.isEmpty) continue;
|
|
7125
|
-
bounds.left = Math.min(bounds.left, glyph.looseBox.left);
|
|
7126
|
-
bounds.bottom = Math.min(bounds.bottom, glyph.looseBox.bottom);
|
|
7127
|
-
bounds.right = Math.max(bounds.right, glyph.looseBox.right);
|
|
7128
|
-
bounds.top = Math.max(bounds.top, glyph.looseBox.top);
|
|
7129
|
-
current.rect = { ...bounds };
|
|
7130
7373
|
}
|
|
7131
|
-
return
|
|
7374
|
+
return raw;
|
|
7132
7375
|
}
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7376
|
+
};
|
|
7377
|
+
/**
|
|
7378
|
+
* Group per-glyph records into the run union. Pure — exported for tests.
|
|
7379
|
+
*
|
|
7380
|
+
* Runs split on text-object change (the legacy rule) and on orientation
|
|
7381
|
+
* change between classifiable glyphs (θ drift / mixed orientation). A run's
|
|
7382
|
+
* variant is decided by its FIRST classifiable glyph:
|
|
7383
|
+
* - empty glyphs never classify (they adopt the run's variant);
|
|
7384
|
+
* - real glyphs WITHOUT an oriented cell (singular matrix, synthesized
|
|
7385
|
+
* /ActualText pieces) classify as upright — box-only data degrades to
|
|
7386
|
+
* exactly the legacy behavior;
|
|
7387
|
+
* - real glyphs with a non-upright matrix classify as rotated.
|
|
7388
|
+
* Runs that never see a classifiable glyph emit as upright (degenerate,
|
|
7389
|
+
* legacy behavior).
|
|
7390
|
+
*/
|
|
7391
|
+
function buildRunsFromRawGlyphs(records) {
|
|
7392
|
+
const runs = [];
|
|
7393
|
+
let buffer = [];
|
|
7394
|
+
let charStart = 0;
|
|
7395
|
+
let fontSize;
|
|
7396
|
+
let objectKey = null;
|
|
7397
|
+
let cls;
|
|
7398
|
+
const flush = (nextStart) => {
|
|
7399
|
+
if (buffer.length > 0) {
|
|
7400
|
+
runs.push(materializeRun(buffer, charStart, fontSize, cls));
|
|
7401
|
+
buffer = [];
|
|
7402
|
+
cls = void 0;
|
|
7403
|
+
}
|
|
7404
|
+
charStart = nextStart;
|
|
7405
|
+
};
|
|
7406
|
+
for (let i = 0; i < records.length; i++) {
|
|
7407
|
+
const record = records[i];
|
|
7408
|
+
if (record.objectKey !== objectKey) {
|
|
7409
|
+
flush(i);
|
|
7410
|
+
objectKey = record.objectKey;
|
|
7411
|
+
fontSize = record.fontSize;
|
|
7412
|
+
}
|
|
7413
|
+
const recordClass = classOf(record);
|
|
7414
|
+
if (recordClass) {
|
|
7415
|
+
if (cls && !sameClass(cls, recordClass)) flush(i);
|
|
7416
|
+
if (!cls) cls = recordClass;
|
|
7174
7417
|
}
|
|
7418
|
+
buffer.push(record);
|
|
7419
|
+
}
|
|
7420
|
+
flush(records.length);
|
|
7421
|
+
return runs;
|
|
7422
|
+
}
|
|
7423
|
+
function classOf(record) {
|
|
7424
|
+
if (record.flags & 2) return void 0;
|
|
7425
|
+
if (record.looseQuad && !record.upright) return {
|
|
7426
|
+
upright: false,
|
|
7427
|
+
rotation: record.rotation,
|
|
7428
|
+
ascentFlip: record.ascentFlip
|
|
7429
|
+
};
|
|
7430
|
+
return { upright: true };
|
|
7431
|
+
}
|
|
7432
|
+
function sameClass(a, b) {
|
|
7433
|
+
if (a.upright || b.upright) return a.upright === b.upright;
|
|
7434
|
+
if (a.ascentFlip !== b.ascentFlip) return false;
|
|
7435
|
+
const delta = a.rotation - b.rotation;
|
|
7436
|
+
return Math.abs(Math.atan2(Math.sin(delta), Math.cos(delta))) <= ROTATION_SPLIT_TOLERANCE;
|
|
7437
|
+
}
|
|
7438
|
+
function materializeRun(buffer, charStart, fontSize, cls) {
|
|
7439
|
+
if (cls && !cls.upright) {
|
|
7440
|
+
const glyphs = buffer.map((g) => g.looseQuad ? {
|
|
7441
|
+
looseQuad: g.looseQuad,
|
|
7442
|
+
flags: g.flags,
|
|
7443
|
+
...g.tightQuad ? { tightQuad: g.tightQuad } : {}
|
|
7444
|
+
} : {
|
|
7445
|
+
looseQuad: zeroQuad(),
|
|
7446
|
+
flags: g.flags
|
|
7447
|
+
});
|
|
7448
|
+
return {
|
|
7449
|
+
rect: runBounds(buffer, (g) => g.looseQuad ? (0, _embedpdf_engine_core_runtime.pdfQuadBounds)(g.looseQuad) : ZERO_RECT),
|
|
7450
|
+
charStart,
|
|
7451
|
+
glyphs,
|
|
7452
|
+
rotation: cls.rotation,
|
|
7453
|
+
ascentFlip: cls.ascentFlip,
|
|
7454
|
+
...fontSize !== void 0 ? { fontSize } : {}
|
|
7455
|
+
};
|
|
7456
|
+
}
|
|
7457
|
+
const glyphs = buffer.map((g) => ({
|
|
7458
|
+
looseBox: g.looseBox,
|
|
7459
|
+
flags: g.flags,
|
|
7460
|
+
...g.tightBox ? { tightBox: g.tightBox } : {}
|
|
7461
|
+
}));
|
|
7462
|
+
return {
|
|
7463
|
+
rect: runBounds(buffer, (g) => g.looseBox),
|
|
7464
|
+
charStart,
|
|
7465
|
+
glyphs,
|
|
7466
|
+
...fontSize !== void 0 ? { fontSize } : {}
|
|
7467
|
+
};
|
|
7468
|
+
}
|
|
7469
|
+
/** Legacy run-rect algorithm: seed from the first glyph, expand on non-empty. */
|
|
7470
|
+
function runBounds(buffer, boundsOf) {
|
|
7471
|
+
const seed = boundsOf(buffer[0]);
|
|
7472
|
+
const rect = {
|
|
7473
|
+
left: seed.left,
|
|
7474
|
+
bottom: seed.bottom,
|
|
7475
|
+
right: seed.right,
|
|
7476
|
+
top: seed.top
|
|
7477
|
+
};
|
|
7478
|
+
for (const g of buffer) {
|
|
7479
|
+
if (g.flags & 2) continue;
|
|
7480
|
+
const b = boundsOf(g);
|
|
7481
|
+
rect.left = Math.min(rect.left, b.left);
|
|
7482
|
+
rect.bottom = Math.min(rect.bottom, b.bottom);
|
|
7483
|
+
rect.right = Math.max(rect.right, b.right);
|
|
7484
|
+
rect.top = Math.max(rect.top, b.top);
|
|
7175
7485
|
}
|
|
7486
|
+
return rect;
|
|
7487
|
+
}
|
|
7488
|
+
const ZERO_RECT = {
|
|
7489
|
+
left: 0,
|
|
7490
|
+
bottom: 0,
|
|
7491
|
+
right: 0,
|
|
7492
|
+
top: 0
|
|
7176
7493
|
};
|
|
7177
|
-
function
|
|
7494
|
+
function zeroQuad() {
|
|
7495
|
+
return {
|
|
7496
|
+
p1: {
|
|
7497
|
+
x: 0,
|
|
7498
|
+
y: 0
|
|
7499
|
+
},
|
|
7500
|
+
p2: {
|
|
7501
|
+
x: 0,
|
|
7502
|
+
y: 0
|
|
7503
|
+
},
|
|
7504
|
+
p3: {
|
|
7505
|
+
x: 0,
|
|
7506
|
+
y: 0
|
|
7507
|
+
},
|
|
7508
|
+
p4: {
|
|
7509
|
+
x: 0,
|
|
7510
|
+
y: 0
|
|
7511
|
+
}
|
|
7512
|
+
};
|
|
7513
|
+
}
|
|
7514
|
+
function emptyRawGlyph() {
|
|
7178
7515
|
return {
|
|
7516
|
+
flags: 2,
|
|
7179
7517
|
looseBox: {
|
|
7180
7518
|
left: 0,
|
|
7181
7519
|
bottom: 0,
|
|
7182
7520
|
right: 0,
|
|
7183
7521
|
top: 0
|
|
7184
7522
|
},
|
|
7185
|
-
|
|
7523
|
+
upright: true,
|
|
7524
|
+
rotation: 0,
|
|
7525
|
+
ascentFlip: false
|
|
7186
7526
|
};
|
|
7187
7527
|
}
|
|
7188
|
-
function
|
|
7528
|
+
function readQuad(mem, ptr, byteOffset) {
|
|
7529
|
+
const f = (offset) => readF32(mem, ptr, byteOffset + offset);
|
|
7189
7530
|
return {
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7531
|
+
p1: {
|
|
7532
|
+
x: f(0),
|
|
7533
|
+
y: f(4)
|
|
7534
|
+
},
|
|
7535
|
+
p2: {
|
|
7536
|
+
x: f(8),
|
|
7537
|
+
y: f(12)
|
|
7538
|
+
},
|
|
7539
|
+
p3: {
|
|
7540
|
+
x: f(16),
|
|
7541
|
+
y: f(20)
|
|
7542
|
+
},
|
|
7543
|
+
p4: {
|
|
7544
|
+
x: f(24),
|
|
7545
|
+
y: f(28)
|
|
7546
|
+
}
|
|
7193
7547
|
};
|
|
7194
7548
|
}
|
|
7195
7549
|
//#endregion
|
|
@@ -8562,7 +8916,7 @@ function emptyResult(index, status) {
|
|
|
8562
8916
|
//#endregion
|
|
8563
8917
|
//#region src/features/search/internal/pageCorpusCache.ts
|
|
8564
8918
|
/**
|
|
8565
|
-
* Per-session, per-page search corpus: the page's
|
|
8919
|
+
* Per-session, per-page search corpus: the page's text snapshot plus its
|
|
8566
8920
|
* DEFAULT fold (the one literal queries with default options search).
|
|
8567
8921
|
* This is the local engine's in-memory equivalent of the server's corpus
|
|
8568
8922
|
* artifacts — same fold version, same shape, built lazily on first search
|
|
@@ -8587,19 +8941,20 @@ function acquirePageCorpus(runtime, session, pageObjectNumber, signal) {
|
|
|
8587
8941
|
cache.set(session, pages);
|
|
8588
8942
|
}
|
|
8589
8943
|
const hit = pages.get(pageObjectNumber);
|
|
8590
|
-
if (hit && hit.seq === seq) return hit
|
|
8591
|
-
const
|
|
8592
|
-
const
|
|
8593
|
-
pages.delete(pageObjectNumber);
|
|
8594
|
-
pages.set(pageObjectNumber, {
|
|
8944
|
+
if (hit && hit.seq === seq) return hit;
|
|
8945
|
+
const snapshot = new PageTextReader(runtime, session).read(pageObjectNumber, signal);
|
|
8946
|
+
const entry = {
|
|
8595
8947
|
seq,
|
|
8596
|
-
folded
|
|
8597
|
-
|
|
8948
|
+
folded: (0, _embedpdf_engine_core_runtime.foldText)(snapshot.text),
|
|
8949
|
+
snapshot
|
|
8950
|
+
};
|
|
8951
|
+
pages.delete(pageObjectNumber);
|
|
8952
|
+
pages.set(pageObjectNumber, entry);
|
|
8598
8953
|
if (pages.size > MAX_CACHED_PAGES) {
|
|
8599
8954
|
const oldest = pages.keys().next().value;
|
|
8600
8955
|
if (oldest !== void 0) pages.delete(oldest);
|
|
8601
8956
|
}
|
|
8602
|
-
return
|
|
8957
|
+
return entry;
|
|
8603
8958
|
}
|
|
8604
8959
|
//#endregion
|
|
8605
8960
|
//#region src/features/search/internal/searchCursor.ts
|
|
@@ -8708,19 +9063,23 @@ var SearchReader = class {
|
|
|
8708
9063
|
throwIfAborted(signal);
|
|
8709
9064
|
const pon = order[scanned].pageObjectNumber;
|
|
8710
9065
|
const corpus = acquirePageCorpus(this.runtime, this.session, pon, signal);
|
|
9066
|
+
const text = corpus.snapshot.text;
|
|
8711
9067
|
let ranges;
|
|
8712
|
-
if (query.regex) ranges = (0, _embedpdf_engine_core_runtime.matchRegex)(
|
|
8713
|
-
else if (query.matchCase || query.matchDiacritics) ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)((0, _embedpdf_engine_core_runtime.foldText)(
|
|
8714
|
-
else ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)(corpus, query);
|
|
9068
|
+
if (query.regex) ranges = (0, _embedpdf_engine_core_runtime.matchRegex)(text, query);
|
|
9069
|
+
else if (query.matchCase || query.matchDiacritics) ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)((0, _embedpdf_engine_core_runtime.foldText)(text, (0, _embedpdf_engine_core_runtime.foldOptionsFor)(query)), query);
|
|
9070
|
+
else ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)(corpus.folded, query);
|
|
8715
9071
|
if (ranges.length > 0) {
|
|
8716
|
-
const
|
|
8717
|
-
for (const range of ranges)
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
9072
|
+
const layout = (0, _embedpdf_engine_core_runtime.buildPageTextLayout)(new PageGeometryReader(this.runtime, this.session).read(pon, signal));
|
|
9073
|
+
for (const range of ranges) {
|
|
9074
|
+
const chars = (0, _embedpdf_engine_core_runtime.charRangeForTextOffsets)(corpus.snapshot, range.start, range.start + range.length);
|
|
9075
|
+
matches.push({
|
|
9076
|
+
pageObjectNumber: pon,
|
|
9077
|
+
charStart: chars.start,
|
|
9078
|
+
charCount: chars.end - chars.start,
|
|
9079
|
+
segments: (0, _embedpdf_engine_core_runtime.textSegmentsForRange)(layout, chars.start, chars.end - chars.start),
|
|
9080
|
+
...mode === "full" ? { snippet: (0, _embedpdf_engine_core_runtime.buildSnippet)(text, range) } : {}
|
|
9081
|
+
});
|
|
9082
|
+
}
|
|
8724
9083
|
}
|
|
8725
9084
|
scanned++;
|
|
8726
9085
|
pagesThisSlice++;
|
|
@@ -9001,9 +9360,10 @@ function validatePatchValue(key, value) {
|
|
|
9001
9360
|
* worker wire shape from @embedpdf/engine-core.
|
|
9002
9361
|
*/
|
|
9003
9362
|
var WorkerHost = class {
|
|
9004
|
-
constructor(runtime, post) {
|
|
9363
|
+
constructor(runtime, post, options = {}) {
|
|
9005
9364
|
this.runtime = runtime;
|
|
9006
9365
|
this.post = post;
|
|
9366
|
+
this.options = options;
|
|
9007
9367
|
_defineProperty(this, "sessions", /* @__PURE__ */ new Map());
|
|
9008
9368
|
_defineProperty(this, "baseDocuments", void 0);
|
|
9009
9369
|
_defineProperty(this, "aborts", /* @__PURE__ */ new Map());
|
|
@@ -9029,6 +9389,10 @@ var WorkerHost = class {
|
|
|
9029
9389
|
this.aborts.get(msg.jobId)?.abort();
|
|
9030
9390
|
return;
|
|
9031
9391
|
}
|
|
9392
|
+
if (msg.kind === "pages.renderEncoded" || msg.kind === "document.renderPageFileEncoded" || msg.kind === "annotations.renderAppearancesEncoded") {
|
|
9393
|
+
this.receiveEncoded(msg);
|
|
9394
|
+
return;
|
|
9395
|
+
}
|
|
9032
9396
|
const ctrl = new AbortController();
|
|
9033
9397
|
this.aborts.set(msg.jobId, ctrl);
|
|
9034
9398
|
let resultPack;
|
|
@@ -9132,6 +9496,9 @@ var WorkerHost = class {
|
|
|
9132
9496
|
case "pages.insert":
|
|
9133
9497
|
resultPack = this.handlePagesInsert(msg, ctrl.signal);
|
|
9134
9498
|
break;
|
|
9499
|
+
case "pages.insertBlank":
|
|
9500
|
+
resultPack = this.handlePagesInsertBlank(msg, ctrl.signal);
|
|
9501
|
+
break;
|
|
9135
9502
|
case "attachments.list":
|
|
9136
9503
|
resultPack = this.handleAttachmentsList(msg, ctrl.signal);
|
|
9137
9504
|
break;
|
|
@@ -9424,6 +9791,17 @@ var WorkerHost = class {
|
|
|
9424
9791
|
result
|
|
9425
9792
|
}, req.artifactPath);
|
|
9426
9793
|
}
|
|
9794
|
+
handlePagesInsertBlank(req, signal) {
|
|
9795
|
+
const session = this.requireSession(req);
|
|
9796
|
+
const result = new PagesInserter(this.runtime, session).insertBlank({
|
|
9797
|
+
size: req.size,
|
|
9798
|
+
count: req.count
|
|
9799
|
+
}, req.destIndex, signal);
|
|
9800
|
+
return this.finishMutation(session, {
|
|
9801
|
+
tag: "pages.insertBlank",
|
|
9802
|
+
result
|
|
9803
|
+
}, req.artifactPath);
|
|
9804
|
+
}
|
|
9427
9805
|
handleAttachmentsList(req, signal) {
|
|
9428
9806
|
const session = this.requireSession(req);
|
|
9429
9807
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
@@ -9516,6 +9894,105 @@ var WorkerHost = class {
|
|
|
9516
9894
|
raster
|
|
9517
9895
|
}, [raster.data]);
|
|
9518
9896
|
}
|
|
9897
|
+
async receiveEncoded(msg) {
|
|
9898
|
+
if (!this.options.imageEncoder) {
|
|
9899
|
+
this.post((0, _embedpdf_engine_core_runtime.wirePack)({
|
|
9900
|
+
kind: "reject",
|
|
9901
|
+
jobId: msg.jobId,
|
|
9902
|
+
error: (0, _embedpdf_engine_core_runtime.serializeError)(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "this engine has no image encoder (the *.renderEncoded kinds are cloud-server surface)"))
|
|
9903
|
+
}, _embedpdf_engine_core_runtime.EMPTY_TRANSFER));
|
|
9904
|
+
return;
|
|
9905
|
+
}
|
|
9906
|
+
const ctrl = new AbortController();
|
|
9907
|
+
this.aborts.set(msg.jobId, ctrl);
|
|
9908
|
+
try {
|
|
9909
|
+
let resultPack;
|
|
9910
|
+
switch (msg.kind) {
|
|
9911
|
+
case "pages.renderEncoded":
|
|
9912
|
+
resultPack = await this.handlePagesRenderEncoded(msg, ctrl.signal);
|
|
9913
|
+
break;
|
|
9914
|
+
case "document.renderPageFileEncoded":
|
|
9915
|
+
resultPack = await this.handleDocumentRenderPageFileEncoded(msg, ctrl.signal);
|
|
9916
|
+
break;
|
|
9917
|
+
case "annotations.renderAppearancesEncoded":
|
|
9918
|
+
resultPack = await this.handleAnnotationsRenderAppearancesEncoded(msg, ctrl.signal);
|
|
9919
|
+
break;
|
|
9920
|
+
}
|
|
9921
|
+
this.post((0, _embedpdf_engine_core_runtime.wirePack)({
|
|
9922
|
+
kind: "resolve",
|
|
9923
|
+
jobId: msg.jobId,
|
|
9924
|
+
result: resultPack.payload
|
|
9925
|
+
}, resultPack.transfer));
|
|
9926
|
+
} catch (err) {
|
|
9927
|
+
this.post((0, _embedpdf_engine_core_runtime.wirePack)({
|
|
9928
|
+
kind: "reject",
|
|
9929
|
+
jobId: msg.jobId,
|
|
9930
|
+
error: (0, _embedpdf_engine_core_runtime.serializeError)(err)
|
|
9931
|
+
}, _embedpdf_engine_core_runtime.EMPTY_TRANSFER));
|
|
9932
|
+
} finally {
|
|
9933
|
+
this.aborts.delete(msg.jobId);
|
|
9934
|
+
}
|
|
9935
|
+
}
|
|
9936
|
+
async encodeRaster(raster, encode, signal) {
|
|
9937
|
+
const encoder = this.options.imageEncoder;
|
|
9938
|
+
if (!encoder) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotImplemented, "this engine has no image encoder (the *.renderEncoded kinds are cloud-server surface)");
|
|
9939
|
+
const { bytes, contentType } = await encoder.encode(raster, encode);
|
|
9940
|
+
if (signal.aborted) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Aborted, "aborted during image encode");
|
|
9941
|
+
return {
|
|
9942
|
+
contentType,
|
|
9943
|
+
width: raster.width,
|
|
9944
|
+
height: raster.height,
|
|
9945
|
+
bytes
|
|
9946
|
+
};
|
|
9947
|
+
}
|
|
9948
|
+
async handlePagesRenderEncoded(req, signal) {
|
|
9949
|
+
const inner = this.handlePagesRender({
|
|
9950
|
+
...req,
|
|
9951
|
+
kind: "pages.render"
|
|
9952
|
+
}, signal);
|
|
9953
|
+
if (inner.payload.tag !== "pages.render") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `unexpected ${inner.payload.tag}`);
|
|
9954
|
+
const image = await this.encodeRaster(inner.payload.raster, req.encode, signal);
|
|
9955
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
9956
|
+
tag: "pages.renderEncoded",
|
|
9957
|
+
image
|
|
9958
|
+
}, [image.bytes.buffer]);
|
|
9959
|
+
}
|
|
9960
|
+
async handleDocumentRenderPageFileEncoded(req, signal) {
|
|
9961
|
+
const inner = this.handleDocumentRenderPageFile({
|
|
9962
|
+
...req,
|
|
9963
|
+
kind: "document.renderPageFile"
|
|
9964
|
+
}, signal);
|
|
9965
|
+
if (inner.payload.tag !== "document.renderPageFile") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `unexpected ${inner.payload.tag}`);
|
|
9966
|
+
const image = await this.encodeRaster(inner.payload.raster, req.encode, signal);
|
|
9967
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
9968
|
+
tag: "document.renderPageFileEncoded",
|
|
9969
|
+
pageObjectNumber: inner.payload.pageObjectNumber,
|
|
9970
|
+
pageCount: inner.payload.pageCount,
|
|
9971
|
+
image
|
|
9972
|
+
}, [image.bytes.buffer]);
|
|
9973
|
+
}
|
|
9974
|
+
async handleAnnotationsRenderAppearancesEncoded(req, signal) {
|
|
9975
|
+
const inner = this.handleAnnotationsRenderAppearances({
|
|
9976
|
+
...req,
|
|
9977
|
+
kind: "annotations.renderAppearances"
|
|
9978
|
+
}, signal);
|
|
9979
|
+
if (inner.payload.tag !== "annotations.renderAppearances") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `unexpected ${inner.payload.tag}`);
|
|
9980
|
+
const { pageState, appearances } = inner.payload.result;
|
|
9981
|
+
const encoded = [];
|
|
9982
|
+
for (const a of appearances) encoded.push({
|
|
9983
|
+
ref: a.ref,
|
|
9984
|
+
mode: a.mode,
|
|
9985
|
+
rect: a.rect,
|
|
9986
|
+
image: await this.encodeRaster(a.raster, req.encode, signal)
|
|
9987
|
+
});
|
|
9988
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
9989
|
+
tag: "annotations.renderAppearancesEncoded",
|
|
9990
|
+
result: {
|
|
9991
|
+
pageState,
|
|
9992
|
+
appearances: encoded
|
|
9993
|
+
}
|
|
9994
|
+
}, encoded.map((e) => e.image.bytes.buffer));
|
|
9995
|
+
}
|
|
9519
9996
|
handleDocumentSaveBuffer(req) {
|
|
9520
9997
|
const session = this.requireSession(req);
|
|
9521
9998
|
const saved = new DocumentSaver(this.runtime, session).saveStandaloneToBuffer(req.mode);
|
|
@@ -9860,6 +10337,7 @@ exports.DocumentActionsReader = DocumentActionsReader;
|
|
|
9860
10337
|
exports.DocumentPagesMutator = PagesMutator;
|
|
9861
10338
|
exports.DocumentSaver = DocumentSaver;
|
|
9862
10339
|
exports.DocumentSession = DocumentSession;
|
|
10340
|
+
exports.EPDF_CHAR_GEOMETRY_LAYOUT = EPDF_CHAR_GEOMETRY_LAYOUT;
|
|
9863
10341
|
exports.EventHub = EventHub;
|
|
9864
10342
|
exports.FontRegistrar = FontRegistrar;
|
|
9865
10343
|
exports.FormMutator = FormMutator;
|
|
@@ -9885,6 +10363,7 @@ exports.SearchReader = SearchReader;
|
|
|
9885
10363
|
exports.SecurityReader = SecurityReader;
|
|
9886
10364
|
exports.SessionEventPublisher = SessionEventPublisher;
|
|
9887
10365
|
exports.WorkerHost = WorkerHost;
|
|
10366
|
+
exports.buildRunsFromRawGlyphs = buildRunsFromRawGlyphs;
|
|
9888
10367
|
exports.computeMutationImpact = computeMutationImpact;
|
|
9889
10368
|
exports.destroyLibrary = destroyLibrary;
|
|
9890
10369
|
exports.disposeFormModel = disposeFormModel;
|