@embedpdf/engine-services 3.0.0-next.1 → 3.0.0-next.11
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 +1391 -710
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +156 -22
- package/dist/index.d.ts +156 -22
- package/dist/index.js +1391 -712
- 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`. */
|
|
@@ -1189,11 +1212,143 @@ function readI32(mem, ptr, byteOffset = 0) {
|
|
|
1189
1212
|
return Number(mem.peek(ptr, "i32", byteOffset));
|
|
1190
1213
|
}
|
|
1191
1214
|
//#endregion
|
|
1215
|
+
//#region src/features/destinations/destinationViewCodes.ts
|
|
1216
|
+
/**
|
|
1217
|
+
* `PDFDEST_VIEW_*` fit-type codes (public/fpdf_doc.h) — the shared
|
|
1218
|
+
* vocabulary of `FPDFDest_GetView` (read) and `EPDFDest_CreateView`
|
|
1219
|
+
* (write). XYZ is listed for completeness but never goes through
|
|
1220
|
+
* CreateView (it has its own null-aware constructor).
|
|
1221
|
+
*/
|
|
1222
|
+
const DEST_VIEW = {
|
|
1223
|
+
unknown: 0,
|
|
1224
|
+
xyz: 1,
|
|
1225
|
+
fit: 2,
|
|
1226
|
+
fitH: 3,
|
|
1227
|
+
fitV: 4,
|
|
1228
|
+
fitR: 5,
|
|
1229
|
+
fitB: 6,
|
|
1230
|
+
fitBH: 7,
|
|
1231
|
+
fitBV: 8
|
|
1232
|
+
};
|
|
1233
|
+
/** Wire destination kind → PDFDEST_VIEW code, for the non-XYZ kinds. */
|
|
1234
|
+
const VIEW_CODE_BY_KIND = {
|
|
1235
|
+
fit: DEST_VIEW.fit,
|
|
1236
|
+
fitH: DEST_VIEW.fitH,
|
|
1237
|
+
fitV: DEST_VIEW.fitV,
|
|
1238
|
+
fitR: DEST_VIEW.fitR,
|
|
1239
|
+
fitB: DEST_VIEW.fitB,
|
|
1240
|
+
fitBH: DEST_VIEW.fitBH,
|
|
1241
|
+
fitBV: DEST_VIEW.fitBV
|
|
1242
|
+
};
|
|
1243
|
+
//#endregion
|
|
1244
|
+
//#region src/features/destinations/readDestination.ts
|
|
1245
|
+
const MAX_VIEW_PARAMS = 4;
|
|
1246
|
+
/**
|
|
1247
|
+
* Materialise one explicit `PdfDestination` from a live `destPtr`. PDFium
|
|
1248
|
+
* already resolved named destinations before handing us the pointer, so
|
|
1249
|
+
* only explicit arrays arrive here (the rule `PdfDestination` documents).
|
|
1250
|
+
*
|
|
1251
|
+
* Returns `null` when the destination is unreadable: no visible indirect page
|
|
1252
|
+
* in the document, or an unknown fit type. Coordinates stay raw PDF user
|
|
1253
|
+
* space — no conversion at this layer.
|
|
1254
|
+
*
|
|
1255
|
+
* Shared by the link reader, `goto` action-node payloads, and the document
|
|
1256
|
+
* `openDestination`; the outline/bookmark port reuses it too.
|
|
1257
|
+
*/
|
|
1258
|
+
function readDestination(fn, mem, docPtr, destPtr) {
|
|
1259
|
+
const pageObjectNumber = fn.EPDFDest_GetPageObjectNumber(docPtr, destPtr);
|
|
1260
|
+
if (pageObjectNumber === 0) return null;
|
|
1261
|
+
const view = withScratchN(mem, [4, MAX_VIEW_PARAMS * 4], ([countPtr, paramsPtr]) => {
|
|
1262
|
+
const code = fn.FPDFDest_GetView(destPtr, countPtr, paramsPtr);
|
|
1263
|
+
const count = Math.min(readI32(mem, countPtr), MAX_VIEW_PARAMS);
|
|
1264
|
+
const params = [];
|
|
1265
|
+
for (let i = 0; i < count; i++) params.push(readF32(mem, paramsPtr, i * 4));
|
|
1266
|
+
return {
|
|
1267
|
+
code,
|
|
1268
|
+
params
|
|
1269
|
+
};
|
|
1270
|
+
});
|
|
1271
|
+
const at = (i) => i < view.params.length ? view.params[i] : null;
|
|
1272
|
+
switch (view.code) {
|
|
1273
|
+
case DEST_VIEW.xyz: return {
|
|
1274
|
+
kind: "xyz",
|
|
1275
|
+
pageObjectNumber,
|
|
1276
|
+
...readXyzLocation(fn, mem, destPtr)
|
|
1277
|
+
};
|
|
1278
|
+
case DEST_VIEW.fit: return {
|
|
1279
|
+
kind: "fit",
|
|
1280
|
+
pageObjectNumber
|
|
1281
|
+
};
|
|
1282
|
+
case DEST_VIEW.fitH: return {
|
|
1283
|
+
kind: "fitH",
|
|
1284
|
+
pageObjectNumber,
|
|
1285
|
+
top: at(0)
|
|
1286
|
+
};
|
|
1287
|
+
case DEST_VIEW.fitV: return {
|
|
1288
|
+
kind: "fitV",
|
|
1289
|
+
pageObjectNumber,
|
|
1290
|
+
left: at(0)
|
|
1291
|
+
};
|
|
1292
|
+
case DEST_VIEW.fitR:
|
|
1293
|
+
if (view.params.length < 4) return {
|
|
1294
|
+
kind: "fit",
|
|
1295
|
+
pageObjectNumber
|
|
1296
|
+
};
|
|
1297
|
+
return {
|
|
1298
|
+
kind: "fitR",
|
|
1299
|
+
pageObjectNumber,
|
|
1300
|
+
left: view.params[0],
|
|
1301
|
+
bottom: view.params[1],
|
|
1302
|
+
right: view.params[2],
|
|
1303
|
+
top: view.params[3]
|
|
1304
|
+
};
|
|
1305
|
+
case DEST_VIEW.fitB: return {
|
|
1306
|
+
kind: "fitB",
|
|
1307
|
+
pageObjectNumber
|
|
1308
|
+
};
|
|
1309
|
+
case DEST_VIEW.fitBH: return {
|
|
1310
|
+
kind: "fitBH",
|
|
1311
|
+
pageObjectNumber,
|
|
1312
|
+
top: at(0)
|
|
1313
|
+
};
|
|
1314
|
+
case DEST_VIEW.fitBV: return {
|
|
1315
|
+
kind: "fitBV",
|
|
1316
|
+
pageObjectNumber,
|
|
1317
|
+
left: at(0)
|
|
1318
|
+
};
|
|
1319
|
+
default: return null;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
/** `/XYZ` null-aware axes via `FPDFDest_GetLocationInPage` (FPDF_BOOL is 4 bytes). */
|
|
1323
|
+
function readXyzLocation(fn, mem, destPtr) {
|
|
1324
|
+
return withScratchN(mem, [
|
|
1325
|
+
4,
|
|
1326
|
+
4,
|
|
1327
|
+
4,
|
|
1328
|
+
4,
|
|
1329
|
+
4,
|
|
1330
|
+
4
|
|
1331
|
+
], ([hasLeft, hasTop, hasZoom, left, top, zoom]) => {
|
|
1332
|
+
if (!fn.FPDFDest_GetLocationInPage(destPtr, hasLeft, hasTop, hasZoom, left, top, zoom)) return {
|
|
1333
|
+
left: null,
|
|
1334
|
+
top: null,
|
|
1335
|
+
zoom: null
|
|
1336
|
+
};
|
|
1337
|
+
return {
|
|
1338
|
+
left: readI32(mem, hasLeft) ? readF32(mem, left) : null,
|
|
1339
|
+
top: readI32(mem, hasTop) ? readF32(mem, top) : null,
|
|
1340
|
+
zoom: readI32(mem, hasZoom) ? readF32(mem, zoom) : null
|
|
1341
|
+
};
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
//#endregion
|
|
1192
1345
|
//#region src/features/actions/ActionModelReader.ts
|
|
1193
1346
|
const DEFAULT_ACTION_READ_BUDGET = {
|
|
1194
1347
|
maxModels: 512,
|
|
1195
1348
|
maxNodes: 16384,
|
|
1196
|
-
maxScriptCodeUnits: 16e6
|
|
1349
|
+
maxScriptCodeUnits: 16e6,
|
|
1350
|
+
maxTargetEntries: 4096,
|
|
1351
|
+
maxPayloadCodeUnits: 262144
|
|
1197
1352
|
};
|
|
1198
1353
|
const ACTION_TYPE_BY_CODE = {
|
|
1199
1354
|
0: "unknown",
|
|
@@ -1221,22 +1376,20 @@ const WARNING_MALFORMED_NEXT = 2;
|
|
|
1221
1376
|
const WARNING_INCOMPLETE = 4;
|
|
1222
1377
|
const INVALID_NODE_U32 = 4294967295;
|
|
1223
1378
|
/** Decode one `EPDF_ACTION_TYPE_*` code — the shared vocabulary consumers
|
|
1224
|
-
*
|
|
1379
|
+
* use so action-shaped surfaces cannot drift. */
|
|
1225
1380
|
function actionTypeFromCode(code) {
|
|
1226
1381
|
return ACTION_TYPE_BY_CODE[code] ?? "unknown";
|
|
1227
1382
|
}
|
|
1228
|
-
/**
|
|
1229
|
-
*
|
|
1230
|
-
function isValidActionNodeId(raw) {
|
|
1231
|
-
return raw !== -1 && raw >>> 0 !== INVALID_NODE_U32;
|
|
1232
|
-
}
|
|
1233
|
-
/** Mutable aggregate budget shared by every action model in one read job. */
|
|
1383
|
+
/** Mutable aggregate budget shared by every action model in one read job.
|
|
1384
|
+
* Payload budgets are RESERVED before the backing buffer is allocated. */
|
|
1234
1385
|
var ActionReadBudgetTracker = class {
|
|
1235
1386
|
constructor(limits = DEFAULT_ACTION_READ_BUDGET) {
|
|
1236
1387
|
this.limits = limits;
|
|
1237
1388
|
_defineProperty(this, "models", 0);
|
|
1238
1389
|
_defineProperty(this, "nodes", 0);
|
|
1239
1390
|
_defineProperty(this, "scriptCodeUnits", 0);
|
|
1391
|
+
_defineProperty(this, "targetEntries", 0);
|
|
1392
|
+
_defineProperty(this, "payloadCodeUnits", 0);
|
|
1240
1393
|
}
|
|
1241
1394
|
addModel(nodeCount) {
|
|
1242
1395
|
this.models += 1;
|
|
@@ -1247,11 +1400,23 @@ var ActionReadBudgetTracker = class {
|
|
|
1247
1400
|
this.scriptCodeUnits += script.length;
|
|
1248
1401
|
this.assertWithinBudget();
|
|
1249
1402
|
}
|
|
1403
|
+
/** Charge target entries BEFORE the target array is walked. */
|
|
1404
|
+
reserveTargets(count) {
|
|
1405
|
+
this.targetEntries += count;
|
|
1406
|
+
this.assertWithinBudget();
|
|
1407
|
+
}
|
|
1408
|
+
/** Charge payload text BEFORE the scratch buffer for it is allocated. */
|
|
1409
|
+
reservePayloadBytes(length) {
|
|
1410
|
+
this.payloadCodeUnits += length;
|
|
1411
|
+
this.assertWithinBudget();
|
|
1412
|
+
}
|
|
1250
1413
|
assertWithinBudget() {
|
|
1251
|
-
if (this.models > this.limits.maxModels || this.nodes > this.limits.maxNodes || this.scriptCodeUnits > this.limits.maxScriptCodeUnits) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "aggregate PDF action read budget exceeded", { details: {
|
|
1414
|
+
if (this.models > this.limits.maxModels || this.nodes > this.limits.maxNodes || this.scriptCodeUnits > this.limits.maxScriptCodeUnits || this.targetEntries > this.limits.maxTargetEntries || this.payloadCodeUnits > this.limits.maxPayloadCodeUnits) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.MalformedPdf, "aggregate PDF action read budget exceeded", { details: {
|
|
1252
1415
|
models: this.models,
|
|
1253
1416
|
nodes: this.nodes,
|
|
1254
1417
|
scriptCodeUnits: this.scriptCodeUnits,
|
|
1418
|
+
targetEntries: this.targetEntries,
|
|
1419
|
+
payloadCodeUnits: this.payloadCodeUnits,
|
|
1255
1420
|
limits: this.limits
|
|
1256
1421
|
} });
|
|
1257
1422
|
}
|
|
@@ -1259,8 +1424,17 @@ var ActionReadBudgetTracker = class {
|
|
|
1259
1424
|
/**
|
|
1260
1425
|
* Convert and close one detached native action model. This is the only
|
|
1261
1426
|
* native-model walker used by document, page, field, and annotation reads.
|
|
1427
|
+
*
|
|
1428
|
+
* `docPtr` resolves `goto` destinations (page identity + named-destination
|
|
1429
|
+
* lookup); every caller reads within an open document session.
|
|
1430
|
+
*
|
|
1431
|
+
* Payload rule: a node whose payload cannot be materialised (a goto without
|
|
1432
|
+
* a resolvable destination, a javascript node with no `/JS`, a withheld
|
|
1433
|
+
* target list) degrades to `{ type: 'unknown' }` with its original `/S` on
|
|
1434
|
+
* `subtype`, and the tree gains the `'payload-dropped'` warning — impossible
|
|
1435
|
+
* states stay unrepresentable while malformed documents stay readable.
|
|
1262
1436
|
*/
|
|
1263
|
-
function readActionModel(fn, mem, modelPtr, budget) {
|
|
1437
|
+
function readActionModel(fn, mem, docPtr, modelPtr, budget) {
|
|
1264
1438
|
if (modelPtr === _embedpdf_engine_runtime.NULL_PTR) return null;
|
|
1265
1439
|
try {
|
|
1266
1440
|
const nodeCount = fn.EPDFAction_GetNodeCount(modelPtr);
|
|
@@ -1270,13 +1444,61 @@ function readActionModel(fn, mem, modelPtr, budget) {
|
|
|
1270
1444
|
const incomplete = !fn.EPDFAction_IsComplete(modelPtr);
|
|
1271
1445
|
const rootId = normalizeOptionalNodeId(fn.EPDFAction_GetRootNode(modelPtr), nodeCount);
|
|
1272
1446
|
const visiting = /* @__PURE__ */ new Set();
|
|
1447
|
+
let payloadDropped = false;
|
|
1448
|
+
/** Probe the two-call length first and charge the budget BEFORE the
|
|
1449
|
+
* scratch buffer for the value is allocated. */
|
|
1450
|
+
const readPayloadString = (call) => {
|
|
1451
|
+
const length = call(_embedpdf_engine_runtime.NULL_PTR, 0);
|
|
1452
|
+
if (length <= 0) return null;
|
|
1453
|
+
budget.reservePayloadBytes(length);
|
|
1454
|
+
return readUtf8String(mem, call);
|
|
1455
|
+
};
|
|
1456
|
+
const readOutBool = (read) => withScratchN(mem, [4], ([outPtr]) => read(outPtr) ? readI32(mem, outPtr) !== 0 : null);
|
|
1457
|
+
const readTargetObjectNumber = (nodeId, index) => withScratchN(mem, [4], ([outPtr]) => {
|
|
1458
|
+
if (!fn.EPDFAction_GetNodeTargetObjectNumber(modelPtr, nodeId, index, outPtr)) return null;
|
|
1459
|
+
const value = readI32(mem, outPtr) >>> 0;
|
|
1460
|
+
return value === 0 ? null : value;
|
|
1461
|
+
});
|
|
1462
|
+
const readTargets = (nodeId, count) => {
|
|
1463
|
+
const targets = [];
|
|
1464
|
+
for (let index = 0; index < count; index++) {
|
|
1465
|
+
const name = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeTargetName(modelPtr, nodeId, index, buf, capacity));
|
|
1466
|
+
if (name !== null) {
|
|
1467
|
+
targets.push({
|
|
1468
|
+
kind: "name",
|
|
1469
|
+
name
|
|
1470
|
+
});
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
const objectNumber = readTargetObjectNumber(nodeId, index);
|
|
1474
|
+
if (objectNumber === null) return null;
|
|
1475
|
+
targets.push({
|
|
1476
|
+
kind: "objectNumber",
|
|
1477
|
+
objectNumber
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
return targets;
|
|
1481
|
+
};
|
|
1482
|
+
const readResetFormState = (nodeId) => withScratchN(mem, [4, 4], ([hasFieldsPtr, excludePtr]) => {
|
|
1483
|
+
if (!fn.EPDFAction_GetNodeResetForm(modelPtr, nodeId, hasFieldsPtr, excludePtr)) return null;
|
|
1484
|
+
return {
|
|
1485
|
+
hasFields: readI32(mem, hasFieldsPtr) !== 0,
|
|
1486
|
+
exclude: readI32(mem, excludePtr) !== 0
|
|
1487
|
+
};
|
|
1488
|
+
});
|
|
1489
|
+
const readSubmitFormState = (nodeId) => withScratchN(mem, [4, 4], ([hasFieldsPtr, flagsPtr]) => {
|
|
1490
|
+
if (!fn.EPDFAction_GetNodeSubmitForm(modelPtr, nodeId, hasFieldsPtr, flagsPtr)) return null;
|
|
1491
|
+
return {
|
|
1492
|
+
hasFields: readI32(mem, hasFieldsPtr) !== 0,
|
|
1493
|
+
flags: readI32(mem, flagsPtr) >>> 0
|
|
1494
|
+
};
|
|
1495
|
+
});
|
|
1273
1496
|
const readNode = (nodeId) => {
|
|
1274
1497
|
if (visiting.has(nodeId)) throw malformedActionModel("native action model contains a cycle", { nodeId });
|
|
1275
1498
|
visiting.add(nodeId);
|
|
1276
1499
|
try {
|
|
1500
|
+
const type = actionTypeFromCode(fn.EPDFAction_GetNodeType(modelPtr, nodeId));
|
|
1277
1501
|
const subtype = readUtf8String(mem, (buf, capacity) => fn.EPDFAction_GetNodeSubtype(modelPtr, nodeId, buf, capacity)) ?? "";
|
|
1278
|
-
const script = fn.EPDFAction_NodeHasJavaScript(modelPtr, nodeId) ? readUtf16String(mem, (buf, capacity) => fn.EPDFAction_GetNodeJavaScript(modelPtr, nodeId, buf, capacity)) ?? "" : void 0;
|
|
1279
|
-
if (script !== void 0) budget.addScript(script);
|
|
1280
1502
|
const nextCount = fn.EPDFAction_GetNextCount(modelPtr, nodeId);
|
|
1281
1503
|
if (!Number.isInteger(nextCount) || nextCount < 0) throw malformedActionModel("invalid action child count", {
|
|
1282
1504
|
nodeId,
|
|
@@ -1287,21 +1509,157 @@ function readActionModel(fn, mem, modelPtr, budget) {
|
|
|
1287
1509
|
const childId = normalizeNodeId(fn.EPDFAction_GetNextAt(modelPtr, nodeId, index), nodeCount);
|
|
1288
1510
|
next.push(readNode(childId));
|
|
1289
1511
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1512
|
+
const degraded = () => {
|
|
1513
|
+
payloadDropped = true;
|
|
1514
|
+
return {
|
|
1515
|
+
type: "unknown",
|
|
1516
|
+
subtype,
|
|
1517
|
+
next
|
|
1518
|
+
};
|
|
1295
1519
|
};
|
|
1520
|
+
switch (type) {
|
|
1521
|
+
case "javascript": {
|
|
1522
|
+
if (!fn.EPDFAction_NodeHasJavaScript(modelPtr, nodeId)) return degraded();
|
|
1523
|
+
const script = readUtf16String(mem, (buf, capacity) => fn.EPDFAction_GetNodeJavaScript(modelPtr, nodeId, buf, capacity)) ?? "";
|
|
1524
|
+
budget.addScript(script);
|
|
1525
|
+
return {
|
|
1526
|
+
type,
|
|
1527
|
+
subtype,
|
|
1528
|
+
script,
|
|
1529
|
+
next
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
case "rendition": {
|
|
1533
|
+
if (!fn.EPDFAction_NodeHasJavaScript(modelPtr, nodeId)) return {
|
|
1534
|
+
type,
|
|
1535
|
+
subtype,
|
|
1536
|
+
next
|
|
1537
|
+
};
|
|
1538
|
+
const script = readUtf16String(mem, (buf, capacity) => fn.EPDFAction_GetNodeJavaScript(modelPtr, nodeId, buf, capacity)) ?? "";
|
|
1539
|
+
budget.addScript(script);
|
|
1540
|
+
return {
|
|
1541
|
+
type,
|
|
1542
|
+
subtype,
|
|
1543
|
+
script,
|
|
1544
|
+
next
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
case "goto": {
|
|
1548
|
+
const destPtr = fn.EPDFAction_GetNodeDest(docPtr, modelPtr, nodeId);
|
|
1549
|
+
const destination = destPtr !== _embedpdf_engine_runtime.NULL_PTR ? readDestination(fn, mem, docPtr, destPtr) : null;
|
|
1550
|
+
return destination ? {
|
|
1551
|
+
type,
|
|
1552
|
+
subtype,
|
|
1553
|
+
destination,
|
|
1554
|
+
next
|
|
1555
|
+
} : degraded();
|
|
1556
|
+
}
|
|
1557
|
+
case "uri": {
|
|
1558
|
+
const uri = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeURI(docPtr, modelPtr, nodeId, buf, capacity));
|
|
1559
|
+
if (uri === null) return degraded();
|
|
1560
|
+
return {
|
|
1561
|
+
type,
|
|
1562
|
+
subtype,
|
|
1563
|
+
uri,
|
|
1564
|
+
isMap: readOutBool((outPtr) => fn.EPDFAction_GetNodeURIIsMap(modelPtr, nodeId, outPtr)) ?? false,
|
|
1565
|
+
next
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
case "named": {
|
|
1569
|
+
const name = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeName(modelPtr, nodeId, buf, capacity));
|
|
1570
|
+
return name === null ? degraded() : {
|
|
1571
|
+
type,
|
|
1572
|
+
subtype,
|
|
1573
|
+
name,
|
|
1574
|
+
next
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
case "hide": {
|
|
1578
|
+
const count = fn.EPDFAction_GetNodeTargetCount(modelPtr, nodeId);
|
|
1579
|
+
if (!Number.isInteger(count) || count < 0) return degraded();
|
|
1580
|
+
budget.reserveTargets(count);
|
|
1581
|
+
const targets = readTargets(nodeId, count);
|
|
1582
|
+
if (targets === null) return degraded();
|
|
1583
|
+
return {
|
|
1584
|
+
type,
|
|
1585
|
+
subtype,
|
|
1586
|
+
targets,
|
|
1587
|
+
hide: readOutBool((outPtr) => fn.EPDFAction_GetNodeHideFlag(modelPtr, nodeId, outPtr)) ?? true,
|
|
1588
|
+
next
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
case "reset-form": {
|
|
1592
|
+
const state = readResetFormState(nodeId);
|
|
1593
|
+
const count = fn.EPDFAction_GetNodeTargetCount(modelPtr, nodeId);
|
|
1594
|
+
if (state === null || !Number.isInteger(count) || count < 0) return degraded();
|
|
1595
|
+
budget.reserveTargets(count);
|
|
1596
|
+
const fields = state.hasFields ? readTargets(nodeId, count) : null;
|
|
1597
|
+
if (state.hasFields && fields === null) return degraded();
|
|
1598
|
+
return {
|
|
1599
|
+
type,
|
|
1600
|
+
subtype,
|
|
1601
|
+
fields,
|
|
1602
|
+
exclude: state.exclude,
|
|
1603
|
+
next
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
case "submit-form": {
|
|
1607
|
+
if (typeof fn.EPDFAction_GetNodeSubmitForm !== "function") return {
|
|
1608
|
+
type,
|
|
1609
|
+
subtype,
|
|
1610
|
+
next
|
|
1611
|
+
};
|
|
1612
|
+
const state = readSubmitFormState(nodeId);
|
|
1613
|
+
if (state === null) return degraded();
|
|
1614
|
+
const url = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeSubmitFormURL(modelPtr, nodeId, buf, capacity));
|
|
1615
|
+
if (url === null) return degraded();
|
|
1616
|
+
const count = fn.EPDFAction_GetNodeTargetCount(modelPtr, nodeId);
|
|
1617
|
+
if (!Number.isInteger(count) || count < 0) return degraded();
|
|
1618
|
+
budget.reserveTargets(count);
|
|
1619
|
+
const fields = state.hasFields ? readTargets(nodeId, count) : null;
|
|
1620
|
+
if (state.hasFields && fields === null) return degraded();
|
|
1621
|
+
const charSet = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeSubmitFormCharSet(modelPtr, nodeId, buf, capacity));
|
|
1622
|
+
return {
|
|
1623
|
+
type,
|
|
1624
|
+
subtype,
|
|
1625
|
+
payload: {
|
|
1626
|
+
url,
|
|
1627
|
+
fields,
|
|
1628
|
+
flags: (0, _embedpdf_engine_core_runtime.decodeSubmitFormFlags)(state.flags),
|
|
1629
|
+
...charSet === null ? {} : { charSet }
|
|
1630
|
+
},
|
|
1631
|
+
next
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
case "goto-remote":
|
|
1635
|
+
case "goto-embedded":
|
|
1636
|
+
case "launch": {
|
|
1637
|
+
const filePath = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeFilePath(modelPtr, nodeId, buf, capacity));
|
|
1638
|
+
return filePath === null ? degraded() : {
|
|
1639
|
+
type,
|
|
1640
|
+
subtype,
|
|
1641
|
+
filePath,
|
|
1642
|
+
next
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
default: return {
|
|
1646
|
+
type,
|
|
1647
|
+
subtype,
|
|
1648
|
+
next
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1296
1651
|
} finally {
|
|
1297
1652
|
visiting.delete(nodeId);
|
|
1298
1653
|
}
|
|
1299
1654
|
};
|
|
1655
|
+
const root = rootId === null ? null : readNode(rootId);
|
|
1656
|
+
const warnings = warningsFromFlags(warningFlags, incomplete);
|
|
1657
|
+
if (payloadDropped) warnings.push("payload-dropped");
|
|
1300
1658
|
return {
|
|
1301
|
-
root
|
|
1659
|
+
root,
|
|
1302
1660
|
incomplete,
|
|
1303
1661
|
warningFlags,
|
|
1304
|
-
warnings
|
|
1662
|
+
warnings
|
|
1305
1663
|
};
|
|
1306
1664
|
} finally {
|
|
1307
1665
|
fn.EPDFAction_CloseModel(modelPtr);
|
|
@@ -1381,8 +1739,8 @@ var PagesReader = class {
|
|
|
1381
1739
|
};
|
|
1382
1740
|
function readPageActions(fn, mem, docPtr, pageObjectNumber, budget) {
|
|
1383
1741
|
const actions = {};
|
|
1384
|
-
const open = readActionModel(fn, mem, fn.EPDFDoc_GetPageActionModel(docPtr, pageObjectNumber, 0), budget);
|
|
1385
|
-
const close = readActionModel(fn, mem, fn.EPDFDoc_GetPageActionModel(docPtr, pageObjectNumber, 1), budget);
|
|
1742
|
+
const open = readActionModel(fn, mem, docPtr, fn.EPDFDoc_GetPageActionModel(docPtr, pageObjectNumber, 0), budget);
|
|
1743
|
+
const close = readActionModel(fn, mem, docPtr, fn.EPDFDoc_GetPageActionModel(docPtr, pageObjectNumber, 1), budget);
|
|
1386
1744
|
if (open) actions.open = open;
|
|
1387
1745
|
if (close) actions.close = close;
|
|
1388
1746
|
return open || close ? actions : void 0;
|
|
@@ -1706,6 +2064,42 @@ var PagesInserter = class {
|
|
|
1706
2064
|
cache: null
|
|
1707
2065
|
};
|
|
1708
2066
|
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Create `count` blank pages of `size` at `destIndex`. Same mutation
|
|
2069
|
+
* contract as `insert`, but native creation (`FPDFPage_New`) instead of a
|
|
2070
|
+
* deep copy: no source document, so none of the retain-until-close
|
|
2071
|
+
* lifetime hazard above. Each page gets an empty content stream
|
|
2072
|
+
* (`FPDFPage_GenerateContent`) so the saved PDF renders identically in
|
|
2073
|
+
* third-party viewers.
|
|
2074
|
+
*/
|
|
2075
|
+
insertBlank(spec, destIndex, signal) {
|
|
2076
|
+
throwIfAborted(signal);
|
|
2077
|
+
const { size } = spec;
|
|
2078
|
+
const count = spec.count ?? 1;
|
|
2079
|
+
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}`);
|
|
2080
|
+
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}]`);
|
|
2081
|
+
const { fn } = this.runtime;
|
|
2082
|
+
const destPtr = this.session.requireDocPtr();
|
|
2083
|
+
const beforeCount = fn.FPDF_GetPageCount(destPtr);
|
|
2084
|
+
const at = destIndex ?? beforeCount;
|
|
2085
|
+
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}]`);
|
|
2086
|
+
for (let i = 0; i < count; i++) {
|
|
2087
|
+
const pagePtr = fn.FPDFPage_New(destPtr, at + i, size.width, size.height);
|
|
2088
|
+
if (!pagePtr) {
|
|
2089
|
+
for (let j = 0; j < i; j++) fn.FPDFPage_Delete(destPtr, at);
|
|
2090
|
+
throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, `FPDFPage_New rejected page ${i + 1}/${count} at index ${at + i}`);
|
|
2091
|
+
}
|
|
2092
|
+
fn.FPDFPage_GenerateContent(pagePtr);
|
|
2093
|
+
fn.FPDF_ClosePage(pagePtr);
|
|
2094
|
+
}
|
|
2095
|
+
this.session.refreshPageRegistry();
|
|
2096
|
+
const layout = new PagesReader(this.runtime, this.session).read(signal);
|
|
2097
|
+
return {
|
|
2098
|
+
insertedPageObjectNumbers: layout.pages.slice(at, at + count).map((page) => page.pageObjectNumber),
|
|
2099
|
+
layout,
|
|
2100
|
+
cache: null
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
1709
2103
|
};
|
|
1710
2104
|
//#endregion
|
|
1711
2105
|
//#region src/features/annotations/internal/read/annotationReadContext.ts
|
|
@@ -2154,6 +2548,49 @@ function readQuadPoints(fn, mem, annotPtr) {
|
|
|
2154
2548
|
});
|
|
2155
2549
|
}
|
|
2156
2550
|
//#endregion
|
|
2551
|
+
//#region src/features/annotations/internal/read/readAnnotationTransformMetadata.ts
|
|
2552
|
+
/**
|
|
2553
|
+
* Read-side twin of `writers/.../writeAnnotationTransformMetadata.ts`. Decodes
|
|
2554
|
+
* the EmbedPDF rotation keys from /EMBD_Metadata back onto the DTO:
|
|
2555
|
+
*
|
|
2556
|
+
* - `Rotation` — degrees, PDF convention. Returned for EVERY rotatable
|
|
2557
|
+
* kind (box AND vertex); the plugin converts it back to its CW-content
|
|
2558
|
+
* `rot` once at the repository seam.
|
|
2559
|
+
* - `UnrotatedRect` — the logical box. BOX kinds only (square/circle/
|
|
2560
|
+
* free-text); a vertex annotation never carries one (its points are the
|
|
2561
|
+
* visual), so vertex readers don't ask for it.
|
|
2562
|
+
*
|
|
2563
|
+
* Both are absent on un-rotated annotations (and on everything authored before
|
|
2564
|
+
* rotation shipped), so both reads return `undefined` and the DTO field stays
|
|
2565
|
+
* unset.
|
|
2566
|
+
*/
|
|
2567
|
+
const KEY_ROTATION$1 = "Rotation";
|
|
2568
|
+
const KEY_UNROTATED_RECT$1 = "UnrotatedRect";
|
|
2569
|
+
/** The advisory/portable rotation scalar (degrees, PDF convention), or
|
|
2570
|
+
* `undefined` when absent or zero. */
|
|
2571
|
+
function readAnnotationRotation(fn, mem, annotPtr) {
|
|
2572
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
|
|
2573
|
+
return withScratch(mem, 4, (buf) => {
|
|
2574
|
+
if (!fn.EPDFAnnot_GetEmbedMetadataNumber(annotPtr, KEY_ROTATION$1, buf)) return void 0;
|
|
2575
|
+
const v = readF32(mem, buf);
|
|
2576
|
+
return v ? v : void 0;
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
/** The logical (unrotated) box of a rotated BOX annotation, or `undefined`. */
|
|
2580
|
+
function readAnnotationUnrotatedRect(fn, mem, annotPtr) {
|
|
2581
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) return void 0;
|
|
2582
|
+
return withScratch(mem, 16, (buf) => {
|
|
2583
|
+
if (!fn.EPDFAnnot_GetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT$1, buf)) return void 0;
|
|
2584
|
+
const r = readRectF(mem, buf);
|
|
2585
|
+
return {
|
|
2586
|
+
left: r.left,
|
|
2587
|
+
bottom: r.bottom,
|
|
2588
|
+
right: r.right,
|
|
2589
|
+
top: r.top
|
|
2590
|
+
};
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
//#endregion
|
|
2157
2594
|
//#region src/features/annotations/internal/textEditIntent.ts
|
|
2158
2595
|
/** Adobe-compatible `/IT` names, normalized at the engine-core boundary. */
|
|
2159
2596
|
const IT_REPLACE = "Replace";
|
|
@@ -2188,13 +2625,17 @@ function readCaret(fn, mem, annotPtr, base) {
|
|
|
2188
2625
|
const opacity = ca == null ? 1 : Math.max(0, Math.min(1, ca));
|
|
2189
2626
|
const rd = readRectangleDifferences(fn, mem, annotPtr);
|
|
2190
2627
|
const intent = caretIntentFromName(readIntent(fn, mem, annotPtr));
|
|
2628
|
+
const rotation = readAnnotationRotation(fn, mem, annotPtr);
|
|
2629
|
+
const unrotatedRect = readAnnotationUnrotatedRect(fn, mem, annotPtr);
|
|
2191
2630
|
return {
|
|
2192
2631
|
...base,
|
|
2193
2632
|
subtype: "caret",
|
|
2194
2633
|
intent,
|
|
2195
2634
|
color,
|
|
2196
2635
|
opacity,
|
|
2197
|
-
rectDifferences: rd
|
|
2636
|
+
rectDifferences: rd,
|
|
2637
|
+
...rotation != null ? { rotation } : {},
|
|
2638
|
+
...unrotatedRect ? { unrotatedRect } : {}
|
|
2198
2639
|
};
|
|
2199
2640
|
}
|
|
2200
2641
|
//#endregion
|
|
@@ -2612,49 +3053,6 @@ function readFilledStyleExtras(fn, mem, annotPtr) {
|
|
|
2612
3053
|
};
|
|
2613
3054
|
}
|
|
2614
3055
|
//#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
3056
|
//#region src/features/annotations/internal/read/readFreeTextAnnotation.ts
|
|
2659
3057
|
/** Default `/DA` colour (black) when an annotation has no default appearance. */
|
|
2660
3058
|
const DEFAULT_FREETEXT_COLOR$1 = {
|
|
@@ -2760,167 +3158,67 @@ function readLine(fn, mem, annotPtr, base) {
|
|
|
2760
3158
|
};
|
|
2761
3159
|
}
|
|
2762
3160
|
//#endregion
|
|
2763
|
-
//#region src/features/annotations/internal/destinationViewCodes.ts
|
|
2764
|
-
/**
|
|
2765
|
-
* `PDFDEST_VIEW_*` fit-type codes (public/fpdf_doc.h) — the shared
|
|
2766
|
-
* vocabulary of `FPDFDest_GetView` (read) and `EPDFDest_CreateView`
|
|
2767
|
-
* (write). XYZ is listed for completeness but never goes through
|
|
2768
|
-
* CreateView (it has its own null-aware constructor).
|
|
2769
|
-
*/
|
|
2770
|
-
const DEST_VIEW = {
|
|
2771
|
-
unknown: 0,
|
|
2772
|
-
xyz: 1,
|
|
2773
|
-
fit: 2,
|
|
2774
|
-
fitH: 3,
|
|
2775
|
-
fitV: 4,
|
|
2776
|
-
fitR: 5,
|
|
2777
|
-
fitB: 6,
|
|
2778
|
-
fitBH: 7,
|
|
2779
|
-
fitBV: 8
|
|
2780
|
-
};
|
|
2781
|
-
/** Wire destination kind → PDFDEST_VIEW code, for the non-XYZ kinds. */
|
|
2782
|
-
const VIEW_CODE_BY_KIND = {
|
|
2783
|
-
fit: DEST_VIEW.fit,
|
|
2784
|
-
fitH: DEST_VIEW.fitH,
|
|
2785
|
-
fitV: DEST_VIEW.fitV,
|
|
2786
|
-
fitR: DEST_VIEW.fitR,
|
|
2787
|
-
fitB: DEST_VIEW.fitB,
|
|
2788
|
-
fitBH: DEST_VIEW.fitBH,
|
|
2789
|
-
fitBV: DEST_VIEW.fitBV
|
|
2790
|
-
};
|
|
2791
|
-
//#endregion
|
|
2792
|
-
//#region src/features/annotations/internal/read/readDestination.ts
|
|
2793
|
-
const MAX_VIEW_PARAMS = 4;
|
|
2794
|
-
/**
|
|
2795
|
-
* Materialise one explicit `PdfDestination` from a live `destPtr`. PDFium
|
|
2796
|
-
* already resolved named destinations before handing us the pointer, so
|
|
2797
|
-
* only explicit arrays arrive here (the rule `PdfDestination` documents).
|
|
2798
|
-
*
|
|
2799
|
-
* Returns `null` when the destination is unreadable: no visible indirect page
|
|
2800
|
-
* in the document, or an unknown fit type. Coordinates stay raw PDF user
|
|
2801
|
-
* space — no conversion at this layer.
|
|
2802
|
-
*
|
|
2803
|
-
* Shared by the link reader today; the outline/bookmark port reuses it.
|
|
2804
|
-
*/
|
|
2805
|
-
function readDestination(fn, mem, docPtr, destPtr) {
|
|
2806
|
-
const pageObjectNumber = fn.EPDFDest_GetPageObjectNumber(docPtr, destPtr);
|
|
2807
|
-
if (pageObjectNumber === 0) return null;
|
|
2808
|
-
const view = withScratchN(mem, [4, MAX_VIEW_PARAMS * 4], ([countPtr, paramsPtr]) => {
|
|
2809
|
-
const code = fn.FPDFDest_GetView(destPtr, countPtr, paramsPtr);
|
|
2810
|
-
const count = Math.min(readI32(mem, countPtr), MAX_VIEW_PARAMS);
|
|
2811
|
-
const params = [];
|
|
2812
|
-
for (let i = 0; i < count; i++) params.push(readF32(mem, paramsPtr, i * 4));
|
|
2813
|
-
return {
|
|
2814
|
-
code,
|
|
2815
|
-
params
|
|
2816
|
-
};
|
|
2817
|
-
});
|
|
2818
|
-
const at = (i) => i < view.params.length ? view.params[i] : null;
|
|
2819
|
-
switch (view.code) {
|
|
2820
|
-
case DEST_VIEW.xyz: return {
|
|
2821
|
-
kind: "xyz",
|
|
2822
|
-
pageObjectNumber,
|
|
2823
|
-
...readXyzLocation(fn, mem, destPtr)
|
|
2824
|
-
};
|
|
2825
|
-
case DEST_VIEW.fit: return {
|
|
2826
|
-
kind: "fit",
|
|
2827
|
-
pageObjectNumber
|
|
2828
|
-
};
|
|
2829
|
-
case DEST_VIEW.fitH: return {
|
|
2830
|
-
kind: "fitH",
|
|
2831
|
-
pageObjectNumber,
|
|
2832
|
-
top: at(0)
|
|
2833
|
-
};
|
|
2834
|
-
case DEST_VIEW.fitV: return {
|
|
2835
|
-
kind: "fitV",
|
|
2836
|
-
pageObjectNumber,
|
|
2837
|
-
left: at(0)
|
|
2838
|
-
};
|
|
2839
|
-
case DEST_VIEW.fitR:
|
|
2840
|
-
if (view.params.length < 4) return {
|
|
2841
|
-
kind: "fit",
|
|
2842
|
-
pageObjectNumber
|
|
2843
|
-
};
|
|
2844
|
-
return {
|
|
2845
|
-
kind: "fitR",
|
|
2846
|
-
pageObjectNumber,
|
|
2847
|
-
left: view.params[0],
|
|
2848
|
-
bottom: view.params[1],
|
|
2849
|
-
right: view.params[2],
|
|
2850
|
-
top: view.params[3]
|
|
2851
|
-
};
|
|
2852
|
-
case DEST_VIEW.fitB: return {
|
|
2853
|
-
kind: "fitB",
|
|
2854
|
-
pageObjectNumber
|
|
2855
|
-
};
|
|
2856
|
-
case DEST_VIEW.fitBH: return {
|
|
2857
|
-
kind: "fitBH",
|
|
2858
|
-
pageObjectNumber,
|
|
2859
|
-
top: at(0)
|
|
2860
|
-
};
|
|
2861
|
-
case DEST_VIEW.fitBV: return {
|
|
2862
|
-
kind: "fitBV",
|
|
2863
|
-
pageObjectNumber,
|
|
2864
|
-
left: at(0)
|
|
2865
|
-
};
|
|
2866
|
-
default: return null;
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
/** `/XYZ` null-aware axes via `FPDFDest_GetLocationInPage` (FPDF_BOOL is 4 bytes). */
|
|
2870
|
-
function readXyzLocation(fn, mem, destPtr) {
|
|
2871
|
-
return withScratchN(mem, [
|
|
2872
|
-
4,
|
|
2873
|
-
4,
|
|
2874
|
-
4,
|
|
2875
|
-
4,
|
|
2876
|
-
4,
|
|
2877
|
-
4
|
|
2878
|
-
], ([hasLeft, hasTop, hasZoom, left, top, zoom]) => {
|
|
2879
|
-
if (!fn.FPDFDest_GetLocationInPage(destPtr, hasLeft, hasTop, hasZoom, left, top, zoom)) return {
|
|
2880
|
-
left: null,
|
|
2881
|
-
top: null,
|
|
2882
|
-
zoom: null
|
|
2883
|
-
};
|
|
2884
|
-
return {
|
|
2885
|
-
left: readI32(mem, hasLeft) ? readF32(mem, left) : null,
|
|
2886
|
-
top: readI32(mem, hasTop) ? readF32(mem, top) : null,
|
|
2887
|
-
zoom: readI32(mem, hasZoom) ? readF32(mem, zoom) : null
|
|
2888
|
-
};
|
|
2889
|
-
});
|
|
2890
|
-
}
|
|
2891
|
-
//#endregion
|
|
2892
3161
|
//#region src/features/annotations/internal/read/readLinkAnnotation.ts
|
|
2893
3162
|
/**
|
|
2894
3163
|
* Link reader: rect/flags/relationship ride the base (a link grouped to an
|
|
2895
3164
|
* annotation is plain `inReplyTo` + `replyType: 'group'`); this module only
|
|
2896
3165
|
* materialises the normalized target.
|
|
2897
3166
|
*
|
|
2898
|
-
* ONE truth,
|
|
2899
|
-
*
|
|
2900
|
-
*
|
|
2901
|
-
*
|
|
2902
|
-
* and Named-blind) is deliberately not consulted; `FPDFLink_GetDest`
|
|
2903
|
-
* remains only for the data-only direct `/Dest`, which is not an action.
|
|
3167
|
+
* ONE truth, one projection: the target is a pure function of the SAME
|
|
3168
|
+
* payload-carrying action tree the scripting plane reads
|
|
3169
|
+
* (`base.actions.activate`) — no second native read exists any more, so the
|
|
3170
|
+
* two action-shaped surfaces cannot drift by construction.
|
|
2904
3171
|
*
|
|
2905
|
-
* `/A`
|
|
2906
|
-
*
|
|
2907
|
-
*
|
|
3172
|
+
* `/A` precedence, pinned: an activate action that exists but cannot be
|
|
3173
|
+
* executed (an `incomplete` tree, a degraded/unreadable root) projects
|
|
3174
|
+
* `unsupported` — a broken action is a dead link, never an invitation to
|
|
3175
|
+
* guess. The direct `/Dest` (which is data, not an action) is consulted
|
|
3176
|
+
* ONLY when no `/A` exists at all.
|
|
2908
3177
|
*/
|
|
2909
3178
|
function readLink(fn, mem, annotPtr, base, _rawSubtypeCode, ctx) {
|
|
2910
3179
|
return {
|
|
2911
3180
|
...base,
|
|
2912
3181
|
subtype: "link",
|
|
2913
|
-
target: readLinkTarget(fn, mem, annotPtr, ctx)
|
|
3182
|
+
target: readLinkTarget(fn, mem, annotPtr, base, ctx)
|
|
2914
3183
|
};
|
|
2915
3184
|
}
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
3185
|
+
/**
|
|
3186
|
+
* Project the navigation view from a payload-carrying activate tree. Pure —
|
|
3187
|
+
* shared with any consumer that wants the root-level navigation reading of a
|
|
3188
|
+
* tree (the link plugin's no-actions-plugin fallback uses the same law).
|
|
3189
|
+
*/
|
|
3190
|
+
function linkTargetFromActionTree(tree) {
|
|
3191
|
+
if (tree.incomplete) return { kind: "unsupported" };
|
|
3192
|
+
const root = tree.root;
|
|
3193
|
+
if (!root) return { kind: "unsupported" };
|
|
3194
|
+
switch (root.type) {
|
|
3195
|
+
case "goto": return {
|
|
3196
|
+
kind: "goto",
|
|
3197
|
+
destination: root.destination
|
|
3198
|
+
};
|
|
3199
|
+
case "uri": return {
|
|
3200
|
+
kind: "uri",
|
|
3201
|
+
uri: root.uri
|
|
3202
|
+
};
|
|
3203
|
+
case "goto-remote": return {
|
|
3204
|
+
kind: "goto-remote",
|
|
3205
|
+
file: root.filePath
|
|
3206
|
+
};
|
|
3207
|
+
case "launch": return {
|
|
3208
|
+
kind: "launch",
|
|
3209
|
+
path: root.filePath
|
|
3210
|
+
};
|
|
3211
|
+
case "javascript": return { kind: "javascript" };
|
|
3212
|
+
case "named": return {
|
|
3213
|
+
kind: "named",
|
|
3214
|
+
name: root.name
|
|
3215
|
+
};
|
|
3216
|
+
default: return { kind: "unsupported" };
|
|
2923
3217
|
}
|
|
3218
|
+
}
|
|
3219
|
+
function readLinkTarget(fn, mem, annotPtr, base, ctx) {
|
|
3220
|
+
const activate = base.actions?.activate;
|
|
3221
|
+
if (activate) return linkTargetFromActionTree(activate);
|
|
2924
3222
|
const linkPtr = fn.FPDFAnnot_GetLink(annotPtr);
|
|
2925
3223
|
if (!linkPtr) return null;
|
|
2926
3224
|
const destPtr = fn.FPDFLink_GetDest(ctx.docPtr, linkPtr);
|
|
@@ -2931,43 +3229,6 @@ function readLinkTarget(fn, mem, annotPtr, ctx) {
|
|
|
2931
3229
|
destination
|
|
2932
3230
|
} : { kind: "unsupported" };
|
|
2933
3231
|
}
|
|
2934
|
-
/** Classify the root action node and fetch its payload — the projection. */
|
|
2935
|
-
function readRootTarget(fn, mem, modelPtr, node, ctx) {
|
|
2936
|
-
switch (actionTypeFromCode(fn.EPDFAction_GetNodeType(modelPtr, node))) {
|
|
2937
|
-
case "goto": {
|
|
2938
|
-
const destPtr = fn.EPDFAction_GetNodeDest(ctx.docPtr, modelPtr, node);
|
|
2939
|
-
const destination = destPtr ? readDestination(fn, mem, ctx.docPtr, destPtr) : null;
|
|
2940
|
-
return destination ? {
|
|
2941
|
-
kind: "goto",
|
|
2942
|
-
destination
|
|
2943
|
-
} : { kind: "unsupported" };
|
|
2944
|
-
}
|
|
2945
|
-
case "uri": {
|
|
2946
|
-
const uri = readUtf8String(mem, (buf, capacity) => fn.EPDFAction_GetNodeURI(ctx.docPtr, modelPtr, node, buf, capacity));
|
|
2947
|
-
return uri == null ? { kind: "unsupported" } : {
|
|
2948
|
-
kind: "uri",
|
|
2949
|
-
uri
|
|
2950
|
-
};
|
|
2951
|
-
}
|
|
2952
|
-
case "goto-remote": return {
|
|
2953
|
-
kind: "goto-remote",
|
|
2954
|
-
file: readUtf8String(mem, (buf, capacity) => fn.EPDFAction_GetNodeFilePath(modelPtr, node, buf, capacity)) ?? ""
|
|
2955
|
-
};
|
|
2956
|
-
case "launch": return {
|
|
2957
|
-
kind: "launch",
|
|
2958
|
-
path: readUtf8String(mem, (buf, capacity) => fn.EPDFAction_GetNodeFilePath(modelPtr, node, buf, capacity)) ?? ""
|
|
2959
|
-
};
|
|
2960
|
-
case "javascript": return { kind: "javascript" };
|
|
2961
|
-
case "named": {
|
|
2962
|
-
const name = readUtf8String(mem, (buf, capacity) => fn.EPDFAction_GetNodeName(modelPtr, node, buf, capacity));
|
|
2963
|
-
return name == null ? { kind: "unsupported" } : {
|
|
2964
|
-
kind: "named",
|
|
2965
|
-
name
|
|
2966
|
-
};
|
|
2967
|
-
}
|
|
2968
|
-
default: return { kind: "unsupported" };
|
|
2969
|
-
}
|
|
2970
|
-
}
|
|
2971
3232
|
//#endregion
|
|
2972
3233
|
//#region src/features/annotations/internal/read/readRedactAnnotation.ts
|
|
2973
3234
|
/** Default `/C` marking outline (red) — the redaction marking convention and
|
|
@@ -3107,6 +3368,47 @@ function readStampName(fn, mem, annotPtr) {
|
|
|
3107
3368
|
return readAnnotString(fn, mem, annotPtr, "Name");
|
|
3108
3369
|
}
|
|
3109
3370
|
//#endregion
|
|
3371
|
+
//#region src/features/annotations/internal/annotationState.ts
|
|
3372
|
+
/**
|
|
3373
|
+
* `/State` + `/StateModel` spelling maps — ISO 32000 §12.5.6.3, Table 174.
|
|
3374
|
+
*
|
|
3375
|
+
* Known values are wire-stable lowercase (the house vocabulary — same
|
|
3376
|
+
* rationale as `AnnotationReplyType` / `AnnotationBorderStyle`); the PDF
|
|
3377
|
+
* stores the capitalized Table 174 spellings. Unknown values — custom
|
|
3378
|
+
* Acrobat state models and their states — pass through VERBATIM in both
|
|
3379
|
+
* directions, so foreign review workflows survive a round trip.
|
|
3380
|
+
*/
|
|
3381
|
+
const STATE_MODEL_TO_PDF = {
|
|
3382
|
+
review: "Review",
|
|
3383
|
+
marked: "Marked"
|
|
3384
|
+
};
|
|
3385
|
+
const STATE_TO_PDF = {
|
|
3386
|
+
accepted: "Accepted",
|
|
3387
|
+
rejected: "Rejected",
|
|
3388
|
+
cancelled: "Cancelled",
|
|
3389
|
+
completed: "Completed",
|
|
3390
|
+
none: "None",
|
|
3391
|
+
marked: "Marked",
|
|
3392
|
+
unmarked: "Unmarked"
|
|
3393
|
+
};
|
|
3394
|
+
const PDF_TO_STATE_MODEL = invert(STATE_MODEL_TO_PDF);
|
|
3395
|
+
const PDF_TO_STATE = invert(STATE_TO_PDF);
|
|
3396
|
+
function stateModelToPdf(value) {
|
|
3397
|
+
return STATE_MODEL_TO_PDF[value] ?? value;
|
|
3398
|
+
}
|
|
3399
|
+
function stateModelFromPdf(value) {
|
|
3400
|
+
return PDF_TO_STATE_MODEL[value] ?? value;
|
|
3401
|
+
}
|
|
3402
|
+
function stateToPdf(value) {
|
|
3403
|
+
return STATE_TO_PDF[value] ?? value;
|
|
3404
|
+
}
|
|
3405
|
+
function stateFromPdf(value) {
|
|
3406
|
+
return PDF_TO_STATE[value] ?? value;
|
|
3407
|
+
}
|
|
3408
|
+
function invert(map) {
|
|
3409
|
+
return Object.fromEntries(Object.entries(map).map(([wire, pdf]) => [pdf, wire]));
|
|
3410
|
+
}
|
|
3411
|
+
//#endregion
|
|
3110
3412
|
//#region src/features/annotations/internal/read/readTextAnnotation.ts
|
|
3111
3413
|
/** Default `/C` — matches the generator's yellow note fill and the writer default. */
|
|
3112
3414
|
const DEFAULT_NOTE_COLOR$1 = {
|
|
@@ -3121,12 +3423,16 @@ function readText(fn, mem, annotPtr, base) {
|
|
|
3121
3423
|
const ca = readAnnotOpacity(fn, mem, annotPtr);
|
|
3122
3424
|
const opacity = ca == null ? 1 : Math.max(0, Math.min(1, ca));
|
|
3123
3425
|
const icon = NOTE_CODE_TO_ICON[fn.EPDFAnnot_GetName(annotPtr)] ?? DEFAULT_NOTE_ICON;
|
|
3426
|
+
const stateRaw = readAnnotString(fn, mem, annotPtr, "State");
|
|
3427
|
+
const stateModelRaw = readAnnotString(fn, mem, annotPtr, "StateModel");
|
|
3124
3428
|
return {
|
|
3125
3429
|
...base,
|
|
3126
3430
|
subtype: "text",
|
|
3127
3431
|
icon,
|
|
3128
3432
|
color,
|
|
3129
|
-
opacity
|
|
3433
|
+
opacity,
|
|
3434
|
+
state: stateRaw === null ? null : stateFromPdf(stateRaw),
|
|
3435
|
+
stateModel: stateModelRaw === null ? null : stateModelFromPdf(stateModelRaw)
|
|
3130
3436
|
};
|
|
3131
3437
|
}
|
|
3132
3438
|
//#endregion
|
|
@@ -3432,7 +3738,7 @@ function readOptions(runtime, model, fieldIndex) {
|
|
|
3432
3738
|
return options;
|
|
3433
3739
|
}
|
|
3434
3740
|
/** Read one field of the native model into its per-family DTO. */
|
|
3435
|
-
function readFieldAt(runtime, model, fieldIndex, actionBudget = new ActionReadBudgetTracker()) {
|
|
3741
|
+
function readFieldAt(runtime, model, fieldIndex, docPtr, actionBudget = new ActionReadBudgetTracker()) {
|
|
3436
3742
|
const { fn } = runtime;
|
|
3437
3743
|
const fieldObjectNumber = fn.EPDFForm_GetFieldObjNum(model, fieldIndex);
|
|
3438
3744
|
const name = readWide(runtime, (buf, cap) => fn.EPDFForm_GetFieldName(model, fieldIndex, buf, cap));
|
|
@@ -3440,7 +3746,7 @@ function readFieldAt(runtime, model, fieldIndex, actionBudget = new ActionReadBu
|
|
|
3440
3746
|
const family = FAMILY_BY_CODE[fn.EPDFForm_GetFieldFamily(model, fieldIndex)] ?? "unknown";
|
|
3441
3747
|
const valueEntry = readValueEntry(runtime, model, fieldIndex, false);
|
|
3442
3748
|
const defaultValueEntry = readValueEntry(runtime, model, fieldIndex, true);
|
|
3443
|
-
const actions = readFieldActions(runtime, model, fieldIndex, actionBudget);
|
|
3749
|
+
const actions = readFieldActions(runtime, model, fieldIndex, docPtr, actionBudget);
|
|
3444
3750
|
const base = {
|
|
3445
3751
|
ref: fieldObjectNumber > 0 ? {
|
|
3446
3752
|
kind: "objectNumber",
|
|
@@ -3539,12 +3845,12 @@ function readFieldAt(runtime, model, fieldIndex, actionBudget = new ActionReadBu
|
|
|
3539
3845
|
}
|
|
3540
3846
|
}
|
|
3541
3847
|
/** Read the whole native model into a detached {@link FormSnapshot}. */
|
|
3542
|
-
function readFormSnapshot(runtime, model) {
|
|
3848
|
+
function readFormSnapshot(runtime, model, docPtr) {
|
|
3543
3849
|
const { fn } = runtime;
|
|
3544
3850
|
const count = fn.EPDFForm_CountFields(model);
|
|
3545
3851
|
const fields = [];
|
|
3546
3852
|
const actionBudget = new ActionReadBudgetTracker();
|
|
3547
|
-
for (let i = 0; i < count; i++) fields.push(readFieldAt(runtime, model, i, actionBudget));
|
|
3853
|
+
for (let i = 0; i < count; i++) fields.push(readFieldAt(runtime, model, i, docPtr, actionBudget));
|
|
3548
3854
|
const calculationOrder = [];
|
|
3549
3855
|
const calculationCount = fn.EPDFForm_CountCalculationOrder(model);
|
|
3550
3856
|
for (let index = 0; index < calculationCount; index++) {
|
|
@@ -3580,7 +3886,7 @@ function entryScalar(entry) {
|
|
|
3580
3886
|
if (entry.kind === "array") return entry.values[0] ?? "";
|
|
3581
3887
|
return "";
|
|
3582
3888
|
}
|
|
3583
|
-
function readFieldActions(runtime, model, fieldIndex, budget) {
|
|
3889
|
+
function readFieldActions(runtime, model, fieldIndex, docPtr, budget) {
|
|
3584
3890
|
const { fn, mem } = runtime;
|
|
3585
3891
|
const entries = [
|
|
3586
3892
|
["keystroke", 0],
|
|
@@ -3590,7 +3896,7 @@ function readFieldActions(runtime, model, fieldIndex, budget) {
|
|
|
3590
3896
|
];
|
|
3591
3897
|
const actions = {};
|
|
3592
3898
|
for (const [key, event] of entries) {
|
|
3593
|
-
const action = readActionModel(fn, mem, fn.EPDFForm_GetFieldActionModel(model, fieldIndex, event), budget);
|
|
3899
|
+
const action = readActionModel(fn, mem, docPtr, fn.EPDFForm_GetFieldActionModel(model, fieldIndex, event), budget);
|
|
3594
3900
|
if (action) actions[key] = action;
|
|
3595
3901
|
}
|
|
3596
3902
|
return Object.keys(actions).length > 0 ? actions : void 0;
|
|
@@ -3836,18 +4142,19 @@ function blendModeFromCode(code) {
|
|
|
3836
4142
|
* per-subtype reader builds its DTO by extending this with its own
|
|
3837
4143
|
* fields and `subtype: '...'` discriminator.
|
|
3838
4144
|
*/
|
|
3839
|
-
function readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision, actionBudget = new ActionReadBudgetTracker()) {
|
|
4145
|
+
function readAnnotationBase(fn, mem, docPtr, annotPtr, pageObjectNumber, index, revision, actionBudget = new ActionReadBudgetTracker()) {
|
|
3840
4146
|
const identity = readAnnotationIdentity(fn, mem, annotPtr, pageObjectNumber, index, revision);
|
|
3841
4147
|
const rect = readAnnotRect(fn, mem, annotPtr);
|
|
3842
4148
|
const flags = readAnnotFlags(fn, annotPtr);
|
|
3843
4149
|
const contents = readAnnotString(fn, mem, annotPtr, "Contents");
|
|
4150
|
+
const subject = readAnnotString(fn, mem, annotPtr, "Subj");
|
|
3844
4151
|
const author = readAnnotString(fn, mem, annotPtr, "T");
|
|
3845
4152
|
const createdRaw = readAnnotString(fn, mem, annotPtr, "CreationDate");
|
|
3846
4153
|
const modifiedRaw = readAnnotString(fn, mem, annotPtr, "M");
|
|
3847
4154
|
const blendMode = blendModeFromCode(fn.EPDFAnnot_GetBlendMode(annotPtr));
|
|
3848
4155
|
const embd = readEmbedMetadata(fn, mem, annotPtr);
|
|
3849
4156
|
const relationship = readAnnotationRelationship(fn, mem, annotPtr, pageObjectNumber);
|
|
3850
|
-
const actions = readAnnotationActions(fn, mem, annotPtr, actionBudget);
|
|
4157
|
+
const actions = readAnnotationActions(fn, mem, docPtr, annotPtr, actionBudget);
|
|
3851
4158
|
return {
|
|
3852
4159
|
ref: identity.ref,
|
|
3853
4160
|
pageObjectNumber,
|
|
@@ -3857,6 +4164,7 @@ function readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision
|
|
|
3857
4164
|
flags,
|
|
3858
4165
|
rect,
|
|
3859
4166
|
contents,
|
|
4167
|
+
subject,
|
|
3860
4168
|
author,
|
|
3861
4169
|
created: createdRaw ? pdfDateToIso(createdRaw) : null,
|
|
3862
4170
|
modified: modifiedRaw ? pdfDateToIso(modifiedRaw) : null,
|
|
@@ -3870,7 +4178,7 @@ function readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision
|
|
|
3870
4178
|
...actions ? { actions } : {}
|
|
3871
4179
|
};
|
|
3872
4180
|
}
|
|
3873
|
-
function readAnnotationActions(fn, mem, annotPtr, budget) {
|
|
4181
|
+
function readAnnotationActions(fn, mem, docPtr, annotPtr, budget) {
|
|
3874
4182
|
const events = [
|
|
3875
4183
|
["activate", 0],
|
|
3876
4184
|
["cursorEnter", 1],
|
|
@@ -3886,7 +4194,7 @@ function readAnnotationActions(fn, mem, annotPtr, budget) {
|
|
|
3886
4194
|
];
|
|
3887
4195
|
const actions = {};
|
|
3888
4196
|
for (const [key, event] of events) {
|
|
3889
|
-
const action = readActionModel(fn, mem, fn.EPDFAnnot_GetActionModel(annotPtr, event), budget);
|
|
4197
|
+
const action = readActionModel(fn, mem, docPtr, fn.EPDFAnnot_GetActionModel(annotPtr, event), budget);
|
|
3890
4198
|
if (action) actions[key] = action;
|
|
3891
4199
|
}
|
|
3892
4200
|
return Object.keys(actions).length > 0 ? actions : void 0;
|
|
@@ -3917,7 +4225,7 @@ function collectPageAnnotations(input) {
|
|
|
3917
4225
|
const annotPtr = getAnnotPtrAt(i);
|
|
3918
4226
|
if (!annotPtr) continue;
|
|
3919
4227
|
try {
|
|
3920
|
-
const base = readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, i, revision, actionBudget);
|
|
4228
|
+
const base = readAnnotationBase(fn, mem, readCtx.docPtr, annotPtr, pageObjectNumber, i, revision, actionBudget);
|
|
3921
4229
|
const subtypeCode = fn.FPDFAnnot_GetSubtype(annotPtr);
|
|
3922
4230
|
const { reader } = pickReader(subtypeCode);
|
|
3923
4231
|
const dto = reader(fn, mem, annotPtr, base, subtypeCode, readCtx);
|
|
@@ -4131,19 +4439,31 @@ const ANNOT_SUBTYPE_WIDGET = 20;
|
|
|
4131
4439
|
/** `FPDF_ANNOT_FREETEXT` — free-text annotation subtype code. */
|
|
4132
4440
|
const ANNOT_SUBTYPE_FREETEXT = 3;
|
|
4133
4441
|
/**
|
|
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
|
|
4442
|
+
* BOX-family subtypes (free-text 3, square 5, circle 6, stamp 13, caret 14):
|
|
4443
|
+
* the kinds whose v3 writers put rotation in the AP `/Matrix` +
|
|
4444
|
+
* `/EMBD_Metadata` `/UnrotatedRect`. Only these are eligible for
|
|
4445
|
+
* rotation-stripped appearance rendering — vertex kinds
|
|
4446
|
+
* (line/polyline/polygon/ink) pre-rotate their geometry, so their rasters must
|
|
4447
|
+
* stay on the classic path. This set MUST cover every kind whose reader
|
|
4448
|
+
* surfaces the rotation pair: `fromDTO` (plugin-annotation repository) mirrors
|
|
4449
|
+
* this exact condition to re-apply the stripped rotation as `apRot`.
|
|
4139
4450
|
*/
|
|
4140
4451
|
const BOX_FAMILY_SUBTYPES = /* @__PURE__ */ new Set([
|
|
4141
4452
|
3,
|
|
4142
4453
|
5,
|
|
4143
4454
|
6,
|
|
4144
|
-
13
|
|
4455
|
+
13,
|
|
4456
|
+
14
|
|
4145
4457
|
]);
|
|
4146
4458
|
/**
|
|
4459
|
+
* Per-appearance output-pixel ceiling (see the clamp in `renderOne`).
|
|
4460
|
+
* 16 M px ≈ a 4000² raster ≈ 64 MB of transient RGBA — safely inside the
|
|
4461
|
+
* wasm heap for the one-at-a-time batch loop, while a page-sized annotation
|
|
4462
|
+
* stays crisp to roughly 5–6× before its appearance starts stretching.
|
|
4463
|
+
* Small annotations (the overwhelming majority) never come near it.
|
|
4464
|
+
*/
|
|
4465
|
+
const APPEARANCE_PIXEL_CLAMP = 16e6;
|
|
4466
|
+
/**
|
|
4147
4467
|
* Maps an `AnnotationAppearanceMode` onto the PDFium appearance-mode int and
|
|
4148
4468
|
* the `EPDFAnnot_GetAvailableAppearanceModes` bit it occupies.
|
|
4149
4469
|
* N -> mode 0 / bit 1, R -> mode 1 / bit 2, D -> mode 2 / bit 4.
|
|
@@ -4242,13 +4562,15 @@ var AnnotationAppearanceReader = class {
|
|
|
4242
4562
|
fn.EPDFAnnot_GenerateFormFieldAP(annotPtr);
|
|
4243
4563
|
if (!fn.EPDFAnnot_HasAppearanceStream(annotPtr, modeInt)) return null;
|
|
4244
4564
|
} else return null;
|
|
4565
|
+
const rectArea = Math.max(1, (rect.right - rect.left) * (rect.top - rect.bottom));
|
|
4566
|
+
const effScale = Math.min(scale, Math.sqrt(APPEARANCE_PIXEL_CLAMP / rectArea));
|
|
4245
4567
|
return rasterize(this.runtime, {
|
|
4246
4568
|
rect,
|
|
4247
4569
|
page,
|
|
4248
4570
|
rotation,
|
|
4249
4571
|
viewport: {
|
|
4250
4572
|
kind: "scale",
|
|
4251
|
-
scale
|
|
4573
|
+
scale: effScale
|
|
4252
4574
|
},
|
|
4253
4575
|
...maxOutputPixels !== void 0 ? { maxOutputPixels } : {},
|
|
4254
4576
|
background: "transparent",
|
|
@@ -4363,7 +4685,9 @@ function setAnnotFlags(fn, annotPtr, partial) {
|
|
|
4363
4685
|
if (!fn.FPDFAnnot_SetFlags(annotPtr, next)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "FPDFAnnot_SetFlags returned false");
|
|
4364
4686
|
}
|
|
4365
4687
|
/**
|
|
4366
|
-
* Write `/Rect`
|
|
4688
|
+
* Write `/Rect` without touching `/AP`. Appearance preservation/regeneration
|
|
4689
|
+
* belongs to AnnotationMutator; a geometry primitive must not silently rewrite
|
|
4690
|
+
* an existing appearance `/BBox`. FS_RECTF layout is
|
|
4367
4691
|
* `{ left, top, right, bottom }` (top > bottom in PDF coords).
|
|
4368
4692
|
*/
|
|
4369
4693
|
function setAnnotRect(fn, mem, annotPtr, rect) {
|
|
@@ -4373,12 +4697,40 @@ function setAnnotRect(fn, mem, annotPtr, rect) {
|
|
|
4373
4697
|
mem.poke(buf, "f32", rect.top, 4);
|
|
4374
4698
|
mem.poke(buf, "f32", rect.right, 8);
|
|
4375
4699
|
mem.poke(buf, "f32", rect.bottom, 12);
|
|
4376
|
-
if (!fn.
|
|
4700
|
+
if (!fn.EPDFAnnot_SetRect(annotPtr, buf)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "EPDFAnnot_SetRect returned false");
|
|
4377
4701
|
} finally {
|
|
4378
4702
|
mem.free(buf);
|
|
4379
4703
|
}
|
|
4380
4704
|
}
|
|
4381
4705
|
/**
|
|
4706
|
+
* Write a UTF-16 string entry into an annotation dictionary. The write
|
|
4707
|
+
* side of `readAnnotString`.
|
|
4708
|
+
*/
|
|
4709
|
+
function writeAnnotString(fn, mem, annotPtr, key, value) {
|
|
4710
|
+
const ptr = mem.writeU16String(value);
|
|
4711
|
+
try {
|
|
4712
|
+
fn.FPDFAnnot_SetStringValue(annotPtr, key, ptr);
|
|
4713
|
+
} finally {
|
|
4714
|
+
mem.free(ptr);
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
/**
|
|
4718
|
+
* Three-state string write: `null` REMOVES the dictionary entry.
|
|
4719
|
+
*
|
|
4720
|
+
* Clearing goes through `EPDFAnnot_RemoveKey` because
|
|
4721
|
+
* `FPDFAnnot_SetStringValue` with a NULL value writes an EMPTY string —
|
|
4722
|
+
* it never removes. True removal is what keeps the read side honest:
|
|
4723
|
+
* `readAnnotString` returns `null` iff the key is absent, so a cleared
|
|
4724
|
+
* field reads back as `null`, not `''`.
|
|
4725
|
+
*/
|
|
4726
|
+
function writeAnnotStringOrClear(fn, mem, annotPtr, key, value) {
|
|
4727
|
+
if (value === null) {
|
|
4728
|
+
fn.EPDFAnnot_RemoveKey(annotPtr, key);
|
|
4729
|
+
return;
|
|
4730
|
+
}
|
|
4731
|
+
writeAnnotString(fn, mem, annotPtr, key, value);
|
|
4732
|
+
}
|
|
4733
|
+
/**
|
|
4382
4734
|
* Set `/BS /S` (border style code) and `/BS /W` (width) in one call via
|
|
4383
4735
|
* `EPDFAnnot_SetBorderStyle`. `styleCode` is a raw
|
|
4384
4736
|
* `FPDF_ANNOT_BORDER_STYLE` enum value — the string<->code mapping lives
|
|
@@ -4577,8 +4929,8 @@ function setCalloutLine(fn, mem, annotPtr, points) {
|
|
|
4577
4929
|
//#region src/features/annotations/internal/write/writeAnnotationBase.ts
|
|
4578
4930
|
/**
|
|
4579
4931
|
* 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
|
|
4932
|
+
* (contents/subject/nm). The kind-specific writer calls this BEFORE its
|
|
4933
|
+
* own field writes; order doesn't actually matter at the PDF level, but
|
|
4582
4934
|
* keeping the base first makes the writers symmetric with the readers.
|
|
4583
4935
|
*
|
|
4584
4936
|
* `/T` (author display) is stamped by the mutator from
|
|
@@ -4589,13 +4941,14 @@ function setCalloutLine(fn, mem, annotPtr, points) {
|
|
|
4589
4941
|
* annotation; the writer does not.
|
|
4590
4942
|
*/
|
|
4591
4943
|
function applyAnnotationBaseDraft(fn, mem, annotPtr, draft) {
|
|
4592
|
-
if (draft.contents !== void 0)
|
|
4593
|
-
if (draft.
|
|
4944
|
+
if (draft.contents !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Contents", draft.contents);
|
|
4945
|
+
if (draft.subject !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Subj", draft.subject);
|
|
4946
|
+
if (draft.nm !== void 0 && draft.nm.length > 0) writeAnnotString(fn, mem, annotPtr, "NM", draft.nm);
|
|
4594
4947
|
if (draft.flags !== void 0) setAnnotFlags(fn, annotPtr, draft.flags);
|
|
4595
4948
|
}
|
|
4596
4949
|
/**
|
|
4597
4950
|
* Write the annotation-wide base fields shared by every Patch
|
|
4598
|
-
* (contents). /T is bound at creation and not patchable — see
|
|
4951
|
+
* (contents/subject). /T is bound at creation and not patchable — see
|
|
4599
4952
|
* `AnnotationPatchBase`. /NM is monotonic per annotation; the mutator
|
|
4600
4953
|
* may stamp /NM opportunistically on a weak annotation via
|
|
4601
4954
|
* `writeAnnotationNm`.
|
|
@@ -4606,7 +4959,8 @@ function applyAnnotationBaseDraft(fn, mem, annotPtr, draft) {
|
|
|
4606
4959
|
* "..." -> set the entry to that value
|
|
4607
4960
|
*/
|
|
4608
4961
|
function applyAnnotationBasePatch(fn, mem, annotPtr, patch) {
|
|
4609
|
-
if (patch.contents !== void 0)
|
|
4962
|
+
if (patch.contents !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Contents", patch.contents);
|
|
4963
|
+
if (patch.subject !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "Subj", patch.subject);
|
|
4610
4964
|
if (patch.flags !== void 0) setAnnotFlags(fn, annotPtr, patch.flags);
|
|
4611
4965
|
}
|
|
4612
4966
|
/**
|
|
@@ -4617,7 +4971,7 @@ function applyAnnotationBasePatch(fn, mem, annotPtr, patch) {
|
|
|
4617
4971
|
*/
|
|
4618
4972
|
function writeAnnotationAuthor(fn, mem, annotPtr, displayName) {
|
|
4619
4973
|
if (displayName.length === 0) return;
|
|
4620
|
-
|
|
4974
|
+
writeAnnotString(fn, mem, annotPtr, "T", displayName);
|
|
4621
4975
|
}
|
|
4622
4976
|
/**
|
|
4623
4977
|
* Stamp /NM on an annotation that didn't have one. Used by the mutator
|
|
@@ -4626,7 +4980,7 @@ function writeAnnotationAuthor(fn, mem, annotPtr, displayName) {
|
|
|
4626
4980
|
*/
|
|
4627
4981
|
function writeAnnotationNm(fn, mem, annotPtr, nm) {
|
|
4628
4982
|
if (nm.length === 0) return;
|
|
4629
|
-
|
|
4983
|
+
writeAnnotString(fn, mem, annotPtr, "NM", nm);
|
|
4630
4984
|
}
|
|
4631
4985
|
/**
|
|
4632
4986
|
* Stamp /M (modification date) on an annotation. /M is a standard
|
|
@@ -4643,22 +4997,7 @@ function writeAnnotationNm(fn, mem, annotPtr, nm) {
|
|
|
4643
4997
|
* Format: `D:YYYYMMDDHHmmSSOHH'mm'` (see formatPdfDate).
|
|
4644
4998
|
*/
|
|
4645
4999
|
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);
|
|
5000
|
+
writeAnnotString(fn, mem, annotPtr, "M", formatPdfDate(now));
|
|
4662
5001
|
}
|
|
4663
5002
|
//#endregion
|
|
4664
5003
|
//#region src/features/annotations/internal/identity/captureOrStampStableId.ts
|
|
@@ -4824,32 +5163,207 @@ function computeMutationImpact(inputs) {
|
|
|
4824
5163
|
* `AnnotationUpdateResult.updated`.
|
|
4825
5164
|
*/
|
|
4826
5165
|
function readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, index, revision, ctx) {
|
|
4827
|
-
const base = readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision);
|
|
5166
|
+
const base = readAnnotationBase(fn, mem, ctx.docPtr, annotPtr, pageObjectNumber, index, revision);
|
|
4828
5167
|
const subtypeCode = fn.FPDFAnnot_GetSubtype(annotPtr);
|
|
4829
5168
|
const { reader } = pickReader(subtypeCode);
|
|
4830
5169
|
return reader(fn, mem, annotPtr, base, subtypeCode, ctx);
|
|
4831
5170
|
}
|
|
4832
5171
|
//#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;
|
|
5172
|
+
//#region src/features/annotations/internal/write/writeEmbedMetadata.ts
|
|
4842
5173
|
/**
|
|
4843
|
-
*
|
|
4844
|
-
*
|
|
4845
|
-
*
|
|
4846
|
-
*
|
|
4847
|
-
*
|
|
4848
|
-
*
|
|
5174
|
+
* Write the EmbedPDF-namespaced /EMBD_Metadata annotation dictionary.
|
|
5175
|
+
*
|
|
5176
|
+
* This module owns ONLY the vendor-extension dictionary. Standard PDF
|
|
5177
|
+
* base fields (/T, /M, /NM, /Contents) are handled by
|
|
5178
|
+
* `writers/annotations/base.ts` — /EMBD_Metadata is a separate
|
|
5179
|
+
* dictionary nested under the annotation that carries cloud-side
|
|
5180
|
+
* identity bookkeeping which has no PDF-spec equivalent.
|
|
5181
|
+
*
|
|
5182
|
+
* Schema (v1):
|
|
5183
|
+
* /EMBD_Metadata <<
|
|
5184
|
+
* /SchemaVersion 1 % EmbedPDF metadata schema version
|
|
5185
|
+
* /UserID (acme/alice) % identity that created the annotation
|
|
5186
|
+
* /GroupID (engineering) % group the annotation belongs to
|
|
5187
|
+
* /CreatedBy (acme/alice) % same as UserID on create
|
|
5188
|
+
* /UpdatedBy (acme/alice) % last editor; refreshed on every update
|
|
5189
|
+
* >>
|
|
5190
|
+
*
|
|
5191
|
+
* Read side: see `readers/annotations/embed-metadata.ts` (commit 12).
|
|
5192
|
+
*
|
|
5193
|
+
* PDFium bindings used: the `EPDFAnnot_*EmbedMetadata*` extension API
|
|
5194
|
+
* shipped in the EmbedPDF PDFium fork. Values are written as UTF-16LE
|
|
5195
|
+
* strings via the same `mem.writeU16String` pattern the base writer
|
|
5196
|
+
* uses for /T.
|
|
4849
5197
|
*/
|
|
4850
|
-
|
|
4851
|
-
|
|
5198
|
+
const KEY_SCHEMA_VERSION = "SchemaVersion";
|
|
5199
|
+
const KEY_USER_ID = "UserID";
|
|
5200
|
+
const KEY_GROUP_ID = "GroupID";
|
|
5201
|
+
const KEY_CREATED_BY = "CreatedBy";
|
|
5202
|
+
const KEY_UPDATED_BY = "UpdatedBy";
|
|
5203
|
+
/**
|
|
5204
|
+
* Stamp /EMBD_Metadata on a freshly created annotation. No-op if the
|
|
5205
|
+
* actor has no identity fields — the worker still emits the standard
|
|
5206
|
+
* /M timestamp via the base writer; only the vendor dictionary is
|
|
5207
|
+
* skipped.
|
|
5208
|
+
*
|
|
5209
|
+
* actor.userId → /UserID, /CreatedBy, /UpdatedBy
|
|
5210
|
+
* actor.groupId → /GroupID
|
|
5211
|
+
*
|
|
5212
|
+
* /SchemaVersion is written whenever any field is written.
|
|
5213
|
+
*/
|
|
5214
|
+
function applyEmbedMetadataOnCreate(fn, mem, annotPtr, actor) {
|
|
5215
|
+
if (!actor || !actor.userId && !actor.groupId) return;
|
|
5216
|
+
fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
|
|
5217
|
+
if (actor.userId) {
|
|
5218
|
+
setMetadataString(fn, mem, annotPtr, KEY_USER_ID, actor.userId);
|
|
5219
|
+
setMetadataString(fn, mem, annotPtr, KEY_CREATED_BY, actor.userId);
|
|
5220
|
+
setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
|
|
5221
|
+
}
|
|
5222
|
+
if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
|
|
5223
|
+
}
|
|
5224
|
+
/**
|
|
5225
|
+
* Apply /EMBD_Metadata refreshes on update. Two fields can change on
|
|
5226
|
+
* update; everything else (UserID, CreatedBy) is immutable.
|
|
5227
|
+
*
|
|
5228
|
+
* actor.userId → /UpdatedBy (refreshed every update where the
|
|
5229
|
+
* caller has an identity)
|
|
5230
|
+
* actor.groupId → /GroupID (reassigned only when the patch
|
|
5231
|
+
* explicitly contains a new groupId; route runs
|
|
5232
|
+
* `checkSetGroup` first)
|
|
5233
|
+
*
|
|
5234
|
+
* If the target annotation has no EMBD_Metadata yet (created before
|
|
5235
|
+
* the cloud started stamping, or by a non-cloud writer), we seed
|
|
5236
|
+
* /SchemaVersion so subsequent readers can find the marker.
|
|
5237
|
+
*
|
|
5238
|
+
* No-op when the actor has neither userId nor groupId.
|
|
5239
|
+
*/
|
|
5240
|
+
function applyEmbedMetadataOnUpdate(fn, mem, annotPtr, actor) {
|
|
5241
|
+
if (!actor || !actor.userId && !actor.groupId) return;
|
|
5242
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_SCHEMA_VERSION, 1);
|
|
5243
|
+
if (actor.userId) setMetadataString(fn, mem, annotPtr, KEY_UPDATED_BY, actor.userId);
|
|
5244
|
+
if (actor.groupId) setMetadataString(fn, mem, annotPtr, KEY_GROUP_ID, actor.groupId);
|
|
5245
|
+
}
|
|
5246
|
+
function setMetadataString(fn, mem, annotPtr, key, value) {
|
|
5247
|
+
const ptr = mem.writeU16String(value);
|
|
5248
|
+
try {
|
|
5249
|
+
fn.EPDFAnnot_SetEmbedMetadataString(annotPtr, key, ptr);
|
|
5250
|
+
} finally {
|
|
5251
|
+
mem.free(ptr);
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
//#endregion
|
|
5255
|
+
//#region src/features/annotations/internal/write/writeAnnotationTransformMetadata.ts
|
|
5256
|
+
/**
|
|
5257
|
+
* Write the EmbedPDF transform keys under /EMBD_Metadata. This is the seam
|
|
5258
|
+
* PDFium's native AP generator reads to bake a ROTATED appearance:
|
|
5259
|
+
*
|
|
5260
|
+
* /EMBD_Metadata <<
|
|
5261
|
+
* /Rotation 45 % degrees, PDF convention (CCW)
|
|
5262
|
+
* /UnrotatedRect [x0 y0 x1 y1] % BOX kinds only — the logical box
|
|
5263
|
+
* >>
|
|
5264
|
+
*
|
|
5265
|
+
* The family split (see the v3 plan):
|
|
5266
|
+
* - BOX kinds (square/circle/free-text): `/Rotation` + `/UnrotatedRect`.
|
|
5267
|
+
* With BOTH present the AP generator emits an `/AP /Matrix` that rotates a
|
|
5268
|
+
* box-sized appearance about the box centre, and `/Rect` is the enclosing
|
|
5269
|
+
* AABB. This is the portable, externally-correct rotation.
|
|
5270
|
+
* - VERTEX kinds (line/polyline/polygon/ink): `/Rotation` ONLY (advisory).
|
|
5271
|
+
* The points are already rotated (they are the visual), so a lone
|
|
5272
|
+
* `/Rotation` is INERT for the AP generator (it ignores `/Rotation` with no
|
|
5273
|
+
* `/UnrotatedRect`) — it just records the applied angle so EmbedPDF can
|
|
5274
|
+
* reconstruct an oriented selection box + offer reset.
|
|
5275
|
+
*
|
|
5276
|
+
* Both fields follow the engine-wide tri-state law ("a patch touches what it
|
|
5277
|
+
* states, preserves what it omits"):
|
|
5278
|
+
* - `undefined` → the key is UNTOUCHED (a rect-only move keeps its rotation).
|
|
5279
|
+
* - `null` or `0` → CLEAR just that key via `EPDFAnnot_ClearEmbedMetadataKey`
|
|
5280
|
+
* (0 ≡ no rotation is the canonical identity, not a sentinel; never clear
|
|
5281
|
+
* the whole dict — identity fields UserID/GroupID/CreatedBy/UpdatedBy must
|
|
5282
|
+
* survive).
|
|
5283
|
+
* - a value → SET the key. Setting a nonzero rotation with no
|
|
5284
|
+
* `/UnrotatedRect` (neither in the patch nor already on the annotation)
|
|
5285
|
+
* is an unsatisfiable state for the AP generator and throws `InvalidArg`.
|
|
5286
|
+
*
|
|
5287
|
+
* MUST run BEFORE `EPDFAnnot_GenerateAppearance` so the bake sees the rotation.
|
|
5288
|
+
* `/SchemaVersion` is seeded (stays 1) if this is the first key in the dict.
|
|
5289
|
+
*/
|
|
5290
|
+
const KEY_ROTATION = "Rotation";
|
|
5291
|
+
const KEY_UNROTATED_RECT = "UnrotatedRect";
|
|
5292
|
+
/** Seed `/SchemaVersion` when we are about to create the dict by writing the
|
|
5293
|
+
* first transform key, so the marker readers look for is always present. */
|
|
5294
|
+
function ensureSchemaVersion(fn, annotPtr) {
|
|
5295
|
+
if (!fn.EPDFAnnot_HasEmbedMetadata(annotPtr)) fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, "SchemaVersion", 1);
|
|
5296
|
+
}
|
|
5297
|
+
function setRotation(fn, annotPtr, rotation) {
|
|
5298
|
+
ensureSchemaVersion(fn, annotPtr);
|
|
5299
|
+
fn.EPDFAnnot_SetEmbedMetadataNumber(annotPtr, KEY_ROTATION, rotation);
|
|
5300
|
+
}
|
|
5301
|
+
function setUnrotatedRect(fn, mem, annotPtr, rect) {
|
|
5302
|
+
ensureSchemaVersion(fn, annotPtr);
|
|
5303
|
+
const buf = mem.alloc(16);
|
|
5304
|
+
try {
|
|
5305
|
+
mem.poke(buf, "f32", rect.left, 0);
|
|
5306
|
+
mem.poke(buf, "f32", rect.top, 4);
|
|
5307
|
+
mem.poke(buf, "f32", rect.right, 8);
|
|
5308
|
+
mem.poke(buf, "f32", rect.bottom, 12);
|
|
5309
|
+
fn.EPDFAnnot_SetEmbedMetadataRect(annotPtr, KEY_UNROTATED_RECT, buf);
|
|
5310
|
+
} finally {
|
|
5311
|
+
mem.free(buf);
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5314
|
+
/**
|
|
5315
|
+
* Write transform metadata for a BOX kind (square/circle/free-text/stamp),
|
|
5316
|
+
* tri-state per field: `undefined` never touches the document (safe to call on
|
|
5317
|
+
* every patch), `null`/`0` clears, a value sets. A nonzero rotation with no
|
|
5318
|
+
* unrotated box anywhere (patch or annotation) is unsatisfiable — the AP
|
|
5319
|
+
* generator needs the pair — and throws instead of writing a broken state.
|
|
5320
|
+
*/
|
|
5321
|
+
function writeBoxTransformMetadata(fn, mem, annotPtr, t) {
|
|
5322
|
+
if (t.rotation !== void 0) if (t.rotation !== null && t.rotation !== 0) {
|
|
5323
|
+
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)");
|
|
5324
|
+
setRotation(fn, annotPtr, t.rotation);
|
|
5325
|
+
} else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
|
|
5326
|
+
if (t.unrotatedRect !== void 0) if (t.unrotatedRect !== null) setUnrotatedRect(fn, mem, annotPtr, t.unrotatedRect);
|
|
5327
|
+
else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5328
|
+
}
|
|
5329
|
+
/**
|
|
5330
|
+
* Write the advisory `/Rotation` scalar for a VERTEX kind
|
|
5331
|
+
* (line/polyline/polygon/ink), tri-state: `undefined` never touches the
|
|
5332
|
+
* document, `null`/`0` clears, a value sets. Whenever it does write, any
|
|
5333
|
+
* `/UnrotatedRect` is defensively cleared — that key is an impossible state on
|
|
5334
|
+
* a vertex kind and must never accidentally drive the AP generator.
|
|
5335
|
+
*/
|
|
5336
|
+
function writeVertexTransformMetadata(fn, annotPtr, t) {
|
|
5337
|
+
if (t.rotation === void 0) return;
|
|
5338
|
+
if (t.rotation !== null && t.rotation !== 0) setRotation(fn, annotPtr, t.rotation);
|
|
5339
|
+
else fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_ROTATION);
|
|
5340
|
+
fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5341
|
+
}
|
|
5342
|
+
//#endregion
|
|
5343
|
+
//#region src/features/annotations/internal/write/writeCaretAnnotation.ts
|
|
5344
|
+
/** Default `/C` colour when a caret draft omits it (engine-wide default mark). */
|
|
5345
|
+
const DEFAULT_CARET_COLOR = {
|
|
5346
|
+
r: 255,
|
|
5347
|
+
g: 0,
|
|
5348
|
+
b: 0
|
|
5349
|
+
};
|
|
5350
|
+
/** Default opacity, set explicitly so reads always round-trip the same value. */
|
|
5351
|
+
const DEFAULT_OPACITY$4 = 1;
|
|
5352
|
+
/**
|
|
5353
|
+
* Apply a caret draft to a freshly-created annotation. Caret carries no
|
|
5354
|
+
* geometry of its own beyond `/Rect`. Order:
|
|
5355
|
+
* 1. base author-metadata (contents/nm/flags)
|
|
5356
|
+
* 2. `/Rect` (required — supplied by the caller; never derived)
|
|
5357
|
+
* 3. `/C` color + `/CA` opacity
|
|
5358
|
+
* 4. `/RD` rectangle differences (optional)
|
|
5359
|
+
*/
|
|
5360
|
+
function applyCaretDraft(fn, mem, annotPtr, draft) {
|
|
5361
|
+
applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
|
|
4852
5362
|
setAnnotRect(fn, mem, annotPtr, draft.rect);
|
|
5363
|
+
writeBoxTransformMetadata(fn, mem, annotPtr, {
|
|
5364
|
+
rotation: draft.rotation,
|
|
5365
|
+
unrotatedRect: draft.unrotatedRect
|
|
5366
|
+
});
|
|
4853
5367
|
setAnnotColor(fn, annotPtr, draft.color ?? DEFAULT_CARET_COLOR);
|
|
4854
5368
|
setAnnotOpacity(fn, annotPtr, draft.opacity ?? DEFAULT_OPACITY$4);
|
|
4855
5369
|
if (draft.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(draft.intent));
|
|
@@ -4862,6 +5376,10 @@ function applyCaretDraft(fn, mem, annotPtr, draft) {
|
|
|
4862
5376
|
function applyCaretPatch(fn, mem, annotPtr, patch) {
|
|
4863
5377
|
applyAnnotationBasePatch(fn, mem, annotPtr, patch);
|
|
4864
5378
|
if (patch.rect !== void 0) setAnnotRect(fn, mem, annotPtr, patch.rect);
|
|
5379
|
+
if (patch.rotation !== void 0 || patch.unrotatedRect !== void 0) writeBoxTransformMetadata(fn, mem, annotPtr, {
|
|
5380
|
+
rotation: patch.rotation,
|
|
5381
|
+
unrotatedRect: patch.unrotatedRect
|
|
5382
|
+
});
|
|
4865
5383
|
if (patch.color !== void 0) setAnnotColor(fn, annotPtr, patch.color);
|
|
4866
5384
|
if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
|
|
4867
5385
|
if (patch.intent !== void 0) setIntent(fn, annotPtr, caretIntentToName(patch.intent));
|
|
@@ -4951,264 +5469,93 @@ function attachFile(fn, mem, annotPtr, file, resource, docPtr) {
|
|
|
4951
5469
|
* path; anything else is treated as a registered-font `key` and resolved to
|
|
4952
5470
|
* this thread's FontId. A key with no resolver wired (e.g. a host without a
|
|
4953
5471
|
* font registry) is a programming error — fail loud rather than silently
|
|
4954
|
-
* downgrade to Helvetica and embed the wrong glyphs.
|
|
4955
|
-
*
|
|
4956
|
-
* Shared by the free-text and redact writers: `/DA` styles a free-text body
|
|
4957
|
-
* and a redaction `/OverlayText` label through the exact same triple.
|
|
4958
|
-
*/
|
|
4959
|
-
function applyDefaultAppearance(fn, annotPtr, fontFamily, fontSize, color, ctx) {
|
|
4960
|
-
if (isStandardFont(fontFamily)) {
|
|
4961
|
-
setDefaultAppearance(fn, annotPtr, standardFontToCode(fontFamily), fontSize, color);
|
|
4962
|
-
return;
|
|
4963
|
-
}
|
|
4964
|
-
if (!ctx?.resolveRegisteredFontId) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `fontFamily '${fontFamily}' is not a standard font and no font registry is available on this host`);
|
|
4965
|
-
setDefaultAppearanceRegisteredFont(fn, annotPtr, ctx.resolveRegisteredFontId(fontFamily), fontSize, color);
|
|
4966
|
-
}
|
|
4967
|
-
const DEFAULT_COLOR = {
|
|
4968
|
-
r: 255,
|
|
4969
|
-
g: 0,
|
|
4970
|
-
b: 0
|
|
4971
|
-
};
|
|
4972
|
-
/**
|
|
4973
|
-
* Apply the geometry styling (`/C`, `/CA`, `/BS`, dash) from a draft to a
|
|
4974
|
-
* freshly-created annotation. This is the no-fill layer shared by ink and
|
|
4975
|
-
* the filled families; `interiorColor` (`/IC`) is layered on by
|
|
4976
|
-
* {@link applyFilledStyleDraft}. Geometry (`/Rect`, `/Vertices`, `/L`,
|
|
4977
|
-
* `/InkList`), cloudy borders, rect-differences, and line endings are
|
|
4978
|
-
* layered on by the family-specific writer around this call.
|
|
4979
|
-
*/
|
|
4980
|
-
function applyGeometryStyleDraft(fn, mem, annotPtr, draft) {
|
|
4981
|
-
setAnnotColor(fn, annotPtr, draft.color ?? DEFAULT_COLOR);
|
|
4982
|
-
setAnnotOpacity(fn, annotPtr, draft.opacity ?? 1);
|
|
4983
|
-
applyBorderDraft(fn, mem, annotPtr, draft);
|
|
4984
|
-
}
|
|
4985
|
-
/**
|
|
4986
|
-
* Write the `/BS` border (style + width, then dash) from a draft. This is
|
|
4987
|
-
* the border-only slice of {@link applyGeometryStyleDraft}, shared with the
|
|
4988
|
-
* free-text box border (which manages `/C` and `/DA` itself). A missing
|
|
4989
|
-
* `borderStyle`/`strokeWidth` falls back to a 1pt solid border.
|
|
4990
|
-
*/
|
|
4991
|
-
function applyBorderDraft(fn, mem, annotPtr, draft) {
|
|
4992
|
-
setBorderStyle(fn, annotPtr, borderStyleToCode(draft.borderStyle ?? "solid"), draft.strokeWidth ?? 1);
|
|
4993
|
-
if (draft.dashArray !== void 0 && draft.dashArray.length > 0) setBorderDashPattern(fn, mem, annotPtr, draft.dashArray);
|
|
4994
|
-
}
|
|
4995
|
-
/**
|
|
4996
|
-
* Apply the geometry styling from a patch to an existing annotation. Only
|
|
4997
|
-
* present fields are touched. Border style + width share the single
|
|
4998
|
-
* `EPDFAnnot_SetBorderStyle` call, so when only one is patched we read the
|
|
4999
|
-
* current pair first and preserve the other.
|
|
5000
|
-
*/
|
|
5001
|
-
function applyGeometryStylePatch(fn, mem, annotPtr, patch) {
|
|
5002
|
-
if (patch.color !== void 0) setAnnotColor(fn, annotPtr, patch.color);
|
|
5003
|
-
if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
|
|
5004
|
-
applyBorderPatch(fn, mem, annotPtr, patch);
|
|
5005
|
-
}
|
|
5006
|
-
/**
|
|
5007
|
-
* Write the `/BS` border from a patch, touching only present fields. Border
|
|
5008
|
-
* style + width share the single `EPDFAnnot_SetBorderStyle` call, so when
|
|
5009
|
-
* only one is patched we read the current pair first and preserve the other.
|
|
5010
|
-
* The border-only slice of {@link applyGeometryStylePatch}, shared with the
|
|
5011
|
-
* free-text box border.
|
|
5012
|
-
*/
|
|
5013
|
-
function applyBorderPatch(fn, mem, annotPtr, patch) {
|
|
5014
|
-
if (patch.borderStyle !== void 0 || patch.strokeWidth !== void 0) {
|
|
5015
|
-
const current = readBorderStyle(fn, mem, annotPtr);
|
|
5016
|
-
const style = patch.borderStyle ?? borderStyleFromCode(current.styleCode);
|
|
5017
|
-
const width = patch.strokeWidth ?? current.width;
|
|
5018
|
-
setBorderStyle(fn, annotPtr, borderStyleToCode(style), width);
|
|
5019
|
-
}
|
|
5020
|
-
if (patch.dashArray !== void 0 && patch.dashArray.length > 0) setBorderDashPattern(fn, mem, annotPtr, patch.dashArray);
|
|
5021
|
-
}
|
|
5022
|
-
/**
|
|
5023
|
-
* Apply the full stroke/fill styling (geometry styling + `/IC`) from a
|
|
5024
|
-
* draft. A `null`/omitted fill clears `/IC` so the shape renders unfilled.
|
|
5025
|
-
* Used by the filled families (circle/square/polygon/polyline/line).
|
|
5026
|
-
*/
|
|
5027
|
-
function applyFilledStyleDraft(fn, mem, annotPtr, draft) {
|
|
5028
|
-
const interior = draft.interiorColor ?? null;
|
|
5029
|
-
if (interior === null) clearAnnotColor(fn, annotPtr, FPDFANNOT_COLORTYPE.InteriorColor);
|
|
5030
|
-
else setAnnotColor(fn, annotPtr, interior, FPDFANNOT_COLORTYPE.InteriorColor);
|
|
5031
|
-
applyGeometryStyleDraft(fn, mem, annotPtr, draft);
|
|
5032
|
-
}
|
|
5033
|
-
/**
|
|
5034
|
-
* Apply the full stroke/fill styling from a patch. Only present fields are
|
|
5035
|
-
* touched; `interiorColor: null` clears `/IC`.
|
|
5036
|
-
*/
|
|
5037
|
-
function applyFilledStylePatch(fn, mem, annotPtr, patch) {
|
|
5038
|
-
if (patch.interiorColor !== void 0) if (patch.interiorColor === null) clearAnnotColor(fn, annotPtr, FPDFANNOT_COLORTYPE.InteriorColor);
|
|
5039
|
-
else setAnnotColor(fn, annotPtr, patch.interiorColor, FPDFANNOT_COLORTYPE.InteriorColor);
|
|
5040
|
-
applyGeometryStylePatch(fn, mem, annotPtr, patch);
|
|
5041
|
-
}
|
|
5042
|
-
//#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`.
|
|
5472
|
+
* downgrade to Helvetica and embed the wrong glyphs.
|
|
5157
5473
|
*
|
|
5158
|
-
*
|
|
5159
|
-
*
|
|
5474
|
+
* Shared by the free-text and redact writers: `/DA` styles a free-text body
|
|
5475
|
+
* and a redaction `/OverlayText` label through the exact same triple.
|
|
5160
5476
|
*/
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
if (!
|
|
5477
|
+
function applyDefaultAppearance(fn, annotPtr, fontFamily, fontSize, color, ctx) {
|
|
5478
|
+
if (isStandardFont(fontFamily)) {
|
|
5479
|
+
setDefaultAppearance(fn, annotPtr, standardFontToCode(fontFamily), fontSize, color);
|
|
5480
|
+
return;
|
|
5481
|
+
}
|
|
5482
|
+
if (!ctx?.resolveRegisteredFontId) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `fontFamily '${fontFamily}' is not a standard font and no font registry is available on this host`);
|
|
5483
|
+
setDefaultAppearanceRegisteredFont(fn, annotPtr, ctx.resolveRegisteredFontId(fontFamily), fontSize, color);
|
|
5167
5484
|
}
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5485
|
+
const DEFAULT_COLOR = {
|
|
5486
|
+
r: 255,
|
|
5487
|
+
g: 0,
|
|
5488
|
+
b: 0
|
|
5489
|
+
};
|
|
5490
|
+
/**
|
|
5491
|
+
* Apply the geometry styling (`/C`, `/CA`, `/BS`, dash) from a draft to a
|
|
5492
|
+
* freshly-created annotation. This is the no-fill layer shared by ink and
|
|
5493
|
+
* the filled families; `interiorColor` (`/IC`) is layered on by
|
|
5494
|
+
* {@link applyFilledStyleDraft}. Geometry (`/Rect`, `/Vertices`, `/L`,
|
|
5495
|
+
* `/InkList`), cloudy borders, rect-differences, and line endings are
|
|
5496
|
+
* layered on by the family-specific writer around this call.
|
|
5497
|
+
*/
|
|
5498
|
+
function applyGeometryStyleDraft(fn, mem, annotPtr, draft) {
|
|
5499
|
+
setAnnotColor(fn, annotPtr, draft.color ?? DEFAULT_COLOR);
|
|
5500
|
+
setAnnotOpacity(fn, annotPtr, draft.opacity ?? 1);
|
|
5501
|
+
applyBorderDraft(fn, mem, annotPtr, draft);
|
|
5171
5502
|
}
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5503
|
+
/**
|
|
5504
|
+
* Write the `/BS` border (style + width, then dash) from a draft. This is
|
|
5505
|
+
* the border-only slice of {@link applyGeometryStyleDraft}, shared with the
|
|
5506
|
+
* free-text box border (which manages `/C` and `/DA` itself). A missing
|
|
5507
|
+
* `borderStyle`/`strokeWidth` falls back to a 1pt solid border.
|
|
5508
|
+
*/
|
|
5509
|
+
function applyBorderDraft(fn, mem, annotPtr, draft) {
|
|
5510
|
+
setBorderStyle(fn, annotPtr, borderStyleToCode(draft.borderStyle ?? "solid"), draft.strokeWidth ?? 1);
|
|
5511
|
+
if (draft.dashArray !== void 0 && draft.dashArray.length > 0) setBorderDashPattern(fn, mem, annotPtr, draft.dashArray);
|
|
5512
|
+
}
|
|
5513
|
+
/**
|
|
5514
|
+
* Apply the geometry styling from a patch to an existing annotation. Only
|
|
5515
|
+
* present fields are touched. Border style + width share the single
|
|
5516
|
+
* `EPDFAnnot_SetBorderStyle` call, so when only one is patched we read the
|
|
5517
|
+
* current pair first and preserve the other.
|
|
5518
|
+
*/
|
|
5519
|
+
function applyGeometryStylePatch(fn, mem, annotPtr, patch) {
|
|
5520
|
+
if (patch.color !== void 0) setAnnotColor(fn, annotPtr, patch.color);
|
|
5521
|
+
if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
|
|
5522
|
+
applyBorderPatch(fn, mem, annotPtr, patch);
|
|
5523
|
+
}
|
|
5524
|
+
/**
|
|
5525
|
+
* Write the `/BS` border from a patch, touching only present fields. Border
|
|
5526
|
+
* style + width share the single `EPDFAnnot_SetBorderStyle` call, so when
|
|
5527
|
+
* only one is patched we read the current pair first and preserve the other.
|
|
5528
|
+
* The border-only slice of {@link applyGeometryStylePatch}, shared with the
|
|
5529
|
+
* free-text box border.
|
|
5530
|
+
*/
|
|
5531
|
+
function applyBorderPatch(fn, mem, annotPtr, patch) {
|
|
5532
|
+
if (patch.borderStyle !== void 0 || patch.strokeWidth !== void 0) {
|
|
5533
|
+
const current = readBorderStyle(fn, mem, annotPtr);
|
|
5534
|
+
const style = patch.borderStyle ?? borderStyleFromCode(current.styleCode);
|
|
5535
|
+
const width = patch.strokeWidth ?? current.width;
|
|
5536
|
+
setBorderStyle(fn, annotPtr, borderStyleToCode(style), width);
|
|
5183
5537
|
}
|
|
5538
|
+
if (patch.dashArray !== void 0 && patch.dashArray.length > 0) setBorderDashPattern(fn, mem, annotPtr, patch.dashArray);
|
|
5184
5539
|
}
|
|
5185
5540
|
/**
|
|
5186
|
-
*
|
|
5187
|
-
*
|
|
5188
|
-
*
|
|
5189
|
-
* unrotated box anywhere (patch or annotation) is unsatisfiable — the AP
|
|
5190
|
-
* generator needs the pair — and throws instead of writing a broken state.
|
|
5541
|
+
* Apply the full stroke/fill styling (geometry styling + `/IC`) from a
|
|
5542
|
+
* draft. A `null`/omitted fill clears `/IC` so the shape renders unfilled.
|
|
5543
|
+
* Used by the filled families (circle/square/polygon/polyline/line).
|
|
5191
5544
|
*/
|
|
5192
|
-
function
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
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);
|
|
5545
|
+
function applyFilledStyleDraft(fn, mem, annotPtr, draft) {
|
|
5546
|
+
const interior = draft.interiorColor ?? null;
|
|
5547
|
+
if (interior === null) clearAnnotColor(fn, annotPtr, FPDFANNOT_COLORTYPE.InteriorColor);
|
|
5548
|
+
else setAnnotColor(fn, annotPtr, interior, FPDFANNOT_COLORTYPE.InteriorColor);
|
|
5549
|
+
applyGeometryStyleDraft(fn, mem, annotPtr, draft);
|
|
5199
5550
|
}
|
|
5200
5551
|
/**
|
|
5201
|
-
*
|
|
5202
|
-
*
|
|
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.
|
|
5552
|
+
* Apply the full stroke/fill styling from a patch. Only present fields are
|
|
5553
|
+
* touched; `interiorColor: null` clears `/IC`.
|
|
5206
5554
|
*/
|
|
5207
|
-
function
|
|
5208
|
-
if (
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
fn.EPDFAnnot_ClearEmbedMetadataKey(annotPtr, KEY_UNROTATED_RECT);
|
|
5555
|
+
function applyFilledStylePatch(fn, mem, annotPtr, patch) {
|
|
5556
|
+
if (patch.interiorColor !== void 0) if (patch.interiorColor === null) clearAnnotColor(fn, annotPtr, FPDFANNOT_COLORTYPE.InteriorColor);
|
|
5557
|
+
else setAnnotColor(fn, annotPtr, patch.interiorColor, FPDFANNOT_COLORTYPE.InteriorColor);
|
|
5558
|
+
applyGeometryStylePatch(fn, mem, annotPtr, patch);
|
|
5212
5559
|
}
|
|
5213
5560
|
//#endregion
|
|
5214
5561
|
//#region src/features/annotations/internal/write/writeFreeTextAnnotation.ts
|
|
@@ -5980,13 +6327,18 @@ const DEFAULT_OPACITY = 1;
|
|
|
5980
6327
|
* Apply a text (sticky-note) draft. The visual is entirely generator-owned:
|
|
5981
6328
|
* the mutator's closing `regenerateAppearance` bakes the 20×20 note icon
|
|
5982
6329
|
* from `/C` + `/Name` (GenerateTextAP), so this writer only records state.
|
|
6330
|
+
* `/State` + `/StateModel` are dictionary-only (ISO 32000 §12.5.6.3) and
|
|
6331
|
+
* never reach the generator.
|
|
5983
6332
|
*/
|
|
5984
6333
|
function applyTextDraft(fn, mem, annotPtr, draft) {
|
|
6334
|
+
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
6335
|
applyAnnotationBaseDraft(fn, mem, annotPtr, draft);
|
|
5986
6336
|
setAnnotRect(fn, mem, annotPtr, draft.rect);
|
|
5987
6337
|
setAnnotColor(fn, annotPtr, draft.color ?? DEFAULT_NOTE_COLOR);
|
|
5988
6338
|
setAnnotOpacity(fn, annotPtr, draft.opacity ?? DEFAULT_OPACITY);
|
|
5989
6339
|
setNoteIcon(fn, annotPtr, draft.icon ?? "note");
|
|
6340
|
+
if (draft.stateModel !== void 0) writeAnnotString(fn, mem, annotPtr, "StateModel", stateModelToPdf(draft.stateModel));
|
|
6341
|
+
if (draft.state !== void 0) writeAnnotString(fn, mem, annotPtr, "State", stateToPdf(draft.state));
|
|
5990
6342
|
}
|
|
5991
6343
|
function applyTextPatch(fn, mem, annotPtr, patch) {
|
|
5992
6344
|
applyAnnotationBasePatch(fn, mem, annotPtr, patch);
|
|
@@ -5994,6 +6346,8 @@ function applyTextPatch(fn, mem, annotPtr, patch) {
|
|
|
5994
6346
|
if (patch.color !== void 0) setAnnotColor(fn, annotPtr, patch.color);
|
|
5995
6347
|
if (patch.opacity !== void 0) setAnnotOpacity(fn, annotPtr, patch.opacity);
|
|
5996
6348
|
if (patch.icon !== void 0) setNoteIcon(fn, annotPtr, patch.icon);
|
|
6349
|
+
if (patch.stateModel !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "StateModel", patch.stateModel === null ? null : stateModelToPdf(patch.stateModel));
|
|
6350
|
+
if (patch.state !== void 0) writeAnnotStringOrClear(fn, mem, annotPtr, "State", patch.state === null ? null : stateToPdf(patch.state));
|
|
5997
6351
|
}
|
|
5998
6352
|
function isTextSubtype(subtype) {
|
|
5999
6353
|
return subtype === "text";
|
|
@@ -6971,17 +7325,35 @@ var RedactionApplier = class {
|
|
|
6971
7325
|
}
|
|
6972
7326
|
applyAllOnPage(pagePtr) {
|
|
6973
7327
|
const { fn, mem } = this.runtime;
|
|
7328
|
+
const hasRedactions = this.pageHasRedactAnnotations(pagePtr);
|
|
6974
7329
|
return withScratch(mem, 4, (countPtr) => {
|
|
6975
|
-
if (!fn.EPDFPage_ApplyRedactions(pagePtr, countPtr))
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
7330
|
+
if (!fn.EPDFPage_ApplyRedactions(pagePtr, countPtr)) {
|
|
7331
|
+
if (hasRedactions) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "native page redaction apply failed");
|
|
7332
|
+
return {
|
|
7333
|
+
status: "unchanged",
|
|
7334
|
+
removed: 0
|
|
7335
|
+
};
|
|
7336
|
+
}
|
|
6979
7337
|
return {
|
|
6980
7338
|
status: "applied",
|
|
6981
7339
|
removed: readI32(mem, countPtr) >>> 0
|
|
6982
7340
|
};
|
|
6983
7341
|
});
|
|
6984
7342
|
}
|
|
7343
|
+
pageHasRedactAnnotations(pagePtr) {
|
|
7344
|
+
const { fn } = this.runtime;
|
|
7345
|
+
const count = fn.FPDFPage_GetAnnotCount(pagePtr);
|
|
7346
|
+
for (let index = 0; index < count; index++) {
|
|
7347
|
+
const annotPtr = fn.FPDFPage_GetAnnot(pagePtr, index);
|
|
7348
|
+
if (!annotPtr) continue;
|
|
7349
|
+
try {
|
|
7350
|
+
if ((0, _embedpdf_engine_core_runtime.subtypeFromCode)(fn.FPDFAnnot_GetSubtype(annotPtr)) === "redact") return true;
|
|
7351
|
+
} finally {
|
|
7352
|
+
fn.FPDFPage_CloseAnnot(annotPtr);
|
|
7353
|
+
}
|
|
7354
|
+
}
|
|
7355
|
+
return false;
|
|
7356
|
+
}
|
|
6985
7357
|
applyRefsOnPage(pagePtr, refs) {
|
|
6986
7358
|
const { fn, mem } = this.runtime;
|
|
6987
7359
|
let removed = 0;
|
|
@@ -7007,23 +7379,26 @@ var RedactionApplier = class {
|
|
|
7007
7379
|
//#region src/features/text/PageTextReader.ts
|
|
7008
7380
|
/**
|
|
7009
7381
|
* Per-page slow-path text reader. Acquires a `pagePtr` from the
|
|
7010
|
-
* `PagePtrPool`, opens a PDFium text page (`FPDFText_LoadPage`),
|
|
7011
|
-
*
|
|
7012
|
-
*
|
|
7013
|
-
*
|
|
7014
|
-
*
|
|
7015
|
-
*
|
|
7016
|
-
*
|
|
7017
|
-
*
|
|
7018
|
-
*
|
|
7019
|
-
*
|
|
7020
|
-
*
|
|
7021
|
-
*
|
|
7022
|
-
*
|
|
7023
|
-
*
|
|
7024
|
-
*
|
|
7025
|
-
*
|
|
7026
|
-
*
|
|
7382
|
+
* `PagePtrPool`, opens a PDFium text page (`FPDFText_LoadPage`), extracts
|
|
7383
|
+
* the page text, and releases everything in reverse order.
|
|
7384
|
+
*
|
|
7385
|
+
* Index spaces (see engine-core `text/charmap.ts`): `charCount` is the
|
|
7386
|
+
* CHARACTER space (`FPDFText_CountChars` — the space geometry runs tile);
|
|
7387
|
+
* `text` is the TEXT projection. The two diverge when a character
|
|
7388
|
+
* contributes zero text units (non-printing) or two (supplementary plane);
|
|
7389
|
+
* `charMap` anchors encode exactly those deviations.
|
|
7390
|
+
*
|
|
7391
|
+
* Both come from the fork's batched extension calls, which share one
|
|
7392
|
+
* encoding authority (`FX_UTF16Encode`) so text and map can never
|
|
7393
|
+
* disagree:
|
|
7394
|
+
*
|
|
7395
|
+
* - `EPDFText_GetTextFull` — true UTF-16LE extraction; supplementary
|
|
7396
|
+
* characters arrive as surrogate pairs instead of being silently
|
|
7397
|
+
* dropped by `FPDFText_GetText`'s UCS-2 encoder. Called once with an
|
|
7398
|
+
* upper-bound buffer (2 units per character + NUL), so no measure
|
|
7399
|
+
* round-trip.
|
|
7400
|
+
* - `EPDFText_GetCharToTextMap` — the anchors, measure-then-fill. Zero
|
|
7401
|
+
* anchors is the common (identity) page and skips the fill call.
|
|
7027
7402
|
*/
|
|
7028
7403
|
var PageTextReader = class {
|
|
7029
7404
|
constructor(runtime, session) {
|
|
@@ -7032,7 +7407,7 @@ var PageTextReader = class {
|
|
|
7032
7407
|
}
|
|
7033
7408
|
read(pageObjectNumber, signal) {
|
|
7034
7409
|
throwIfAborted(signal);
|
|
7035
|
-
const { fn
|
|
7410
|
+
const { fn } = this.runtime;
|
|
7036
7411
|
const pool = this.session.pagePool();
|
|
7037
7412
|
const pagePtr = pool.acquire(pageObjectNumber);
|
|
7038
7413
|
try {
|
|
@@ -7041,10 +7416,18 @@ var PageTextReader = class {
|
|
|
7041
7416
|
if (!textPagePtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `FPDFText_LoadPage returned null for page object ${pageObjectNumber}`);
|
|
7042
7417
|
try {
|
|
7043
7418
|
throwIfAborted(signal);
|
|
7044
|
-
const charCount = fn.FPDFText_CountChars(textPagePtr);
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7419
|
+
const charCount = Math.max(fn.FPDFText_CountChars(textPagePtr), 0);
|
|
7420
|
+
const text = readTextFull(this.runtime, textPagePtr, charCount);
|
|
7421
|
+
const charMap = readCharMap(this.runtime, textPagePtr, pageObjectNumber);
|
|
7422
|
+
const violation = (0, _embedpdf_engine_core_runtime.charMapViolation)(charCount, text.length, charMap);
|
|
7423
|
+
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}`);
|
|
7424
|
+
return charMap.length > 0 ? {
|
|
7425
|
+
text,
|
|
7426
|
+
charCount,
|
|
7427
|
+
charMap
|
|
7428
|
+
} : {
|
|
7429
|
+
text,
|
|
7430
|
+
charCount
|
|
7048
7431
|
};
|
|
7049
7432
|
} finally {
|
|
7050
7433
|
fn.FPDFText_ClosePage(textPagePtr);
|
|
@@ -7054,21 +7437,65 @@ var PageTextReader = class {
|
|
|
7054
7437
|
}
|
|
7055
7438
|
}
|
|
7056
7439
|
};
|
|
7057
|
-
|
|
7440
|
+
/**
|
|
7441
|
+
* Full-fidelity page text in one call: the buffer is sized at the maximum
|
|
7442
|
+
* possible expansion (every character a surrogate pair, plus the NUL), so
|
|
7443
|
+
* the returned requirement always fits and the measure round-trip is
|
|
7444
|
+
* skipped. The return value counts UTF-16 units INCLUDING the terminator.
|
|
7445
|
+
*/
|
|
7446
|
+
function readTextFull(runtime, textPagePtr, charCount) {
|
|
7058
7447
|
if (charCount <= 0) return "";
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7448
|
+
const { fn, mem } = runtime;
|
|
7449
|
+
const bufUnits = charCount * 2 + 1;
|
|
7450
|
+
return withScratch(mem, bufUnits * 2, (buf) => {
|
|
7451
|
+
const written = fn.EPDFText_GetTextFull(textPagePtr, buf, bufUnits);
|
|
7452
|
+
if (written <= 1) return "";
|
|
7453
|
+
return decodeUtf16Le(mem.readBytes(buf, (written - 1) * 2));
|
|
7454
|
+
});
|
|
7455
|
+
}
|
|
7456
|
+
function decodeUtf16Le(bytes) {
|
|
7457
|
+
const units = new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength >> 1);
|
|
7458
|
+
let out = "";
|
|
7459
|
+
const CHUNK = 8192;
|
|
7460
|
+
for (let i = 0; i < units.length; i += CHUNK) out += String.fromCharCode(...units.subarray(i, Math.min(i + CHUNK, units.length)));
|
|
7461
|
+
return out;
|
|
7462
|
+
}
|
|
7463
|
+
/** Anchor struct layout: two little-endian i32s (see EPDF_CHAR_MAP_ANCHOR). */
|
|
7464
|
+
const ANCHOR_BYTES = 8;
|
|
7465
|
+
function readCharMap(runtime, textPagePtr, pageObjectNumber) {
|
|
7466
|
+
const { fn, mem } = runtime;
|
|
7467
|
+
const count = fn.EPDFText_GetCharToTextMap(textPagePtr, _embedpdf_engine_runtime.NULL_PTR, 0);
|
|
7468
|
+
if (count < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `EPDFText_GetCharToTextMap failed for page object ${pageObjectNumber}`);
|
|
7469
|
+
if (count === 0) return [];
|
|
7470
|
+
return withScratch(mem, count * ANCHOR_BYTES, (buf) => {
|
|
7471
|
+
fn.EPDFText_GetCharToTextMap(textPagePtr, buf, count);
|
|
7472
|
+
const bytes = mem.readBytes(buf, count * ANCHOR_BYTES);
|
|
7473
|
+
const words = new Int32Array(bytes.buffer, bytes.byteOffset, count * 2);
|
|
7474
|
+
const anchors = new Array(count);
|
|
7475
|
+
for (let i = 0; i < count; i++) anchors[i] = [words[i * 2], words[i * 2 + 1]];
|
|
7476
|
+
return anchors;
|
|
7062
7477
|
});
|
|
7063
7478
|
}
|
|
7064
7479
|
//#endregion
|
|
7065
7480
|
//#region src/features/geometry/PageGeometryReader.ts
|
|
7066
7481
|
/**
|
|
7482
|
+
* Mid-object orientation drift that forces a run split (~0.5°). Real content
|
|
7483
|
+
* keeps one char matrix per text object; this guards the exotic cases so a
|
|
7484
|
+
* run's `rotation` is always representative of every glyph in it.
|
|
7485
|
+
*/
|
|
7486
|
+
const ROTATION_SPLIT_TOLERANCE = .0087;
|
|
7487
|
+
/**
|
|
7067
7488
|
* Geometry-only text layout reader.
|
|
7068
7489
|
*
|
|
7069
7490
|
* Emits geometry in PDF user space (y-up edges) — the canonical engine
|
|
7070
7491
|
* geometry. The viewer converts to content/view space via the page geometry
|
|
7071
7492
|
* matrix; this reader applies NO Y-flip or device transform.
|
|
7493
|
+
*
|
|
7494
|
+
* One `EPDFText_GetCharGeometry` call per glyph supplies boxes, oriented
|
|
7495
|
+
* cells, and flags together; `buildRunsFromRawGlyphs` then groups glyphs
|
|
7496
|
+
* into the upright/rotated run union. Upright runs are byte-identical to the
|
|
7497
|
+
* pre-orientation reader (quads the native layer offers are deliberately
|
|
7498
|
+
* dropped — the dominant case never pays for orientation).
|
|
7072
7499
|
*/
|
|
7073
7500
|
var PageGeometryReader = class {
|
|
7074
7501
|
constructor(runtime, session) {
|
|
@@ -7077,7 +7504,7 @@ var PageGeometryReader = class {
|
|
|
7077
7504
|
}
|
|
7078
7505
|
read(pageObjectNumber, signal) {
|
|
7079
7506
|
throwIfAborted(signal);
|
|
7080
|
-
const { fn } = this.runtime;
|
|
7507
|
+
const { fn, mem } = this.runtime;
|
|
7081
7508
|
const pool = this.session.pagePool();
|
|
7082
7509
|
const pagePtr = pool.acquire(pageObjectNumber);
|
|
7083
7510
|
try {
|
|
@@ -7087,12 +7514,27 @@ var PageGeometryReader = class {
|
|
|
7087
7514
|
try {
|
|
7088
7515
|
throwIfAborted(signal);
|
|
7089
7516
|
const glyphCount = Math.max(fn.FPDFText_CountChars(textPagePtr), 0);
|
|
7090
|
-
const
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7517
|
+
const records = [];
|
|
7518
|
+
const geometryPtr = mem.alloc(EPDF_CHAR_GEOMETRY_LAYOUT.bytes);
|
|
7519
|
+
try {
|
|
7520
|
+
let prevObjectKey = null;
|
|
7521
|
+
for (let i = 0; i < glyphCount; i++) {
|
|
7522
|
+
throwIfAborted(signal);
|
|
7523
|
+
const objectKey = fn.FPDFText_GetTextObject(textPagePtr, i);
|
|
7524
|
+
const record = {
|
|
7525
|
+
objectKey,
|
|
7526
|
+
...this.readGlyphRaw(textPagePtr, i, geometryPtr)
|
|
7527
|
+
};
|
|
7528
|
+
if (objectKey !== prevObjectKey) {
|
|
7529
|
+
record.fontSize = fn.FPDFText_GetFontSize(textPagePtr, i);
|
|
7530
|
+
prevObjectKey = objectKey;
|
|
7531
|
+
}
|
|
7532
|
+
records.push(record);
|
|
7533
|
+
}
|
|
7534
|
+
} finally {
|
|
7535
|
+
mem.free(geometryPtr);
|
|
7094
7536
|
}
|
|
7095
|
-
return { runs:
|
|
7537
|
+
return { runs: buildRunsFromRawGlyphs(records) };
|
|
7096
7538
|
} finally {
|
|
7097
7539
|
fn.FPDFText_ClosePage(textPagePtr);
|
|
7098
7540
|
}
|
|
@@ -7100,96 +7542,206 @@ var PageGeometryReader = class {
|
|
|
7100
7542
|
pool.release(pageObjectNumber);
|
|
7101
7543
|
}
|
|
7102
7544
|
}
|
|
7103
|
-
|
|
7104
|
-
const { fn } = this.runtime;
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7545
|
+
readGlyphRaw(textPagePtr, charIndex, geometryPtr) {
|
|
7546
|
+
const { fn, mem } = this.runtime;
|
|
7547
|
+
if (!fn.EPDFText_GetCharGeometry(textPagePtr, charIndex, geometryPtr)) return emptyRawGlyph();
|
|
7548
|
+
const { offsets, flags: bits } = EPDF_CHAR_GEOMETRY_LAYOUT;
|
|
7549
|
+
const native = readI32(mem, geometryPtr, offsets.flags) >>> 0;
|
|
7550
|
+
if (native & bits.empty) return emptyRawGlyph();
|
|
7551
|
+
const upright = Boolean(native & bits.upright);
|
|
7552
|
+
const raw = {
|
|
7553
|
+
flags: native & bits.space ? 1 : 0,
|
|
7554
|
+
looseBox: (0, _embedpdf_engine_core_runtime.normalizePdfRect)(readRectF(mem, geometryPtr, offsets.looseBox)),
|
|
7555
|
+
upright,
|
|
7556
|
+
rotation: 0,
|
|
7557
|
+
ascentFlip: false
|
|
7558
|
+
};
|
|
7559
|
+
if (native & bits.hasTightBox) raw.tightBox = (0, _embedpdf_engine_core_runtime.normalizePdfRect)(readRectF(mem, geometryPtr, offsets.tightBox));
|
|
7560
|
+
if (native & bits.hasLooseQuad) {
|
|
7561
|
+
raw.looseQuad = readQuad(mem, geometryPtr, offsets.looseQuad);
|
|
7562
|
+
if (native & bits.hasTightQuad) raw.tightQuad = readQuad(mem, geometryPtr, offsets.tightQuad);
|
|
7563
|
+
if (!upright) {
|
|
7564
|
+
const a = readF32(mem, geometryPtr, offsets.matrix);
|
|
7565
|
+
const b = readF32(mem, geometryPtr, offsets.matrix + 4);
|
|
7566
|
+
const c = readF32(mem, geometryPtr, offsets.matrix + 8);
|
|
7567
|
+
const d = readF32(mem, geometryPtr, offsets.matrix + 12);
|
|
7568
|
+
raw.rotation = Math.atan2(b, a);
|
|
7569
|
+
raw.ascentFlip = a * d - b * c < 0;
|
|
7122
7570
|
}
|
|
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
7571
|
}
|
|
7131
|
-
return
|
|
7572
|
+
return raw;
|
|
7132
7573
|
}
|
|
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
|
-
|
|
7574
|
+
};
|
|
7575
|
+
/**
|
|
7576
|
+
* Group per-glyph records into the run union. Pure — exported for tests.
|
|
7577
|
+
*
|
|
7578
|
+
* Runs split on text-object change (the legacy rule) and on orientation
|
|
7579
|
+
* change between classifiable glyphs (θ drift / mixed orientation). A run's
|
|
7580
|
+
* variant is decided by its FIRST classifiable glyph:
|
|
7581
|
+
* - empty glyphs never classify (they adopt the run's variant);
|
|
7582
|
+
* - real glyphs WITHOUT an oriented cell (singular matrix, synthesized
|
|
7583
|
+
* /ActualText pieces) classify as upright — box-only data degrades to
|
|
7584
|
+
* exactly the legacy behavior;
|
|
7585
|
+
* - real glyphs with a non-upright matrix classify as rotated.
|
|
7586
|
+
* Runs that never see a classifiable glyph emit as upright (degenerate,
|
|
7587
|
+
* legacy behavior).
|
|
7588
|
+
*/
|
|
7589
|
+
function buildRunsFromRawGlyphs(records) {
|
|
7590
|
+
const runs = [];
|
|
7591
|
+
let buffer = [];
|
|
7592
|
+
let charStart = 0;
|
|
7593
|
+
let fontSize;
|
|
7594
|
+
let objectKey = null;
|
|
7595
|
+
let cls;
|
|
7596
|
+
const flush = (nextStart) => {
|
|
7597
|
+
if (buffer.length > 0) {
|
|
7598
|
+
runs.push(materializeRun(buffer, charStart, fontSize, cls));
|
|
7599
|
+
buffer = [];
|
|
7600
|
+
cls = void 0;
|
|
7601
|
+
}
|
|
7602
|
+
charStart = nextStart;
|
|
7603
|
+
};
|
|
7604
|
+
for (let i = 0; i < records.length; i++) {
|
|
7605
|
+
const record = records[i];
|
|
7606
|
+
if (record.objectKey !== objectKey) {
|
|
7607
|
+
flush(i);
|
|
7608
|
+
objectKey = record.objectKey;
|
|
7609
|
+
fontSize = record.fontSize;
|
|
7610
|
+
}
|
|
7611
|
+
const recordClass = classOf(record);
|
|
7612
|
+
if (recordClass) {
|
|
7613
|
+
if (cls && !sameClass(cls, recordClass)) flush(i);
|
|
7614
|
+
if (!cls) cls = recordClass;
|
|
7174
7615
|
}
|
|
7616
|
+
buffer.push(record);
|
|
7175
7617
|
}
|
|
7618
|
+
flush(records.length);
|
|
7619
|
+
return runs;
|
|
7620
|
+
}
|
|
7621
|
+
function classOf(record) {
|
|
7622
|
+
if (record.flags & 2) return void 0;
|
|
7623
|
+
if (record.looseQuad && !record.upright) return {
|
|
7624
|
+
upright: false,
|
|
7625
|
+
rotation: record.rotation,
|
|
7626
|
+
ascentFlip: record.ascentFlip
|
|
7627
|
+
};
|
|
7628
|
+
return { upright: true };
|
|
7629
|
+
}
|
|
7630
|
+
function sameClass(a, b) {
|
|
7631
|
+
if (a.upright || b.upright) return a.upright === b.upright;
|
|
7632
|
+
if (a.ascentFlip !== b.ascentFlip) return false;
|
|
7633
|
+
const delta = a.rotation - b.rotation;
|
|
7634
|
+
return Math.abs(Math.atan2(Math.sin(delta), Math.cos(delta))) <= ROTATION_SPLIT_TOLERANCE;
|
|
7635
|
+
}
|
|
7636
|
+
function materializeRun(buffer, charStart, fontSize, cls) {
|
|
7637
|
+
if (cls && !cls.upright) {
|
|
7638
|
+
const glyphs = buffer.map((g) => g.looseQuad ? {
|
|
7639
|
+
looseQuad: g.looseQuad,
|
|
7640
|
+
flags: g.flags,
|
|
7641
|
+
...g.tightQuad ? { tightQuad: g.tightQuad } : {}
|
|
7642
|
+
} : {
|
|
7643
|
+
looseQuad: zeroQuad(),
|
|
7644
|
+
flags: g.flags
|
|
7645
|
+
});
|
|
7646
|
+
return {
|
|
7647
|
+
rect: runBounds(buffer, (g) => g.looseQuad ? (0, _embedpdf_engine_core_runtime.pdfQuadBounds)(g.looseQuad) : ZERO_RECT),
|
|
7648
|
+
charStart,
|
|
7649
|
+
glyphs,
|
|
7650
|
+
rotation: cls.rotation,
|
|
7651
|
+
ascentFlip: cls.ascentFlip,
|
|
7652
|
+
...fontSize !== void 0 ? { fontSize } : {}
|
|
7653
|
+
};
|
|
7654
|
+
}
|
|
7655
|
+
const glyphs = buffer.map((g) => ({
|
|
7656
|
+
looseBox: g.looseBox,
|
|
7657
|
+
flags: g.flags,
|
|
7658
|
+
...g.tightBox ? { tightBox: g.tightBox } : {}
|
|
7659
|
+
}));
|
|
7660
|
+
return {
|
|
7661
|
+
rect: runBounds(buffer, (g) => g.looseBox),
|
|
7662
|
+
charStart,
|
|
7663
|
+
glyphs,
|
|
7664
|
+
...fontSize !== void 0 ? { fontSize } : {}
|
|
7665
|
+
};
|
|
7666
|
+
}
|
|
7667
|
+
/** Legacy run-rect algorithm: seed from the first glyph, expand on non-empty. */
|
|
7668
|
+
function runBounds(buffer, boundsOf) {
|
|
7669
|
+
const seed = boundsOf(buffer[0]);
|
|
7670
|
+
const rect = {
|
|
7671
|
+
left: seed.left,
|
|
7672
|
+
bottom: seed.bottom,
|
|
7673
|
+
right: seed.right,
|
|
7674
|
+
top: seed.top
|
|
7675
|
+
};
|
|
7676
|
+
for (const g of buffer) {
|
|
7677
|
+
if (g.flags & 2) continue;
|
|
7678
|
+
const b = boundsOf(g);
|
|
7679
|
+
rect.left = Math.min(rect.left, b.left);
|
|
7680
|
+
rect.bottom = Math.min(rect.bottom, b.bottom);
|
|
7681
|
+
rect.right = Math.max(rect.right, b.right);
|
|
7682
|
+
rect.top = Math.max(rect.top, b.top);
|
|
7683
|
+
}
|
|
7684
|
+
return rect;
|
|
7685
|
+
}
|
|
7686
|
+
const ZERO_RECT = {
|
|
7687
|
+
left: 0,
|
|
7688
|
+
bottom: 0,
|
|
7689
|
+
right: 0,
|
|
7690
|
+
top: 0
|
|
7176
7691
|
};
|
|
7177
|
-
function
|
|
7692
|
+
function zeroQuad() {
|
|
7693
|
+
return {
|
|
7694
|
+
p1: {
|
|
7695
|
+
x: 0,
|
|
7696
|
+
y: 0
|
|
7697
|
+
},
|
|
7698
|
+
p2: {
|
|
7699
|
+
x: 0,
|
|
7700
|
+
y: 0
|
|
7701
|
+
},
|
|
7702
|
+
p3: {
|
|
7703
|
+
x: 0,
|
|
7704
|
+
y: 0
|
|
7705
|
+
},
|
|
7706
|
+
p4: {
|
|
7707
|
+
x: 0,
|
|
7708
|
+
y: 0
|
|
7709
|
+
}
|
|
7710
|
+
};
|
|
7711
|
+
}
|
|
7712
|
+
function emptyRawGlyph() {
|
|
7178
7713
|
return {
|
|
7714
|
+
flags: 2,
|
|
7179
7715
|
looseBox: {
|
|
7180
7716
|
left: 0,
|
|
7181
7717
|
bottom: 0,
|
|
7182
7718
|
right: 0,
|
|
7183
7719
|
top: 0
|
|
7184
7720
|
},
|
|
7185
|
-
|
|
7721
|
+
upright: true,
|
|
7722
|
+
rotation: 0,
|
|
7723
|
+
ascentFlip: false
|
|
7186
7724
|
};
|
|
7187
7725
|
}
|
|
7188
|
-
function
|
|
7726
|
+
function readQuad(mem, ptr, byteOffset) {
|
|
7727
|
+
const f = (offset) => readF32(mem, ptr, byteOffset + offset);
|
|
7189
7728
|
return {
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7729
|
+
p1: {
|
|
7730
|
+
x: f(0),
|
|
7731
|
+
y: f(4)
|
|
7732
|
+
},
|
|
7733
|
+
p2: {
|
|
7734
|
+
x: f(8),
|
|
7735
|
+
y: f(12)
|
|
7736
|
+
},
|
|
7737
|
+
p3: {
|
|
7738
|
+
x: f(16),
|
|
7739
|
+
y: f(20)
|
|
7740
|
+
},
|
|
7741
|
+
p4: {
|
|
7742
|
+
x: f(24),
|
|
7743
|
+
y: f(28)
|
|
7744
|
+
}
|
|
7193
7745
|
};
|
|
7194
7746
|
}
|
|
7195
7747
|
//#endregion
|
|
@@ -7829,13 +8381,13 @@ var FormReader = class {
|
|
|
7829
8381
|
snapshot(signal) {
|
|
7830
8382
|
throwIfAborted(signal);
|
|
7831
8383
|
const model = acquireFormModel(this.runtime, this.session);
|
|
7832
|
-
return readFormSnapshot(this.runtime, model);
|
|
8384
|
+
return readFormSnapshot(this.runtime, model, this.session.requireDocPtr());
|
|
7833
8385
|
}
|
|
7834
8386
|
field(ref, signal) {
|
|
7835
8387
|
throwIfAborted(signal);
|
|
7836
8388
|
const model = acquireFormModel(this.runtime, this.session);
|
|
7837
8389
|
const { fieldIndex } = resolveFieldRef(this.runtime, model, ref);
|
|
7838
|
-
return readFieldAt(this.runtime, model, fieldIndex);
|
|
8390
|
+
return readFieldAt(this.runtime, model, fieldIndex, this.session.requireDocPtr());
|
|
7839
8391
|
}
|
|
7840
8392
|
/**
|
|
7841
8393
|
* Serialize form data. Exports read the reconciled view (recovered
|
|
@@ -7991,7 +8543,7 @@ var FormMutator = class {
|
|
|
7991
8543
|
const model = acquireFormModel(this.runtime, this.session);
|
|
7992
8544
|
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
7993
8545
|
this.assertWritable(resolved.fieldObjectNumber);
|
|
7994
|
-
const before = readFieldAt(this.runtime, model, resolved.fieldIndex);
|
|
8546
|
+
const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
|
|
7995
8547
|
if (!FAMILY_BY_VALUE_TYPE[value.type].includes(before.family)) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `value type '${value.type}' does not apply to a '${before.family}' field`);
|
|
7996
8548
|
const changed = this.applyWrite(resolved.fieldObjectNumber, value);
|
|
7997
8549
|
return this.readBack(resolved.fieldObjectNumber, changed);
|
|
@@ -8027,7 +8579,7 @@ var FormMutator = class {
|
|
|
8027
8579
|
const fresh = acquireFormModel(this.runtime, this.session);
|
|
8028
8580
|
return {
|
|
8029
8581
|
...counters,
|
|
8030
|
-
snapshot: readFormSnapshot(this.runtime, fresh),
|
|
8582
|
+
snapshot: readFormSnapshot(this.runtime, fresh, this.session.requireDocPtr()),
|
|
8031
8583
|
meta: EMPTY_META$1
|
|
8032
8584
|
};
|
|
8033
8585
|
}
|
|
@@ -8100,7 +8652,7 @@ var FormMutator = class {
|
|
|
8100
8652
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8101
8653
|
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
8102
8654
|
this.assertWritable(resolved.fieldObjectNumber);
|
|
8103
|
-
const before = readFieldAt(this.runtime, model, resolved.fieldIndex);
|
|
8655
|
+
const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
|
|
8104
8656
|
if (before.family !== patch.family) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, `patch family '${patch.family}' does not match field family '${before.family}'`);
|
|
8105
8657
|
const fieldObjectNumber = resolved.fieldObjectNumber;
|
|
8106
8658
|
if (patch.name !== void 0) this.applyWideSetter(fn.EPDFForm_SetFieldName, fieldObjectNumber, patch.name, `cannot rename to "${patch.name}" (sibling conflict or invalid)`);
|
|
@@ -8131,7 +8683,7 @@ var FormMutator = class {
|
|
|
8131
8683
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8132
8684
|
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
8133
8685
|
this.assertWritable(resolved.fieldObjectNumber);
|
|
8134
|
-
const before = readFieldAt(this.runtime, model, resolved.fieldIndex);
|
|
8686
|
+
const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
|
|
8135
8687
|
if (!withScratchN(mem, [256 * 4, 4], ([buf, countPtr]) => {
|
|
8136
8688
|
mem.poke(countPtr, "i32", 0);
|
|
8137
8689
|
return fn.EPDFForm_DeleteField(this.session.requireDocPtr(), resolved.fieldObjectNumber, buf, 256, countPtr);
|
|
@@ -8151,7 +8703,7 @@ var FormMutator = class {
|
|
|
8151
8703
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8152
8704
|
const resolved = resolveFieldRef(this.runtime, model, ref);
|
|
8153
8705
|
this.assertWritable(resolved.fieldObjectNumber);
|
|
8154
|
-
const before = readFieldAt(this.runtime, model, resolved.fieldIndex);
|
|
8706
|
+
const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
|
|
8155
8707
|
const toggle = before.family === "checkbox" || before.family === "radio";
|
|
8156
8708
|
const state = toggle ? onState ?? (before.family === "checkbox" ? "Yes" : "") : "";
|
|
8157
8709
|
if (toggle && (!state || state === "Off")) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "attaching to a radio group needs a non-\"Off\" onState");
|
|
@@ -8197,7 +8749,7 @@ var FormMutator = class {
|
|
|
8197
8749
|
const fresh = acquireFormModel(this.runtime, this.session);
|
|
8198
8750
|
const fieldIndex = this.runtime.fn.EPDFForm_GetFieldIndexByObjNum(fresh, fieldObjectNumber);
|
|
8199
8751
|
if (fieldIndex < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "form field vanished after write");
|
|
8200
|
-
return readFieldAt(this.runtime, fresh, fieldIndex);
|
|
8752
|
+
return readFieldAt(this.runtime, fresh, fieldIndex, this.session.requireDocPtr());
|
|
8201
8753
|
}
|
|
8202
8754
|
assertWritable(fieldObjectNumber) {
|
|
8203
8755
|
if (fieldObjectNumber === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "form field is stored as a direct object and cannot be written");
|
|
@@ -8243,7 +8795,7 @@ var FormMutator = class {
|
|
|
8243
8795
|
const fresh = acquireFormModel(this.runtime, this.session);
|
|
8244
8796
|
const fieldIndex = this.runtime.fn.EPDFForm_GetFieldIndexByObjNum(fresh, fieldObjectNumber);
|
|
8245
8797
|
if (fieldIndex < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Unknown, "form field vanished after write");
|
|
8246
|
-
const field = readFieldAt(this.runtime, fresh, fieldIndex);
|
|
8798
|
+
const field = readFieldAt(this.runtime, fresh, fieldIndex, this.session.requireDocPtr());
|
|
8247
8799
|
const changedSet = new Set(changedObjNums);
|
|
8248
8800
|
return {
|
|
8249
8801
|
field,
|
|
@@ -8389,7 +8941,7 @@ var FormsEffectsApplier = class {
|
|
|
8389
8941
|
if (refs.length === 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidArg, "reset effect requires at least one field");
|
|
8390
8942
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8391
8943
|
const resolved = refs.map((ref) => this.resolve(model, ref));
|
|
8392
|
-
const fields = resolved.map(({ fieldIndex }) => readFieldAt(this.runtime, model, fieldIndex, actionBudget));
|
|
8944
|
+
const fields = resolved.map(({ fieldIndex }) => readFieldAt(this.runtime, model, fieldIndex, this.session.requireDocPtr(), actionBudget));
|
|
8393
8945
|
validateEffect(effect, fields);
|
|
8394
8946
|
return {
|
|
8395
8947
|
effect,
|
|
@@ -8414,7 +8966,7 @@ var FormsEffectsApplier = class {
|
|
|
8414
8966
|
const model = acquireFormModel(this.runtime, this.session);
|
|
8415
8967
|
const index = this.runtime.fn.EPDFForm_GetFieldIndexByObjNum(model, fieldObjectNumber);
|
|
8416
8968
|
if (index < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.InvalidReference, "form field disappeared");
|
|
8417
|
-
return readFieldAt(this.runtime, model, index, actionBudget);
|
|
8969
|
+
return readFieldAt(this.runtime, model, index, this.session.requireDocPtr(), actionBudget);
|
|
8418
8970
|
}
|
|
8419
8971
|
readFieldsBestEffort(fieldObjectNumbers, actionBudget) {
|
|
8420
8972
|
const fields = [];
|
|
@@ -8562,7 +9114,7 @@ function emptyResult(index, status) {
|
|
|
8562
9114
|
//#endregion
|
|
8563
9115
|
//#region src/features/search/internal/pageCorpusCache.ts
|
|
8564
9116
|
/**
|
|
8565
|
-
* Per-session, per-page search corpus: the page's
|
|
9117
|
+
* Per-session, per-page search corpus: the page's text snapshot plus its
|
|
8566
9118
|
* DEFAULT fold (the one literal queries with default options search).
|
|
8567
9119
|
* This is the local engine's in-memory equivalent of the server's corpus
|
|
8568
9120
|
* artifacts — same fold version, same shape, built lazily on first search
|
|
@@ -8587,19 +9139,20 @@ function acquirePageCorpus(runtime, session, pageObjectNumber, signal) {
|
|
|
8587
9139
|
cache.set(session, pages);
|
|
8588
9140
|
}
|
|
8589
9141
|
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, {
|
|
9142
|
+
if (hit && hit.seq === seq) return hit;
|
|
9143
|
+
const snapshot = new PageTextReader(runtime, session).read(pageObjectNumber, signal);
|
|
9144
|
+
const entry = {
|
|
8595
9145
|
seq,
|
|
8596
|
-
folded
|
|
8597
|
-
|
|
9146
|
+
folded: (0, _embedpdf_engine_core_runtime.foldText)(snapshot.text),
|
|
9147
|
+
snapshot
|
|
9148
|
+
};
|
|
9149
|
+
pages.delete(pageObjectNumber);
|
|
9150
|
+
pages.set(pageObjectNumber, entry);
|
|
8598
9151
|
if (pages.size > MAX_CACHED_PAGES) {
|
|
8599
9152
|
const oldest = pages.keys().next().value;
|
|
8600
9153
|
if (oldest !== void 0) pages.delete(oldest);
|
|
8601
9154
|
}
|
|
8602
|
-
return
|
|
9155
|
+
return entry;
|
|
8603
9156
|
}
|
|
8604
9157
|
//#endregion
|
|
8605
9158
|
//#region src/features/search/internal/searchCursor.ts
|
|
@@ -8708,19 +9261,23 @@ var SearchReader = class {
|
|
|
8708
9261
|
throwIfAborted(signal);
|
|
8709
9262
|
const pon = order[scanned].pageObjectNumber;
|
|
8710
9263
|
const corpus = acquirePageCorpus(this.runtime, this.session, pon, signal);
|
|
9264
|
+
const text = corpus.snapshot.text;
|
|
8711
9265
|
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);
|
|
9266
|
+
if (query.regex) ranges = (0, _embedpdf_engine_core_runtime.matchRegex)(text, query);
|
|
9267
|
+
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);
|
|
9268
|
+
else ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)(corpus.folded, query);
|
|
8715
9269
|
if (ranges.length > 0) {
|
|
8716
|
-
const
|
|
8717
|
-
for (const range of ranges)
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
9270
|
+
const layout = (0, _embedpdf_engine_core_runtime.buildPageTextLayout)(new PageGeometryReader(this.runtime, this.session).read(pon, signal));
|
|
9271
|
+
for (const range of ranges) {
|
|
9272
|
+
const chars = (0, _embedpdf_engine_core_runtime.charRangeForTextOffsets)(corpus.snapshot, range.start, range.start + range.length);
|
|
9273
|
+
matches.push({
|
|
9274
|
+
pageObjectNumber: pon,
|
|
9275
|
+
charStart: chars.start,
|
|
9276
|
+
charCount: chars.end - chars.start,
|
|
9277
|
+
segments: (0, _embedpdf_engine_core_runtime.textSegmentsForRange)(layout, chars.start, chars.end - chars.start),
|
|
9278
|
+
...mode === "full" ? { snippet: (0, _embedpdf_engine_core_runtime.buildSnippet)(text, range) } : {}
|
|
9279
|
+
});
|
|
9280
|
+
}
|
|
8724
9281
|
}
|
|
8725
9282
|
scanned++;
|
|
8726
9283
|
pagesThisSlice++;
|
|
@@ -8776,22 +9333,26 @@ var DocumentActionsReader = class {
|
|
|
8776
9333
|
} finally {
|
|
8777
9334
|
fn.FPDFDoc_CloseJavaScriptAction(namedPtr);
|
|
8778
9335
|
}
|
|
8779
|
-
|
|
9336
|
+
budget.reservePayloadBytes(name.length);
|
|
9337
|
+
const action = requireAction(readActionModel(fn, mem, docPtr, fn.EPDFDoc_GetNamedJavaScriptActionModel(docPtr, index), budget), "name-tree script", index);
|
|
8780
9338
|
nameTreeScripts.push({
|
|
8781
9339
|
name,
|
|
8782
9340
|
action
|
|
8783
9341
|
});
|
|
8784
9342
|
}
|
|
8785
|
-
const openAction = readActionModel(fn, mem, fn.EPDFDoc_GetOpenActionModel(docPtr), budget);
|
|
9343
|
+
const openAction = readActionModel(fn, mem, docPtr, fn.EPDFDoc_GetOpenActionModel(docPtr), budget);
|
|
9344
|
+
const openDestPtr = openAction ? _embedpdf_engine_runtime.NULL_PTR : fn.EPDFDoc_GetOpenActionDest(docPtr);
|
|
9345
|
+
const openDestination = openDestPtr !== _embedpdf_engine_runtime.NULL_PTR ? readDestination(fn, mem, docPtr, openDestPtr) : null;
|
|
8786
9346
|
const additional = {};
|
|
8787
9347
|
for (const [key, event] of DOCUMENT_ACTION_EVENTS) {
|
|
8788
9348
|
throwIfAborted(signal);
|
|
8789
|
-
const action = readActionModel(fn, mem, fn.EPDFDoc_GetAdditionalActionModel(docPtr, event), budget);
|
|
9349
|
+
const action = readActionModel(fn, mem, docPtr, fn.EPDFDoc_GetAdditionalActionModel(docPtr, event), budget);
|
|
8790
9350
|
if (action) additional[key] = action;
|
|
8791
9351
|
}
|
|
8792
9352
|
return {
|
|
8793
9353
|
nameTreeScripts,
|
|
8794
9354
|
openAction,
|
|
9355
|
+
openDestination,
|
|
8795
9356
|
...additional
|
|
8796
9357
|
};
|
|
8797
9358
|
}
|
|
@@ -9001,9 +9562,10 @@ function validatePatchValue(key, value) {
|
|
|
9001
9562
|
* worker wire shape from @embedpdf/engine-core.
|
|
9002
9563
|
*/
|
|
9003
9564
|
var WorkerHost = class {
|
|
9004
|
-
constructor(runtime, post) {
|
|
9565
|
+
constructor(runtime, post, options = {}) {
|
|
9005
9566
|
this.runtime = runtime;
|
|
9006
9567
|
this.post = post;
|
|
9568
|
+
this.options = options;
|
|
9007
9569
|
_defineProperty(this, "sessions", /* @__PURE__ */ new Map());
|
|
9008
9570
|
_defineProperty(this, "baseDocuments", void 0);
|
|
9009
9571
|
_defineProperty(this, "aborts", /* @__PURE__ */ new Map());
|
|
@@ -9029,6 +9591,10 @@ var WorkerHost = class {
|
|
|
9029
9591
|
this.aborts.get(msg.jobId)?.abort();
|
|
9030
9592
|
return;
|
|
9031
9593
|
}
|
|
9594
|
+
if (msg.kind === "pages.renderEncoded" || msg.kind === "document.renderPageFileEncoded" || msg.kind === "annotations.renderAppearancesEncoded") {
|
|
9595
|
+
this.receiveEncoded(msg);
|
|
9596
|
+
return;
|
|
9597
|
+
}
|
|
9032
9598
|
const ctrl = new AbortController();
|
|
9033
9599
|
this.aborts.set(msg.jobId, ctrl);
|
|
9034
9600
|
let resultPack;
|
|
@@ -9132,6 +9698,9 @@ var WorkerHost = class {
|
|
|
9132
9698
|
case "pages.insert":
|
|
9133
9699
|
resultPack = this.handlePagesInsert(msg, ctrl.signal);
|
|
9134
9700
|
break;
|
|
9701
|
+
case "pages.insertBlank":
|
|
9702
|
+
resultPack = this.handlePagesInsertBlank(msg, ctrl.signal);
|
|
9703
|
+
break;
|
|
9135
9704
|
case "attachments.list":
|
|
9136
9705
|
resultPack = this.handleAttachmentsList(msg, ctrl.signal);
|
|
9137
9706
|
break;
|
|
@@ -9424,6 +9993,17 @@ var WorkerHost = class {
|
|
|
9424
9993
|
result
|
|
9425
9994
|
}, req.artifactPath);
|
|
9426
9995
|
}
|
|
9996
|
+
handlePagesInsertBlank(req, signal) {
|
|
9997
|
+
const session = this.requireSession(req);
|
|
9998
|
+
const result = new PagesInserter(this.runtime, session).insertBlank({
|
|
9999
|
+
size: req.size,
|
|
10000
|
+
count: req.count
|
|
10001
|
+
}, req.destIndex, signal);
|
|
10002
|
+
return this.finishMutation(session, {
|
|
10003
|
+
tag: "pages.insertBlank",
|
|
10004
|
+
result
|
|
10005
|
+
}, req.artifactPath);
|
|
10006
|
+
}
|
|
9427
10007
|
handleAttachmentsList(req, signal) {
|
|
9428
10008
|
const session = this.requireSession(req);
|
|
9429
10009
|
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
@@ -9516,6 +10096,105 @@ var WorkerHost = class {
|
|
|
9516
10096
|
raster
|
|
9517
10097
|
}, [raster.data]);
|
|
9518
10098
|
}
|
|
10099
|
+
async receiveEncoded(msg) {
|
|
10100
|
+
if (!this.options.imageEncoder) {
|
|
10101
|
+
this.post((0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10102
|
+
kind: "reject",
|
|
10103
|
+
jobId: msg.jobId,
|
|
10104
|
+
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)"))
|
|
10105
|
+
}, _embedpdf_engine_core_runtime.EMPTY_TRANSFER));
|
|
10106
|
+
return;
|
|
10107
|
+
}
|
|
10108
|
+
const ctrl = new AbortController();
|
|
10109
|
+
this.aborts.set(msg.jobId, ctrl);
|
|
10110
|
+
try {
|
|
10111
|
+
let resultPack;
|
|
10112
|
+
switch (msg.kind) {
|
|
10113
|
+
case "pages.renderEncoded":
|
|
10114
|
+
resultPack = await this.handlePagesRenderEncoded(msg, ctrl.signal);
|
|
10115
|
+
break;
|
|
10116
|
+
case "document.renderPageFileEncoded":
|
|
10117
|
+
resultPack = await this.handleDocumentRenderPageFileEncoded(msg, ctrl.signal);
|
|
10118
|
+
break;
|
|
10119
|
+
case "annotations.renderAppearancesEncoded":
|
|
10120
|
+
resultPack = await this.handleAnnotationsRenderAppearancesEncoded(msg, ctrl.signal);
|
|
10121
|
+
break;
|
|
10122
|
+
}
|
|
10123
|
+
this.post((0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10124
|
+
kind: "resolve",
|
|
10125
|
+
jobId: msg.jobId,
|
|
10126
|
+
result: resultPack.payload
|
|
10127
|
+
}, resultPack.transfer));
|
|
10128
|
+
} catch (err) {
|
|
10129
|
+
this.post((0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10130
|
+
kind: "reject",
|
|
10131
|
+
jobId: msg.jobId,
|
|
10132
|
+
error: (0, _embedpdf_engine_core_runtime.serializeError)(err)
|
|
10133
|
+
}, _embedpdf_engine_core_runtime.EMPTY_TRANSFER));
|
|
10134
|
+
} finally {
|
|
10135
|
+
this.aborts.delete(msg.jobId);
|
|
10136
|
+
}
|
|
10137
|
+
}
|
|
10138
|
+
async encodeRaster(raster, encode, signal) {
|
|
10139
|
+
const encoder = this.options.imageEncoder;
|
|
10140
|
+
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)");
|
|
10141
|
+
const { bytes, contentType } = await encoder.encode(raster, encode);
|
|
10142
|
+
if (signal.aborted) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.Aborted, "aborted during image encode");
|
|
10143
|
+
return {
|
|
10144
|
+
contentType,
|
|
10145
|
+
width: raster.width,
|
|
10146
|
+
height: raster.height,
|
|
10147
|
+
bytes
|
|
10148
|
+
};
|
|
10149
|
+
}
|
|
10150
|
+
async handlePagesRenderEncoded(req, signal) {
|
|
10151
|
+
const inner = this.handlePagesRender({
|
|
10152
|
+
...req,
|
|
10153
|
+
kind: "pages.render"
|
|
10154
|
+
}, signal);
|
|
10155
|
+
if (inner.payload.tag !== "pages.render") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `unexpected ${inner.payload.tag}`);
|
|
10156
|
+
const image = await this.encodeRaster(inner.payload.raster, req.encode, signal);
|
|
10157
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10158
|
+
tag: "pages.renderEncoded",
|
|
10159
|
+
image
|
|
10160
|
+
}, [image.bytes.buffer]);
|
|
10161
|
+
}
|
|
10162
|
+
async handleDocumentRenderPageFileEncoded(req, signal) {
|
|
10163
|
+
const inner = this.handleDocumentRenderPageFile({
|
|
10164
|
+
...req,
|
|
10165
|
+
kind: "document.renderPageFile"
|
|
10166
|
+
}, signal);
|
|
10167
|
+
if (inner.payload.tag !== "document.renderPageFile") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `unexpected ${inner.payload.tag}`);
|
|
10168
|
+
const image = await this.encodeRaster(inner.payload.raster, req.encode, signal);
|
|
10169
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10170
|
+
tag: "document.renderPageFileEncoded",
|
|
10171
|
+
pageObjectNumber: inner.payload.pageObjectNumber,
|
|
10172
|
+
pageCount: inner.payload.pageCount,
|
|
10173
|
+
image
|
|
10174
|
+
}, [image.bytes.buffer]);
|
|
10175
|
+
}
|
|
10176
|
+
async handleAnnotationsRenderAppearancesEncoded(req, signal) {
|
|
10177
|
+
const inner = this.handleAnnotationsRenderAppearances({
|
|
10178
|
+
...req,
|
|
10179
|
+
kind: "annotations.renderAppearances"
|
|
10180
|
+
}, signal);
|
|
10181
|
+
if (inner.payload.tag !== "annotations.renderAppearances") throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `unexpected ${inner.payload.tag}`);
|
|
10182
|
+
const { pageState, appearances } = inner.payload.result;
|
|
10183
|
+
const encoded = [];
|
|
10184
|
+
for (const a of appearances) encoded.push({
|
|
10185
|
+
ref: a.ref,
|
|
10186
|
+
mode: a.mode,
|
|
10187
|
+
rect: a.rect,
|
|
10188
|
+
image: await this.encodeRaster(a.raster, req.encode, signal)
|
|
10189
|
+
});
|
|
10190
|
+
return (0, _embedpdf_engine_core_runtime.wirePack)({
|
|
10191
|
+
tag: "annotations.renderAppearancesEncoded",
|
|
10192
|
+
result: {
|
|
10193
|
+
pageState,
|
|
10194
|
+
appearances: encoded
|
|
10195
|
+
}
|
|
10196
|
+
}, encoded.map((e) => e.image.bytes.buffer));
|
|
10197
|
+
}
|
|
9519
10198
|
handleDocumentSaveBuffer(req) {
|
|
9520
10199
|
const session = this.requireSession(req);
|
|
9521
10200
|
const saved = new DocumentSaver(this.runtime, session).saveStandaloneToBuffer(req.mode);
|
|
@@ -9860,6 +10539,7 @@ exports.DocumentActionsReader = DocumentActionsReader;
|
|
|
9860
10539
|
exports.DocumentPagesMutator = PagesMutator;
|
|
9861
10540
|
exports.DocumentSaver = DocumentSaver;
|
|
9862
10541
|
exports.DocumentSession = DocumentSession;
|
|
10542
|
+
exports.EPDF_CHAR_GEOMETRY_LAYOUT = EPDF_CHAR_GEOMETRY_LAYOUT;
|
|
9863
10543
|
exports.EventHub = EventHub;
|
|
9864
10544
|
exports.FontRegistrar = FontRegistrar;
|
|
9865
10545
|
exports.FormMutator = FormMutator;
|
|
@@ -9885,6 +10565,7 @@ exports.SearchReader = SearchReader;
|
|
|
9885
10565
|
exports.SecurityReader = SecurityReader;
|
|
9886
10566
|
exports.SessionEventPublisher = SessionEventPublisher;
|
|
9887
10567
|
exports.WorkerHost = WorkerHost;
|
|
10568
|
+
exports.buildRunsFromRawGlyphs = buildRunsFromRawGlyphs;
|
|
9888
10569
|
exports.computeMutationImpact = computeMutationImpact;
|
|
9889
10570
|
exports.destroyLibrary = destroyLibrary;
|
|
9890
10571
|
exports.disposeFormModel = disposeFormModel;
|