@embedpdf/engine-services 3.0.0-next.2 → 3.0.0-next.4
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 +506 -312
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -4
- package/dist/index.d.ts +77 -4
- package/dist/index.js +506 -314
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AbortError, EMPTY_TRANSFER, EngineError, EngineErrorCode, NO_ANNOTATION_FLAGS, PDF_SUBTYPE_TO_CODE, PdfAnnotationSubtypeCode, UNKNOWN_WEAK_ANNOTATION_STATE, appearanceImpactOf, buildSnippet, foldOptionsFor, foldText, invalidatesWeakIndexRefs, isValidPageObjectNumber, knownWeakAnnotationState, matchLiteral, matchRegex, normalizePdfRect,
|
|
1
|
+
import { AbortError, EMPTY_TRANSFER, EngineError, EngineErrorCode, NO_ANNOTATION_FLAGS, PDF_SUBTYPE_TO_CODE, PdfAnnotationSubtypeCode, UNKNOWN_WEAK_ANNOTATION_STATE, appearanceImpactOf, buildPageTextLayout, buildSnippet, foldOptionsFor, foldText, invalidatesWeakIndexRefs, isValidPageObjectNumber, knownWeakAnnotationState, matchLiteral, matchRegex, normalizePdfRect, pdfQuadBounds, revisionTokensEqual, serializeError, shiftsExistingAnnotationIndices, sniffBinaryMetadata, subtypeFromCode, textSegmentsForRange, validateSearchQuery, wirePack } from "@embedpdf/engine-core/runtime";
|
|
2
2
|
import { NULL_PTR } from "@embedpdf/engine-runtime";
|
|
3
3
|
//#region src/runtime/lifecycle/bootstrap.ts
|
|
4
4
|
const initialized = /* @__PURE__ */ new WeakSet();
|
|
@@ -1163,13 +1163,34 @@ var MetadataMutator = class {
|
|
|
1163
1163
|
};
|
|
1164
1164
|
//#endregion
|
|
1165
1165
|
//#region src/runtime/memory/structs.ts
|
|
1166
|
+
/** Generated from `EPDF_CHAR_GEOMETRY`; pinned by native static_asserts. */
|
|
1167
|
+
const EPDF_CHAR_GEOMETRY_LAYOUT = {
|
|
1168
|
+
bytes: 124,
|
|
1169
|
+
offsets: {
|
|
1170
|
+
looseBox: 0,
|
|
1171
|
+
tightBox: 16,
|
|
1172
|
+
looseQuad: 32,
|
|
1173
|
+
tightQuad: 64,
|
|
1174
|
+
matrix: 96,
|
|
1175
|
+
flags: 120
|
|
1176
|
+
},
|
|
1177
|
+
flags: {
|
|
1178
|
+
hasTightBox: 1,
|
|
1179
|
+
hasLooseQuad: 2,
|
|
1180
|
+
hasTightQuad: 4,
|
|
1181
|
+
upright: 8,
|
|
1182
|
+
space: 16,
|
|
1183
|
+
empty: 32,
|
|
1184
|
+
synthesized: 64
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1166
1187
|
/** Decode an `FS_RECTF` struct that a native call wrote into `ptr`. */
|
|
1167
|
-
function readRectF(mem, ptr) {
|
|
1188
|
+
function readRectF(mem, ptr, byteOffset = 0) {
|
|
1168
1189
|
return {
|
|
1169
|
-
left: readF32(mem, ptr,
|
|
1170
|
-
top: readF32(mem, ptr, 4),
|
|
1171
|
-
right: readF32(mem, ptr, 8),
|
|
1172
|
-
bottom: readF32(mem, ptr, 12)
|
|
1190
|
+
left: readF32(mem, ptr, byteOffset),
|
|
1191
|
+
top: readF32(mem, ptr, byteOffset + 4),
|
|
1192
|
+
right: readF32(mem, ptr, byteOffset + 8),
|
|
1193
|
+
bottom: readF32(mem, ptr, byteOffset + 12)
|
|
1173
1194
|
};
|
|
1174
1195
|
}
|
|
1175
1196
|
/** Decode an `FS_SIZEF` struct that a native call wrote into `ptr`. */
|
|
@@ -2153,6 +2174,49 @@ function readQuadPoints(fn, mem, annotPtr) {
|
|
|
2153
2174
|
});
|
|
2154
2175
|
}
|
|
2155
2176
|
//#endregion
|
|
2177
|
+
//#region src/features/annotations/internal/read/readAnnotationTransformMetadata.ts
|
|
2178
|
+
/**
|
|
2179
|
+
* Read-side twin of `writers/.../writeAnnotationTransformMetadata.ts`. Decodes
|
|
2180
|
+
* the EmbedPDF rotation keys from /EMBD_Metadata back onto the DTO:
|
|
2181
|
+
*
|
|
2182
|
+
* - `Rotation` — degrees, PDF convention. Returned for EVERY rotatable
|
|
2183
|
+
* kind (box AND vertex); the plugin converts it back to its CW-content
|
|
2184
|
+
* `rot` once at the repository seam.
|
|
2185
|
+
* - `UnrotatedRect` — the logical box. BOX kinds only (square/circle/
|
|
2186
|
+
* free-text); a vertex annotation never carries one (its points are the
|
|
2187
|
+
* visual), so vertex readers don't ask for it.
|
|
2188
|
+
*
|
|
2189
|
+
* Both are absent on un-rotated annotations (and on everything authored before
|
|
2190
|
+
* rotation shipped), so both reads return `undefined` and the DTO field stays
|
|
2191
|
+
* unset.
|
|
2192
|
+
*/
|
|
2193
|
+
const KEY_ROTATION$1 = "Rotation";
|
|
2194
|
+
const KEY_UNROTATED_RECT$1 = "UnrotatedRect";
|
|
2195
|
+
/** The advisory/portable rotation scalar (degrees, PDF convention), or
|
|
2196
|
+
* `undefined` when absent or zero. */
|
|
2197
|
+
function readAnnotationRotation(fn, mem, annotPtr) {
|
|
2198
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
|
|
2199
|
+
return withScratch(mem, 4, (buf) => {
|
|
2200
|
+
if (!fn.EPDFAnnot_GetEmbedMetadataNumber(annotPtr, KEY_ROTATION$1, buf)) return void 0;
|
|
2201
|
+
const v = readF32(mem, buf);
|
|
2202
|
+
return v ? v : void 0;
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
/** The logical (unrotated) box of a rotated BOX annotation, or `undefined`. */
|
|
2206
|
+
function readAnnotationUnrotatedRect(fn, mem, annotPtr) {
|
|
2207
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
|
|
2208
|
+
return withScratch(mem, 16, (buf) => {
|
|
2209
|
+
if (!fn.EPDFAnnot_GetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT$1, buf)) return void 0;
|
|
2210
|
+
const r = readRectF(mem, buf);
|
|
2211
|
+
return {
|
|
2212
|
+
left: r.left,
|
|
2213
|
+
bottom: r.bottom,
|
|
2214
|
+
right: r.right,
|
|
2215
|
+
top: r.top
|
|
2216
|
+
};
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
//#endregion
|
|
2156
2220
|
//#region src/features/annotations/internal/textEditIntent.ts
|
|
2157
2221
|
/** Adobe-compatible `/IT` names, normalized at the engine-core boundary. */
|
|
2158
2222
|
const IT_REPLACE = "Replace";
|
|
@@ -2187,13 +2251,17 @@ function readCaret(fn, mem, annotPtr, base) {
|
|
|
2187
2251
|
const opacity = ca == null ? 1 : Math.max(0, Math.min(1, ca));
|
|
2188
2252
|
const rd = readRectangleDifferences(fn, mem, annotPtr);
|
|
2189
2253
|
const intent = caretIntentFromName(readIntent(fn, mem, annotPtr));
|
|
2254
|
+
const rotation = readAnnotationRotation(fn, mem, annotPtr);
|
|
2255
|
+
const unrotatedRect = readAnnotationUnrotatedRect(fn, mem, annotPtr);
|
|
2190
2256
|
return {
|
|
2191
2257
|
...base,
|
|
2192
2258
|
subtype: "caret",
|
|
2193
2259
|
intent,
|
|
2194
2260
|
color,
|
|
2195
2261
|
opacity,
|
|
2196
|
-
rectDifferences: rd
|
|
2262
|
+
rectDifferences: rd,
|
|
2263
|
+
...rotation != null ? { rotation } : {},
|
|
2264
|
+
...unrotatedRect ? { unrotatedRect } : {}
|
|
2197
2265
|
};
|
|
2198
2266
|
}
|
|
2199
2267
|
//#endregion
|
|
@@ -2611,49 +2679,6 @@ function readFilledStyleExtras(fn, mem, annotPtr) {
|
|
|
2611
2679
|
};
|
|
2612
2680
|
}
|
|
2613
2681
|
//#endregion
|
|
2614
|
-
//#region src/features/annotations/internal/read/readAnnotationTransformMetadata.ts
|
|
2615
|
-
/**
|
|
2616
|
-
* Read-side twin of `writers/.../writeAnnotationTransformMetadata.ts`. Decodes
|
|
2617
|
-
* the EmbedPDF rotation keys from /EMBD_Metadata back onto the DTO:
|
|
2618
|
-
*
|
|
2619
|
-
* - `Rotation` — degrees, PDF convention. Returned for EVERY rotatable
|
|
2620
|
-
* kind (box AND vertex); the plugin converts it back to its CW-content
|
|
2621
|
-
* `rot` once at the repository seam.
|
|
2622
|
-
* - `UnrotatedRect` — the logical box. BOX kinds only (square/circle/
|
|
2623
|
-
* free-text); a vertex annotation never carries one (its points are the
|
|
2624
|
-
* visual), so vertex readers don't ask for it.
|
|
2625
|
-
*
|
|
2626
|
-
* Both are absent on un-rotated annotations (and on everything authored before
|
|
2627
|
-
* rotation shipped), so both reads return `undefined` and the DTO field stays
|
|
2628
|
-
* unset.
|
|
2629
|
-
*/
|
|
2630
|
-
const KEY_ROTATION$1 = "Rotation";
|
|
2631
|
-
const KEY_UNROTATED_RECT$1 = "UnrotatedRect";
|
|
2632
|
-
/** The advisory/portable rotation scalar (degrees, PDF convention), or
|
|
2633
|
-
* `undefined` when absent or zero. */
|
|
2634
|
-
function readAnnotationRotation(fn, mem, annotPtr) {
|
|
2635
|
-
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
|
|
2636
|
-
return withScratch(mem, 4, (buf) => {
|
|
2637
|
-
if (!fn.EPDFAnnot_GetEmbedMetadataNumber(annotPtr, KEY_ROTATION$1, buf)) return void 0;
|
|
2638
|
-
const v = readF32(mem, buf);
|
|
2639
|
-
return v ? v : void 0;
|
|
2640
|
-
});
|
|
2641
|
-
}
|
|
2642
|
-
/** The logical (unrotated) box of a rotated BOX annotation, or `undefined`. */
|
|
2643
|
-
function readAnnotationUnrotatedRect(fn, mem, annotPtr) {
|
|
2644
|
-
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
|
|
2645
|
-
return withScratch(mem, 16, (buf) => {
|
|
2646
|
-
if (!fn.EPDFAnnot_GetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT$1, buf)) return void 0;
|
|
2647
|
-
const r = readRectF(mem, buf);
|
|
2648
|
-
return {
|
|
2649
|
-
left: r.left,
|
|
2650
|
-
bottom: r.bottom,
|
|
2651
|
-
right: r.right,
|
|
2652
|
-
top: r.top
|
|
2653
|
-
};
|
|
2654
|
-
});
|
|
2655
|
-
}
|
|
2656
|
-
//#endregion
|
|
2657
2682
|
//#region src/features/annotations/internal/read/readFreeTextAnnotation.ts
|
|
2658
2683
|
/** Default `/DA` colour (black) when an annotation has no default appearance. */
|
|
2659
2684
|
const DEFAULT_FREETEXT_COLOR$1 = {
|
|
@@ -4130,17 +4155,21 @@ const ANNOT_SUBTYPE_WIDGET = 20;
|
|
|
4130
4155
|
/** `FPDF_ANNOT_FREETEXT` — free-text annotation subtype code. */
|
|
4131
4156
|
const ANNOT_SUBTYPE_FREETEXT = 3;
|
|
4132
4157
|
/**
|
|
4133
|
-
* BOX-family subtypes (free-text 3, square 5, circle 6, stamp 13):
|
|
4134
|
-
* whose v3 writers put rotation in the AP `/Matrix` +
|
|
4135
|
-
* `/UnrotatedRect`. Only these are eligible for
|
|
4136
|
-
* rendering — vertex kinds
|
|
4137
|
-
* geometry, so their rasters must
|
|
4158
|
+
* BOX-family subtypes (free-text 3, square 5, circle 6, stamp 13, caret 14):
|
|
4159
|
+
* the kinds whose v3 writers put rotation in the AP `/Matrix` +
|
|
4160
|
+
* `/EMBD_Metadata` `/UnrotatedRect`. Only these are eligible for
|
|
4161
|
+
* rotation-stripped appearance rendering — vertex kinds
|
|
4162
|
+
* (line/polyline/polygon/ink) pre-rotate their geometry, so their rasters must
|
|
4163
|
+
* stay on the classic path. This set MUST cover every kind whose reader
|
|
4164
|
+
* surfaces the rotation pair: `fromDTO` (plugin-annotation repository) mirrors
|
|
4165
|
+
* this exact condition to re-apply the stripped rotation as `apRot`.
|
|
4138
4166
|
*/
|
|
4139
4167
|
const BOX_FAMILY_SUBTYPES = /* @__PURE__ */ new Set([
|
|
4140
4168
|
3,
|
|
4141
4169
|
5,
|
|
4142
4170
|
6,
|
|
4143
|
-
13
|
|
4171
|
+
13,
|
|
4172
|
+
14
|
|
4144
4173
|
]);
|
|
4145
4174
|
/**
|
|
4146
4175
|
* Maps an `AnnotationAppearanceMode` onto the PDFium appearance-mode int and
|
|
@@ -4829,6 +4858,177 @@ function readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, index, revis
|
|
|
4829
4858
|
return reader(fn, mem, annotPtr, base, subtypeCode, ctx);
|
|
4830
4859
|
}
|
|
4831
4860
|
//#endregion
|
|
4861
|
+
//#region src/features/annotations/internal/write/writeEmbedMetadata.ts
|
|
4862
|
+
/**
|
|
4863
|
+
* Write the EmbedPDF-namespaced /EMBD_Metadata annotation dictionary.
|
|
4864
|
+
*
|
|
4865
|
+
* This module owns ONLY the vendor-extension dictionary. Standard PDF
|
|
4866
|
+
* base fields (/T, /M, /NM, /Contents) are handled by
|
|
4867
|
+
* `writers/annotations/base.ts` — /EMBD_Metadata is a separate
|
|
4868
|
+
* dictionary nested under the annotation that carries cloud-side
|
|
4869
|
+
* identity bookkeeping which has no PDF-spec equivalent.
|
|
4870
|
+
*
|
|
4871
|
+
* Schema (v1):
|
|
4872
|
+
* /EMBD_Metadata <<
|
|
4873
|
+
* /SchemaVersion 1 % EmbedPDF metadata schema version
|
|
4874
|
+
* /UserID (acme/alice) % identity that created the annotation
|
|
4875
|
+
* /GroupID (engineering) % group the annotation belongs to
|
|
4876
|
+
* /CreatedBy (acme/alice) % same as UserID on create
|
|
4877
|
+
* /UpdatedBy (acme/alice) % last editor; refreshed on every update
|
|
4878
|
+
* >>
|
|
4879
|
+
*
|
|
4880
|
+
* Read side: see `readers/annotations/embed-metadata.ts` (commit 12).
|
|
4881
|
+
*
|
|
4882
|
+
* PDFium bindings used: the `EPDFAnnot_*EmbedMetadata*` extension API
|
|
4883
|
+
* shipped in the EmbedPDF PDFium fork. Values are written as UTF-16LE
|
|
4884
|
+
* strings via the same `mem.writeU16String` pattern the base writer
|
|
4885
|
+
* uses for /T.
|
|
4886
|
+
*/
|
|
4887
|
+
const KEY_SCHEMA_VERSION = "SchemaVersion";
|
|
4888
|
+
const KEY_USER_ID = "UserID";
|
|
4889
|
+
const KEY_GROUP_ID = "GroupID";
|
|
4890
|
+
const KEY_CREATED_BY = "CreatedBy";
|
|
4891
|
+
const KEY_UPDATED_BY = "UpdatedBy";
|
|
4892
|
+
/**
|
|
4893
|
+
* Stamp /EMBD_Metadata on a freshly created annotation. No-op if the
|
|
4894
|
+
* actor has no identity fields — the worker still emits the standard
|
|
4895
|
+
* /M timestamp via the base writer; only the vendor dictionary is
|
|
4896
|
+
* skipped.
|
|
4897
|
+
*
|
|
4898
|
+
* actor.userId → /UserID, /CreatedBy, /UpdatedBy
|
|
4899
|
+
* actor.groupId → /GroupID
|
|
4900
|
+
*
|
|
4901
|
+
* /SchemaVersion is written whenever any field is written.
|
|
4902
|
+
*/
|
|
4903
|
+
function applyEmbedMetadataOnCreate(fn, mem, annotPtr, actor) {
|
|
4904
|
+
if (!actor || !actor.userId && !actor.groupId) return;
|
|
4905
|
+
fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
|
|
4906
|
+
if (actor.userId) {
|
|
4907
|
+
setMetadataString(fn, mem, annotPtr, KEY_USER_ID, actor.userId);
|
|
4908
|
+
setMetadataString(fn, mem, annotPtr, KEY_CREATED_BY, actor.userId);
|
|
4909
|
+
setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
|
|
4910
|
+
}
|
|
4911
|
+
if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
|
|
4912
|
+
}
|
|
4913
|
+
/**
|
|
4914
|
+
* Apply /EMBD_Metadata refreshes on update. Two fields can change on
|
|
4915
|
+
* update; everything else (UserID, CreatedBy) is immutable.
|
|
4916
|
+
*
|
|
4917
|
+
* actor.userId → /UpdatedBy (refreshed every update where the
|
|
4918
|
+
* caller has an identity)
|
|
4919
|
+
* actor.groupId → /GroupID (reassigned only when the patch
|
|
4920
|
+
* explicitly contains a new groupId; route runs
|
|
4921
|
+
* `checkSetGroup` first)
|
|
4922
|
+
*
|
|
4923
|
+
* If the target annotation has no EMBD_Metadata yet (created before
|
|
4924
|
+
* the cloud started stamping, or by a non-cloud writer), we seed
|
|
4925
|
+
* /SchemaVersion so subsequent readers can find the marker.
|
|
4926
|
+
*
|
|
4927
|
+
* No-op when the actor has neither userId nor groupId.
|
|
4928
|
+
*/
|
|
4929
|
+
function applyEmbedMetadataOnUpdate(fn, mem, annotPtr, actor) {
|
|
4930
|
+
if (!actor || !actor.userId && !actor.groupId) return;
|
|
4931
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
|
|
4932
|
+
if (actor.userId) setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
|
|
4933
|
+
if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
|
|
4934
|
+
}
|
|
4935
|
+
function setMetadataString(fn, mem, annotPtr, key, value) {
|
|
4936
|
+
const ptr = mem.writeU16String(value);
|
|
4937
|
+
try {
|
|
4938
|
+
fn.EPDFAnnot_SetEmbedMetadataString(annotPtr, key, ptr);
|
|
4939
|
+
} finally {
|
|
4940
|
+
mem.free(ptr);
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
//#endregion
|
|
4944
|
+
//#region src/features/annotations/internal/write/writeAnnotationTransformMetadata.ts
|
|
4945
|
+
/**
|
|
4946
|
+
* Write the EmbedPDF transform keys under /EMBD_Metadata. This is the seam
|
|
4947
|
+
* PDFium's native AP generator reads to bake a ROTATED appearance:
|
|
4948
|
+
*
|
|
4949
|
+
* /EMBD_Metadata <<
|
|
4950
|
+
* /Rotation 45 % degrees, PDF convention (CCW)
|
|
4951
|
+
* /UnrotatedRect [x0 y0 x1 y1] % BOX kinds only — the logical box
|
|
4952
|
+
* >>
|
|
4953
|
+
*
|
|
4954
|
+
* The family split (see the v3 plan):
|
|
4955
|
+
* - BOX kinds (square/circle/free-text): `/Rotation` + `/UnrotatedRect`.
|
|
4956
|
+
* With BOTH present the AP generator emits an `/AP /Matrix` that rotates a
|
|
4957
|
+
* box-sized appearance about the box centre, and `/Rect` is the enclosing
|
|
4958
|
+
* AABB. This is the portable, externally-correct rotation.
|
|
4959
|
+
* - VERTEX kinds (line/polyline/polygon/ink): `/Rotation` ONLY (advisory).
|
|
4960
|
+
* The points are already rotated (they are the visual), so a lone
|
|
4961
|
+
* `/Rotation` is INERT for the AP generator (it ignores `/Rotation` with no
|
|
4962
|
+
* `/UnrotatedRect`) — it just records the applied angle so EmbedPDF can
|
|
4963
|
+
* reconstruct an oriented selection box + offer reset.
|
|
4964
|
+
*
|
|
4965
|
+
* Both fields follow the engine-wide tri-state law ("a patch touches what it
|
|
4966
|
+
* states, preserves what it omits"):
|
|
4967
|
+
* - `undefined` → the key is UNTOUCHED (a rect-only move keeps its rotation).
|
|
4968
|
+
* - `null` or `0` → CLEAR just that key via `EPDFAnnot_ClearEmbedMetadataKey`
|
|
4969
|
+
* (0 ≡ no rotation is the canonical identity, not a sentinel; never clear
|
|
4970
|
+
* the whole dict — identity fields UserID/GroupID/CreatedBy/UpdatedBy must
|
|
4971
|
+
* survive).
|
|
4972
|
+
* - a value → SET the key. Setting a nonzero rotation with no
|
|
4973
|
+
* `/UnrotatedRect` (neither in the patch nor already on the annotation)
|
|
4974
|
+
* is an unsatisfiable state for the AP generator and throws `InvalidArg`.
|
|
4975
|
+
*
|
|
4976
|
+
* MUST run BEFORE `EPDFAnnot_GenerateAppearance` so the bake sees the rotation.
|
|
4977
|
+
* `/SchemaVersion` is seeded (stays 1) if this is the first key in the dict.
|
|
4978
|
+
*/
|
|
4979
|
+
const KEY_ROTATION = "Rotation";
|
|
4980
|
+
const KEY_UNROTATED_RECT = "UnrotatedRect";
|
|
4981
|
+
/** Seed `/SchemaVersion` when we are about to create the dict by writing the
|
|
4982
|
+
* first transform key, so the marker readers look for is always present. */
|
|
4983
|
+
function ensureSchemaVersion(fn, annotPtr) {
|
|
4984
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, "SchemaVersion", 1);
|
|
4985
|
+
}
|
|
4986
|
+
function setRotation(fn, annotPtr, rotation) {
|
|
4987
|
+
ensureSchemaVersion(fn, annotPtr);
|
|
4988
|
+
fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_ROTATION, rotation);
|
|
4989
|
+
}
|
|
4990
|
+
function setUnrotatedRect(fn, mem, annotPtr, rect) {
|
|
4991
|
+
ensureSchemaVersion(fn, annotPtr);
|
|
4992
|
+
const buf = mem.alloc(16);
|
|
4993
|
+
try {
|
|
4994
|
+
mem.poke(buf, "f32", rect.left, 0);
|
|
4995
|
+
mem.poke(buf, "f32", rect.top, 4);
|
|
4996
|
+
mem.poke(buf, "f32", rect.right, 8);
|
|
4997
|
+
mem.poke(buf, "f32", rect.bottom, 12);
|
|
4998
|
+
fn.EPDFAnnot_SetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT, buf);
|
|
4999
|
+
} finally {
|
|
5000
|
+
mem.free(buf);
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
/**
|
|
5004
|
+
* Write transform metadata for a BOX kind (square/circle/free-text/stamp),
|
|
5005
|
+
* tri-state per field: `undefined` never touches the document (safe to call on
|
|
5006
|
+
* every patch), `null`/`0` clears, a value sets. A nonzero rotation with no
|
|
5007
|
+
* unrotated box anywhere (patch or annotation) is unsatisfiable — the AP
|
|
5008
|
+
* generator needs the pair — and throws instead of writing a broken state.
|
|
5009
|
+
*/
|
|
5010
|
+
function writeBoxTransformMetadata(fn, mem, annotPtr, t) {
|
|
5011
|
+
if (t.rotation !== void 0) if (t.rotation !== null && t.rotation !== 0) {
|
|
5012
|
+
if (!(t.unrotatedRect != null || t.unrotatedRect === void 0 && readAnnotationUnrotatedRect(fn, mem, annotPtr) != null)) throw new EngineError(EngineErrorCode.InvalidArg, "rotation requires unrotatedRect: supply the pair together (or rely on an existing /EMBD_Metadata/UnrotatedRect)");
|
|
5013
|
+
setRotation(fn, annotPtr, t.rotation);
|
|
5014
|
+
} else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
|
|
5015
|
+
if (t.unrotatedRect !== void 0) if (t.unrotatedRect !== null) setUnrotatedRect(fn, mem, annotPtr, t.unrotatedRect);
|
|
5016
|
+
else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5017
|
+
}
|
|
5018
|
+
/**
|
|
5019
|
+
* Write the advisory `/Rotation` scalar for a VERTEX kind
|
|
5020
|
+
* (line/polyline/polygon/ink), tri-state: `undefined` never touches the
|
|
5021
|
+
* document, `null`/`0` clears, a value sets. Whenever it does write, any
|
|
5022
|
+
* `/UnrotatedRect` is defensively cleared — that key is an impossible state on
|
|
5023
|
+
* a vertex kind and must never accidentally drive the AP generator.
|
|
5024
|
+
*/
|
|
5025
|
+
function writeVertexTransformMetadata(fn, annotPtr, t) {
|
|
5026
|
+
if (t.rotation === void 0) return;
|
|
5027
|
+
if (t.rotation !== null && t.rotation !== 0) setRotation(fn, annotPtr, t.rotation);
|
|
5028
|
+
else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
|
|
5029
|
+
fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5030
|
+
}
|
|
5031
|
+
//#endregion
|
|
4832
5032
|
//#region src/features/annotations/internal/write/writeCaretAnnotation.ts
|
|
4833
5033
|
/** Default `/C` colour when a caret draft omits it (engine-wide default mark). */
|
|
4834
5034
|
const DEFAULT_CARET_COLOR = {
|
|
@@ -4849,6 +5049,10 @@ const DEFAULT_OPACITY$4 = 1;
|
|
|
4849
5049
|
function applyCaretDraft(fn, mem, annotPtr, draft) {
|
|
4850
5050
|
applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
|
|
4851
5051
|
setAnnotRect(fn, mem, annotPtr, draft.rect);
|
|
5052
|
+
writeBoxTransformMetadata(fn, mem, annotPtr, {
|
|
5053
|
+
rotation: draft.rotation,
|
|
5054
|
+
unrotatedRect: draft.unrotatedRect
|
|
5055
|
+
});
|
|
4852
5056
|
setAnnotColor(fn, annotPtr, draft.color ?? DEFAULT_CARET_COLOR);
|
|
4853
5057
|
setAnnotOpacity(fn, annotPtr, draft.opacity ?? DEFAULT_OPACITY$4);
|
|
4854
5058
|
if (draft.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(draft.intent));
|
|
@@ -4861,6 +5065,10 @@ function applyCaretDraft(fn, mem, annotPtr, draft) {
|
|
|
4861
5065
|
function applyCaretPatch(fn, mem, annotPtr, patch) {
|
|
4862
5066
|
applyAnnotationBasePatch(fn, mem, annotPtr, patch);
|
|
4863
5067
|
if (patch.rect !== void 0) setAnnotRect(fn, mem, annotPtr, patch.rect);
|
|
5068
|
+
if (patch.rotation !== void 0 || patch.unrotatedRect !== void 0) writeBoxTransformMetadata(fn, mem, annotPtr, {
|
|
5069
|
+
rotation: patch.rotation,
|
|
5070
|
+
unrotatedRect: patch.unrotatedRect
|
|
5071
|
+
});
|
|
4864
5072
|
if (patch.color !== void 0) setAnnotColor(fn, annotPtr, patch.color);
|
|
4865
5073
|
if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
|
|
4866
5074
|
if (patch.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(patch.intent));
|
|
@@ -5039,177 +5247,6 @@ function applyFilledStylePatch(fn, mem, annotPtr, patch) {
|
|
|
5039
5247
|
applyGeometryStylePatch(fn, mem, annotPtr, patch);
|
|
5040
5248
|
}
|
|
5041
5249
|
//#endregion
|
|
5042
|
-
//#region src/features/annotations/internal/write/writeEmbedMetadata.ts
|
|
5043
|
-
/**
|
|
5044
|
-
* Write the EmbedPDF-namespaced /EMBD_Metadata annotation dictionary.
|
|
5045
|
-
*
|
|
5046
|
-
* This module owns ONLY the vendor-extension dictionary. Standard PDF
|
|
5047
|
-
* base fields (/T, /M, /NM, /Contents) are handled by
|
|
5048
|
-
* `writers/annotations/base.ts` — /EMBD_Metadata is a separate
|
|
5049
|
-
* dictionary nested under the annotation that carries cloud-side
|
|
5050
|
-
* identity bookkeeping which has no PDF-spec equivalent.
|
|
5051
|
-
*
|
|
5052
|
-
* Schema (v1):
|
|
5053
|
-
* /EMBD_Metadata <<
|
|
5054
|
-
* /SchemaVersion 1 % EmbedPDF metadata schema version
|
|
5055
|
-
* /UserID (acme/alice) % identity that created the annotation
|
|
5056
|
-
* /GroupID (engineering) % group the annotation belongs to
|
|
5057
|
-
* /CreatedBy (acme/alice) % same as UserID on create
|
|
5058
|
-
* /UpdatedBy (acme/alice) % last editor; refreshed on every update
|
|
5059
|
-
* >>
|
|
5060
|
-
*
|
|
5061
|
-
* Read side: see `readers/annotations/embed-metadata.ts` (commit 12).
|
|
5062
|
-
*
|
|
5063
|
-
* PDFium bindings used: the `EPDFAnnot_*EmbedMetadata*` extension API
|
|
5064
|
-
* shipped in the EmbedPDF PDFium fork. Values are written as UTF-16LE
|
|
5065
|
-
* strings via the same `mem.writeU16String` pattern the base writer
|
|
5066
|
-
* uses for /T.
|
|
5067
|
-
*/
|
|
5068
|
-
const KEY_SCHEMA_VERSION = "SchemaVersion";
|
|
5069
|
-
const KEY_USER_ID = "UserID";
|
|
5070
|
-
const KEY_GROUP_ID = "GroupID";
|
|
5071
|
-
const KEY_CREATED_BY = "CreatedBy";
|
|
5072
|
-
const KEY_UPDATED_BY = "UpdatedBy";
|
|
5073
|
-
/**
|
|
5074
|
-
* Stamp /EMBD_Metadata on a freshly created annotation. No-op if the
|
|
5075
|
-
* actor has no identity fields — the worker still emits the standard
|
|
5076
|
-
* /M timestamp via the base writer; only the vendor dictionary is
|
|
5077
|
-
* skipped.
|
|
5078
|
-
*
|
|
5079
|
-
* actor.userId → /UserID, /CreatedBy, /UpdatedBy
|
|
5080
|
-
* actor.groupId → /GroupID
|
|
5081
|
-
*
|
|
5082
|
-
* /SchemaVersion is written whenever any field is written.
|
|
5083
|
-
*/
|
|
5084
|
-
function applyEmbedMetadataOnCreate(fn, mem, annotPtr, actor) {
|
|
5085
|
-
if (!actor || !actor.userId && !actor.groupId) return;
|
|
5086
|
-
fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
|
|
5087
|
-
if (actor.userId) {
|
|
5088
|
-
setMetadataString(fn, mem, annotPtr, KEY_USER_ID, actor.userId);
|
|
5089
|
-
setMetadataString(fn, mem, annotPtr, KEY_CREATED_BY, actor.userId);
|
|
5090
|
-
setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
|
|
5091
|
-
}
|
|
5092
|
-
if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
|
|
5093
|
-
}
|
|
5094
|
-
/**
|
|
5095
|
-
* Apply /EMBD_Metadata refreshes on update. Two fields can change on
|
|
5096
|
-
* update; everything else (UserID, CreatedBy) is immutable.
|
|
5097
|
-
*
|
|
5098
|
-
* actor.userId → /UpdatedBy (refreshed every update where the
|
|
5099
|
-
* caller has an identity)
|
|
5100
|
-
* actor.groupId → /GroupID (reassigned only when the patch
|
|
5101
|
-
* explicitly contains a new groupId; route runs
|
|
5102
|
-
* `checkSetGroup` first)
|
|
5103
|
-
*
|
|
5104
|
-
* If the target annotation has no EMBD_Metadata yet (created before
|
|
5105
|
-
* the cloud started stamping, or by a non-cloud writer), we seed
|
|
5106
|
-
* /SchemaVersion so subsequent readers can find the marker.
|
|
5107
|
-
*
|
|
5108
|
-
* No-op when the actor has neither userId nor groupId.
|
|
5109
|
-
*/
|
|
5110
|
-
function applyEmbedMetadataOnUpdate(fn, mem, annotPtr, actor) {
|
|
5111
|
-
if (!actor || !actor.userId && !actor.groupId) return;
|
|
5112
|
-
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
|
|
5113
|
-
if (actor.userId) setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
|
|
5114
|
-
if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
|
|
5115
|
-
}
|
|
5116
|
-
function setMetadataString(fn, mem, annotPtr, key, value) {
|
|
5117
|
-
const ptr = mem.writeU16String(value);
|
|
5118
|
-
try {
|
|
5119
|
-
fn.EPDFAnnot_SetEmbedMetadataString(annotPtr, key, ptr);
|
|
5120
|
-
} finally {
|
|
5121
|
-
mem.free(ptr);
|
|
5122
|
-
}
|
|
5123
|
-
}
|
|
5124
|
-
//#endregion
|
|
5125
|
-
//#region src/features/annotations/internal/write/writeAnnotationTransformMetadata.ts
|
|
5126
|
-
/**
|
|
5127
|
-
* Write the EmbedPDF transform keys under /EMBD_Metadata. This is the seam
|
|
5128
|
-
* PDFium's native AP generator reads to bake a ROTATED appearance:
|
|
5129
|
-
*
|
|
5130
|
-
* /EMBD_Metadata <<
|
|
5131
|
-
* /Rotation 45 % degrees, PDF convention (CCW)
|
|
5132
|
-
* /UnrotatedRect [x0 y0 x1 y1] % BOX kinds only — the logical box
|
|
5133
|
-
* >>
|
|
5134
|
-
*
|
|
5135
|
-
* The family split (see the v3 plan):
|
|
5136
|
-
* - BOX kinds (square/circle/free-text): `/Rotation` + `/UnrotatedRect`.
|
|
5137
|
-
* With BOTH present the AP generator emits an `/AP /Matrix` that rotates a
|
|
5138
|
-
* box-sized appearance about the box centre, and `/Rect` is the enclosing
|
|
5139
|
-
* AABB. This is the portable, externally-correct rotation.
|
|
5140
|
-
* - VERTEX kinds (line/polyline/polygon/ink): `/Rotation` ONLY (advisory).
|
|
5141
|
-
* The points are already rotated (they are the visual), so a lone
|
|
5142
|
-
* `/Rotation` is INERT for the AP generator (it ignores `/Rotation` with no
|
|
5143
|
-
* `/UnrotatedRect`) — it just records the applied angle so EmbedPDF can
|
|
5144
|
-
* reconstruct an oriented selection box + offer reset.
|
|
5145
|
-
*
|
|
5146
|
-
* Both fields follow the engine-wide tri-state law ("a patch touches what it
|
|
5147
|
-
* states, preserves what it omits"):
|
|
5148
|
-
* - `undefined` → the key is UNTOUCHED (a rect-only move keeps its rotation).
|
|
5149
|
-
* - `null` or `0` → CLEAR just that key via `EPDFAnnot_ClearEmbedMetadataKey`
|
|
5150
|
-
* (0 ≡ no rotation is the canonical identity, not a sentinel; never clear
|
|
5151
|
-
* the whole dict — identity fields UserID/GroupID/CreatedBy/UpdatedBy must
|
|
5152
|
-
* survive).
|
|
5153
|
-
* - a value → SET the key. Setting a nonzero rotation with no
|
|
5154
|
-
* `/UnrotatedRect` (neither in the patch nor already on the annotation)
|
|
5155
|
-
* is an unsatisfiable state for the AP generator and throws `InvalidArg`.
|
|
5156
|
-
*
|
|
5157
|
-
* MUST run BEFORE `EPDFAnnot_GenerateAppearance` so the bake sees the rotation.
|
|
5158
|
-
* `/SchemaVersion` is seeded (stays 1) if this is the first key in the dict.
|
|
5159
|
-
*/
|
|
5160
|
-
const KEY_ROTATION = "Rotation";
|
|
5161
|
-
const KEY_UNROTATED_RECT = "UnrotatedRect";
|
|
5162
|
-
/** Seed `/SchemaVersion` when we are about to create the dict by writing the
|
|
5163
|
-
* first transform key, so the marker readers look for is always present. */
|
|
5164
|
-
function ensureSchemaVersion(fn, annotPtr) {
|
|
5165
|
-
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, "SchemaVersion", 1);
|
|
5166
|
-
}
|
|
5167
|
-
function setRotation(fn, annotPtr, rotation) {
|
|
5168
|
-
ensureSchemaVersion(fn, annotPtr);
|
|
5169
|
-
fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_ROTATION, rotation);
|
|
5170
|
-
}
|
|
5171
|
-
function setUnrotatedRect(fn, mem, annotPtr, rect) {
|
|
5172
|
-
ensureSchemaVersion(fn, annotPtr);
|
|
5173
|
-
const buf = mem.alloc(16);
|
|
5174
|
-
try {
|
|
5175
|
-
mem.poke(buf, "f32", rect.left, 0);
|
|
5176
|
-
mem.poke(buf, "f32", rect.top, 4);
|
|
5177
|
-
mem.poke(buf, "f32", rect.right, 8);
|
|
5178
|
-
mem.poke(buf, "f32", rect.bottom, 12);
|
|
5179
|
-
fn.EPDFAnnot_SetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT, buf);
|
|
5180
|
-
} finally {
|
|
5181
|
-
mem.free(buf);
|
|
5182
|
-
}
|
|
5183
|
-
}
|
|
5184
|
-
/**
|
|
5185
|
-
* Write transform metadata for a BOX kind (square/circle/free-text/stamp),
|
|
5186
|
-
* tri-state per field: `undefined` never touches the document (safe to call on
|
|
5187
|
-
* every patch), `null`/`0` clears, a value sets. A nonzero rotation with no
|
|
5188
|
-
* unrotated box anywhere (patch or annotation) is unsatisfiable — the AP
|
|
5189
|
-
* generator needs the pair — and throws instead of writing a broken state.
|
|
5190
|
-
*/
|
|
5191
|
-
function writeBoxTransformMetadata(fn, mem, annotPtr, t) {
|
|
5192
|
-
if (t.rotation !== void 0) if (t.rotation !== null && t.rotation !== 0) {
|
|
5193
|
-
if (!(t.unrotatedRect != null || t.unrotatedRect === void 0 && readAnnotationUnrotatedRect(fn, mem, annotPtr) != null)) throw new EngineError(EngineErrorCode.InvalidArg, "rotation requires unrotatedRect: supply the pair together (or rely on an existing /EMBD_Metadata/UnrotatedRect)");
|
|
5194
|
-
setRotation(fn, annotPtr, t.rotation);
|
|
5195
|
-
} else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
|
|
5196
|
-
if (t.unrotatedRect !== void 0) if (t.unrotatedRect !== null) setUnrotatedRect(fn, mem, annotPtr, t.unrotatedRect);
|
|
5197
|
-
else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5198
|
-
}
|
|
5199
|
-
/**
|
|
5200
|
-
* Write the advisory `/Rotation` scalar for a VERTEX kind
|
|
5201
|
-
* (line/polyline/polygon/ink), tri-state: `undefined` never touches the
|
|
5202
|
-
* document, `null`/`0` clears, a value sets. Whenever it does write, any
|
|
5203
|
-
* `/UnrotatedRect` is defensively cleared — that key is an impossible state on
|
|
5204
|
-
* a vertex kind and must never accidentally drive the AP generator.
|
|
5205
|
-
*/
|
|
5206
|
-
function writeVertexTransformMetadata(fn, annotPtr, t) {
|
|
5207
|
-
if (t.rotation === void 0) return;
|
|
5208
|
-
if (t.rotation !== null && t.rotation !== 0) setRotation(fn, annotPtr, t.rotation);
|
|
5209
|
-
else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
|
|
5210
|
-
fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5211
|
-
}
|
|
5212
|
-
//#endregion
|
|
5213
5250
|
//#region src/features/annotations/internal/write/writeFreeTextAnnotation.ts
|
|
5214
5251
|
/**
|
|
5215
5252
|
* Default `/DA` colour for free text: black (border + default text). Unlike
|
|
@@ -6970,17 +7007,35 @@ var RedactionApplier = class {
|
|
|
6970
7007
|
}
|
|
6971
7008
|
applyAllOnPage(pagePtr) {
|
|
6972
7009
|
const { fn, mem } = this.runtime;
|
|
7010
|
+
const hasRedactions = this.pageHasRedactAnnotations(pagePtr);
|
|
6973
7011
|
return withScratch(mem, 4, (countPtr) => {
|
|
6974
|
-
if (!fn.EPDFPage_ApplyRedactions(pagePtr, countPtr))
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
7012
|
+
if (!fn.EPDFPage_ApplyRedactions(pagePtr, countPtr)) {
|
|
7013
|
+
if (hasRedactions) throw new EngineError(EngineErrorCode.Unknown, "native page redaction apply failed");
|
|
7014
|
+
return {
|
|
7015
|
+
status: "unchanged",
|
|
7016
|
+
removed: 0
|
|
7017
|
+
};
|
|
7018
|
+
}
|
|
6978
7019
|
return {
|
|
6979
7020
|
status: "applied",
|
|
6980
7021
|
removed: readI32(mem, countPtr) >>> 0
|
|
6981
7022
|
};
|
|
6982
7023
|
});
|
|
6983
7024
|
}
|
|
7025
|
+
pageHasRedactAnnotations(pagePtr) {
|
|
7026
|
+
const { fn } = this.runtime;
|
|
7027
|
+
const count = fn.FPDFPage_GetAnnotCount(pagePtr);
|
|
7028
|
+
for (let index = 0; index < count; index++) {
|
|
7029
|
+
const annotPtr = fn.FPDFPage_GetAnnot(pagePtr, index);
|
|
7030
|
+
if (!annotPtr) continue;
|
|
7031
|
+
try {
|
|
7032
|
+
if (subtypeFromCode(fn.FPDFAnnot_GetSubtype(annotPtr)) === "redact") return true;
|
|
7033
|
+
} finally {
|
|
7034
|
+
fn.FPDFPage_CloseAnnot(annotPtr);
|
|
7035
|
+
}
|
|
7036
|
+
}
|
|
7037
|
+
return false;
|
|
7038
|
+
}
|
|
6984
7039
|
applyRefsOnPage(pagePtr, refs) {
|
|
6985
7040
|
const { fn, mem } = this.runtime;
|
|
6986
7041
|
let removed = 0;
|
|
@@ -7063,11 +7118,23 @@ function readPageText(fn, mem, textPagePtr, charCount) {
|
|
|
7063
7118
|
//#endregion
|
|
7064
7119
|
//#region src/features/geometry/PageGeometryReader.ts
|
|
7065
7120
|
/**
|
|
7121
|
+
* Mid-object orientation drift that forces a run split (~0.5°). Real content
|
|
7122
|
+
* keeps one char matrix per text object; this guards the exotic cases so a
|
|
7123
|
+
* run's `rotation` is always representative of every glyph in it.
|
|
7124
|
+
*/
|
|
7125
|
+
const ROTATION_SPLIT_TOLERANCE = .0087;
|
|
7126
|
+
/**
|
|
7066
7127
|
* Geometry-only text layout reader.
|
|
7067
7128
|
*
|
|
7068
7129
|
* Emits geometry in PDF user space (y-up edges) — the canonical engine
|
|
7069
7130
|
* geometry. The viewer converts to content/view space via the page geometry
|
|
7070
7131
|
* matrix; this reader applies NO Y-flip or device transform.
|
|
7132
|
+
*
|
|
7133
|
+
* One `EPDFText_GetCharGeometry` call per glyph supplies boxes, oriented
|
|
7134
|
+
* cells, and flags together; `buildRunsFromRawGlyphs` then groups glyphs
|
|
7135
|
+
* into the upright/rotated run union. Upright runs are byte-identical to the
|
|
7136
|
+
* pre-orientation reader (quads the native layer offers are deliberately
|
|
7137
|
+
* dropped — the dominant case never pays for orientation).
|
|
7071
7138
|
*/
|
|
7072
7139
|
var PageGeometryReader = class {
|
|
7073
7140
|
constructor(runtime, session) {
|
|
@@ -7076,7 +7143,7 @@ var PageGeometryReader = class {
|
|
|
7076
7143
|
}
|
|
7077
7144
|
read(pageObjectNumber, signal) {
|
|
7078
7145
|
throwIfAborted(signal);
|
|
7079
|
-
const { fn } = this.runtime;
|
|
7146
|
+
const { fn, mem } = this.runtime;
|
|
7080
7147
|
const pool = this.session.pagePool();
|
|
7081
7148
|
const pagePtr = pool.acquire(pageObjectNumber);
|
|
7082
7149
|
try {
|
|
@@ -7086,12 +7153,27 @@ var PageGeometryReader = class {
|
|
|
7086
7153
|
try {
|
|
7087
7154
|
throwIfAborted(signal);
|
|
7088
7155
|
const glyphCount = Math.max(fn.FPDFText_CountChars(textPagePtr), 0);
|
|
7089
|
-
const
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7156
|
+
const records = [];
|
|
7157
|
+
const geometryPtr = mem.alloc(EPDF_CHAR_GEOMETRY_LAYOUT.bytes);
|
|
7158
|
+
try {
|
|
7159
|
+
let prevObjectKey = null;
|
|
7160
|
+
for (let i = 0; i < glyphCount; i++) {
|
|
7161
|
+
throwIfAborted(signal);
|
|
7162
|
+
const objectKey = fn.FPDFText_GetTextObject(textPagePtr, i);
|
|
7163
|
+
const record = {
|
|
7164
|
+
objectKey,
|
|
7165
|
+
...this.readGlyphRaw(textPagePtr, i, geometryPtr)
|
|
7166
|
+
};
|
|
7167
|
+
if (objectKey !== prevObjectKey) {
|
|
7168
|
+
record.fontSize = fn.FPDFText_GetFontSize(textPagePtr, i);
|
|
7169
|
+
prevObjectKey = objectKey;
|
|
7170
|
+
}
|
|
7171
|
+
records.push(record);
|
|
7172
|
+
}
|
|
7173
|
+
} finally {
|
|
7174
|
+
mem.free(geometryPtr);
|
|
7093
7175
|
}
|
|
7094
|
-
return { runs:
|
|
7176
|
+
return { runs: buildRunsFromRawGlyphs(records) };
|
|
7095
7177
|
} finally {
|
|
7096
7178
|
fn.FPDFText_ClosePage(textPagePtr);
|
|
7097
7179
|
}
|
|
@@ -7099,96 +7181,206 @@ var PageGeometryReader = class {
|
|
|
7099
7181
|
pool.release(pageObjectNumber);
|
|
7100
7182
|
}
|
|
7101
7183
|
}
|
|
7102
|
-
|
|
7103
|
-
const { fn } = this.runtime;
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7184
|
+
readGlyphRaw(textPagePtr, charIndex, geometryPtr) {
|
|
7185
|
+
const { fn, mem } = this.runtime;
|
|
7186
|
+
if (!fn.EPDFText_GetCharGeometry(textPagePtr, charIndex, geometryPtr)) return emptyRawGlyph();
|
|
7187
|
+
const { offsets, flags: bits } = EPDF_CHAR_GEOMETRY_LAYOUT;
|
|
7188
|
+
const native = readI32(mem, geometryPtr, offsets.flags) >>> 0;
|
|
7189
|
+
if (native & bits.empty) return emptyRawGlyph();
|
|
7190
|
+
const upright = Boolean(native & bits.upright);
|
|
7191
|
+
const raw = {
|
|
7192
|
+
flags: native & bits.space ? 1 : 0,
|
|
7193
|
+
looseBox: normalizePdfRect(readRectF(mem, geometryPtr, offsets.looseBox)),
|
|
7194
|
+
upright,
|
|
7195
|
+
rotation: 0,
|
|
7196
|
+
ascentFlip: false
|
|
7197
|
+
};
|
|
7198
|
+
if (native & bits.hasTightBox) raw.tightBox = normalizePdfRect(readRectF(mem, geometryPtr, offsets.tightBox));
|
|
7199
|
+
if (native & bits.hasLooseQuad) {
|
|
7200
|
+
raw.looseQuad = readQuad(mem, geometryPtr, offsets.looseQuad);
|
|
7201
|
+
if (native & bits.hasTightQuad) raw.tightQuad = readQuad(mem, geometryPtr, offsets.tightQuad);
|
|
7202
|
+
if (!upright) {
|
|
7203
|
+
const a = readF32(mem, geometryPtr, offsets.matrix);
|
|
7204
|
+
const b = readF32(mem, geometryPtr, offsets.matrix + 4);
|
|
7205
|
+
const c = readF32(mem, geometryPtr, offsets.matrix + 8);
|
|
7206
|
+
const d = readF32(mem, geometryPtr, offsets.matrix + 12);
|
|
7207
|
+
raw.rotation = Math.atan2(b, a);
|
|
7208
|
+
raw.ascentFlip = a * d - b * c < 0;
|
|
7121
7209
|
}
|
|
7122
|
-
current.glyphs.push(toSlimGlyph(glyph));
|
|
7123
|
-
if (glyph.isEmpty) continue;
|
|
7124
|
-
bounds.left = Math.min(bounds.left, glyph.looseBox.left);
|
|
7125
|
-
bounds.bottom = Math.min(bounds.bottom, glyph.looseBox.bottom);
|
|
7126
|
-
bounds.right = Math.max(bounds.right, glyph.looseBox.right);
|
|
7127
|
-
bounds.top = Math.max(bounds.top, glyph.looseBox.top);
|
|
7128
|
-
current.rect = { ...bounds };
|
|
7129
7210
|
}
|
|
7130
|
-
return
|
|
7211
|
+
return raw;
|
|
7131
7212
|
}
|
|
7132
|
-
|
|
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
|
-
glyph.isSpace = fn.FPDFText_GetUnicode(textPagePtr, charIndex) === 32;
|
|
7170
|
-
return glyph;
|
|
7171
|
-
} finally {
|
|
7172
|
-
for (const ptr of ptrs) mem.free(ptr);
|
|
7213
|
+
};
|
|
7214
|
+
/**
|
|
7215
|
+
* Group per-glyph records into the run union. Pure — exported for tests.
|
|
7216
|
+
*
|
|
7217
|
+
* Runs split on text-object change (the legacy rule) and on orientation
|
|
7218
|
+
* change between classifiable glyphs (θ drift / mixed orientation). A run's
|
|
7219
|
+
* variant is decided by its FIRST classifiable glyph:
|
|
7220
|
+
* - empty glyphs never classify (they adopt the run's variant);
|
|
7221
|
+
* - real glyphs WITHOUT an oriented cell (singular matrix, synthesized
|
|
7222
|
+
* /ActualText pieces) classify as upright — box-only data degrades to
|
|
7223
|
+
* exactly the legacy behavior;
|
|
7224
|
+
* - real glyphs with a non-upright matrix classify as rotated.
|
|
7225
|
+
* Runs that never see a classifiable glyph emit as upright (degenerate,
|
|
7226
|
+
* legacy behavior).
|
|
7227
|
+
*/
|
|
7228
|
+
function buildRunsFromRawGlyphs(records) {
|
|
7229
|
+
const runs = [];
|
|
7230
|
+
let buffer = [];
|
|
7231
|
+
let charStart = 0;
|
|
7232
|
+
let fontSize;
|
|
7233
|
+
let objectKey = null;
|
|
7234
|
+
let cls;
|
|
7235
|
+
const flush = (nextStart) => {
|
|
7236
|
+
if (buffer.length > 0) {
|
|
7237
|
+
runs.push(materializeRun(buffer, charStart, fontSize, cls));
|
|
7238
|
+
buffer = [];
|
|
7239
|
+
cls = void 0;
|
|
7240
|
+
}
|
|
7241
|
+
charStart = nextStart;
|
|
7242
|
+
};
|
|
7243
|
+
for (let i = 0; i < records.length; i++) {
|
|
7244
|
+
const record = records[i];
|
|
7245
|
+
if (record.objectKey !== objectKey) {
|
|
7246
|
+
flush(i);
|
|
7247
|
+
objectKey = record.objectKey;
|
|
7248
|
+
fontSize = record.fontSize;
|
|
7173
7249
|
}
|
|
7250
|
+
const recordClass = classOf(record);
|
|
7251
|
+
if (recordClass) {
|
|
7252
|
+
if (cls && !sameClass(cls, recordClass)) flush(i);
|
|
7253
|
+
if (!cls) cls = recordClass;
|
|
7254
|
+
}
|
|
7255
|
+
buffer.push(record);
|
|
7256
|
+
}
|
|
7257
|
+
flush(records.length);
|
|
7258
|
+
return runs;
|
|
7259
|
+
}
|
|
7260
|
+
function classOf(record) {
|
|
7261
|
+
if (record.flags & 2) return void 0;
|
|
7262
|
+
if (record.looseQuad && !record.upright) return {
|
|
7263
|
+
upright: false,
|
|
7264
|
+
rotation: record.rotation,
|
|
7265
|
+
ascentFlip: record.ascentFlip
|
|
7266
|
+
};
|
|
7267
|
+
return { upright: true };
|
|
7268
|
+
}
|
|
7269
|
+
function sameClass(a, b) {
|
|
7270
|
+
if (a.upright || b.upright) return a.upright === b.upright;
|
|
7271
|
+
if (a.ascentFlip !== b.ascentFlip) return false;
|
|
7272
|
+
const delta = a.rotation - b.rotation;
|
|
7273
|
+
return Math.abs(Math.atan2(Math.sin(delta), Math.cos(delta))) <= ROTATION_SPLIT_TOLERANCE;
|
|
7274
|
+
}
|
|
7275
|
+
function materializeRun(buffer, charStart, fontSize, cls) {
|
|
7276
|
+
if (cls && !cls.upright) {
|
|
7277
|
+
const glyphs = buffer.map((g) => g.looseQuad ? {
|
|
7278
|
+
looseQuad: g.looseQuad,
|
|
7279
|
+
flags: g.flags,
|
|
7280
|
+
...g.tightQuad ? { tightQuad: g.tightQuad } : {}
|
|
7281
|
+
} : {
|
|
7282
|
+
looseQuad: zeroQuad(),
|
|
7283
|
+
flags: g.flags
|
|
7284
|
+
});
|
|
7285
|
+
return {
|
|
7286
|
+
rect: runBounds(buffer, (g) => g.looseQuad ? pdfQuadBounds(g.looseQuad) : ZERO_RECT),
|
|
7287
|
+
charStart,
|
|
7288
|
+
glyphs,
|
|
7289
|
+
rotation: cls.rotation,
|
|
7290
|
+
ascentFlip: cls.ascentFlip,
|
|
7291
|
+
...fontSize !== void 0 ? { fontSize } : {}
|
|
7292
|
+
};
|
|
7174
7293
|
}
|
|
7294
|
+
const glyphs = buffer.map((g) => ({
|
|
7295
|
+
looseBox: g.looseBox,
|
|
7296
|
+
flags: g.flags,
|
|
7297
|
+
...g.tightBox ? { tightBox: g.tightBox } : {}
|
|
7298
|
+
}));
|
|
7299
|
+
return {
|
|
7300
|
+
rect: runBounds(buffer, (g) => g.looseBox),
|
|
7301
|
+
charStart,
|
|
7302
|
+
glyphs,
|
|
7303
|
+
...fontSize !== void 0 ? { fontSize } : {}
|
|
7304
|
+
};
|
|
7305
|
+
}
|
|
7306
|
+
/** Legacy run-rect algorithm: seed from the first glyph, expand on non-empty. */
|
|
7307
|
+
function runBounds(buffer, boundsOf) {
|
|
7308
|
+
const seed = boundsOf(buffer[0]);
|
|
7309
|
+
const rect = {
|
|
7310
|
+
left: seed.left,
|
|
7311
|
+
bottom: seed.bottom,
|
|
7312
|
+
right: seed.right,
|
|
7313
|
+
top: seed.top
|
|
7314
|
+
};
|
|
7315
|
+
for (const g of buffer) {
|
|
7316
|
+
if (g.flags & 2) continue;
|
|
7317
|
+
const b = boundsOf(g);
|
|
7318
|
+
rect.left = Math.min(rect.left, b.left);
|
|
7319
|
+
rect.bottom = Math.min(rect.bottom, b.bottom);
|
|
7320
|
+
rect.right = Math.max(rect.right, b.right);
|
|
7321
|
+
rect.top = Math.max(rect.top, b.top);
|
|
7322
|
+
}
|
|
7323
|
+
return rect;
|
|
7324
|
+
}
|
|
7325
|
+
const ZERO_RECT = {
|
|
7326
|
+
left: 0,
|
|
7327
|
+
bottom: 0,
|
|
7328
|
+
right: 0,
|
|
7329
|
+
top: 0
|
|
7175
7330
|
};
|
|
7176
|
-
function
|
|
7331
|
+
function zeroQuad() {
|
|
7177
7332
|
return {
|
|
7333
|
+
p1: {
|
|
7334
|
+
x: 0,
|
|
7335
|
+
y: 0
|
|
7336
|
+
},
|
|
7337
|
+
p2: {
|
|
7338
|
+
x: 0,
|
|
7339
|
+
y: 0
|
|
7340
|
+
},
|
|
7341
|
+
p3: {
|
|
7342
|
+
x: 0,
|
|
7343
|
+
y: 0
|
|
7344
|
+
},
|
|
7345
|
+
p4: {
|
|
7346
|
+
x: 0,
|
|
7347
|
+
y: 0
|
|
7348
|
+
}
|
|
7349
|
+
};
|
|
7350
|
+
}
|
|
7351
|
+
function emptyRawGlyph() {
|
|
7352
|
+
return {
|
|
7353
|
+
flags: 2,
|
|
7178
7354
|
looseBox: {
|
|
7179
7355
|
left: 0,
|
|
7180
7356
|
bottom: 0,
|
|
7181
7357
|
right: 0,
|
|
7182
7358
|
top: 0
|
|
7183
7359
|
},
|
|
7184
|
-
|
|
7360
|
+
upright: true,
|
|
7361
|
+
rotation: 0,
|
|
7362
|
+
ascentFlip: false
|
|
7185
7363
|
};
|
|
7186
7364
|
}
|
|
7187
|
-
function
|
|
7365
|
+
function readQuad(mem, ptr, byteOffset) {
|
|
7366
|
+
const f = (offset) => readF32(mem, ptr, byteOffset + offset);
|
|
7188
7367
|
return {
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7368
|
+
p1: {
|
|
7369
|
+
x: f(0),
|
|
7370
|
+
y: f(4)
|
|
7371
|
+
},
|
|
7372
|
+
p2: {
|
|
7373
|
+
x: f(8),
|
|
7374
|
+
y: f(12)
|
|
7375
|
+
},
|
|
7376
|
+
p3: {
|
|
7377
|
+
x: f(16),
|
|
7378
|
+
y: f(20)
|
|
7379
|
+
},
|
|
7380
|
+
p4: {
|
|
7381
|
+
x: f(24),
|
|
7382
|
+
y: f(28)
|
|
7383
|
+
}
|
|
7192
7384
|
};
|
|
7193
7385
|
}
|
|
7194
7386
|
//#endregion
|
|
@@ -8712,12 +8904,12 @@ var SearchReader = class {
|
|
|
8712
8904
|
else if (query.matchCase || query.matchDiacritics) ranges = matchLiteral(foldText(corpus.original, foldOptionsFor(query)), query);
|
|
8713
8905
|
else ranges = matchLiteral(corpus, query);
|
|
8714
8906
|
if (ranges.length > 0) {
|
|
8715
|
-
const
|
|
8907
|
+
const layout = buildPageTextLayout(new PageGeometryReader(this.runtime, this.session).read(pon, signal));
|
|
8716
8908
|
for (const range of ranges) matches.push({
|
|
8717
8909
|
pageObjectNumber: pon,
|
|
8718
8910
|
charStart: range.start,
|
|
8719
8911
|
charCount: range.length,
|
|
8720
|
-
|
|
8912
|
+
segments: textSegmentsForRange(layout, range.start, range.length),
|
|
8721
8913
|
...mode === "full" ? { snippet: buildSnippet(corpus.original, range) } : {}
|
|
8722
8914
|
});
|
|
8723
8915
|
}
|
|
@@ -9846,6 +10038,6 @@ function passwordRequiredProbe() {
|
|
|
9846
10038
|
};
|
|
9847
10039
|
}
|
|
9848
10040
|
//#endregion
|
|
9849
|
-
export { ALL_STANDARD_SECURITY_PERMISSIONS, ActionReadBudgetTracker, AnnotationAppearanceReader, AnnotationMutator, AnnotationReader, AttachmentMutator, AttachmentReader, BaseDocumentRegistry, CloseStack, DocumentActionsReader, PagesMutator as DocumentPagesMutator, PagesMutator, DocumentSaver, DocumentSession, EventHub, FontRegistrar, FormMutator, FormReader, FormsEffectsApplier, LocalRevisionAuthority, MetadataMutator, MetadataReader, PageGeometryReader, PagesReader as PageLayoutReader, PagesReader, PagePtrPool, PageRenderReader as PageRasterReader, PageRenderReader, PageTextReader, PagesExtractor, PagesFlattener, PagesInserter, RawAnnotationReader, RedactionApplier, SearchReader, SecurityReader, SessionEventPublisher, WorkerHost, computeMutationImpact, destroyLibrary, disposeFormModel, ensureInitialized, formatPdfDate, generateUuid, hasAllStandardSecurityPermissions, normalizeU32, openFatMemoryDocument, openLayerDocument, pdfDateToIso, readActionModel, readAttachmentFileInfo, throwIfAborted, writeAttachmentFilePayload };
|
|
10041
|
+
export { ALL_STANDARD_SECURITY_PERMISSIONS, ActionReadBudgetTracker, AnnotationAppearanceReader, AnnotationMutator, AnnotationReader, AttachmentMutator, AttachmentReader, BaseDocumentRegistry, CloseStack, DocumentActionsReader, PagesMutator as DocumentPagesMutator, PagesMutator, DocumentSaver, DocumentSession, EPDF_CHAR_GEOMETRY_LAYOUT, EventHub, FontRegistrar, FormMutator, FormReader, FormsEffectsApplier, LocalRevisionAuthority, MetadataMutator, MetadataReader, PageGeometryReader, PagesReader as PageLayoutReader, PagesReader, PagePtrPool, PageRenderReader as PageRasterReader, PageRenderReader, PageTextReader, PagesExtractor, PagesFlattener, PagesInserter, RawAnnotationReader, RedactionApplier, SearchReader, SecurityReader, SessionEventPublisher, WorkerHost, buildRunsFromRawGlyphs, computeMutationImpact, destroyLibrary, disposeFormModel, ensureInitialized, formatPdfDate, generateUuid, hasAllStandardSecurityPermissions, normalizeU32, openFatMemoryDocument, openLayerDocument, pdfDateToIso, readActionModel, readAttachmentFileInfo, throwIfAborted, writeAttachmentFilePayload };
|
|
9850
10042
|
|
|
9851
10043
|
//# sourceMappingURL=index.js.map
|