@embedpdf/engine-services 3.0.0-next.10 → 3.0.0-next.12

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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AbortError, EMPTY_TRANSFER, EngineError, EngineErrorCode, NO_ANNOTATION_FLAGS, PAGE_INSERT_BLANK_MAX_COUNT, PDF_SUBTYPE_TO_CODE, PdfAnnotationSubtypeCode, UNKNOWN_WEAK_ANNOTATION_STATE, appearanceImpactOf, buildPageTextLayout, buildSnippet, charMapViolation, charRangeForTextOffsets, foldOptionsFor, foldText, invalidatesWeakIndexRefs, isValidPageObjectNumber, knownWeakAnnotationState, matchLiteral, matchRegex, normalizePdfRect, pdfQuadBounds, revisionTokensEqual, serializeError, shiftsExistingAnnotationIndices, sniffBinaryMetadata, subtypeFromCode, textSegmentsForRange, validateSearchQuery, wirePack } from "@embedpdf/engine-core/runtime";
1
+ import { AbortError, EMPTY_TRANSFER, EngineError, EngineErrorCode, NO_ANNOTATION_FLAGS, PAGE_INSERT_BLANK_MAX_COUNT, PDF_SUBTYPE_TO_CODE, PdfAnnotationSubtypeCode, UNKNOWN_WEAK_ANNOTATION_STATE, appearanceImpactOf, buildPageTextLayout, buildSnippet, charMapViolation, charRangeForTextOffsets, decodeSubmitFormFlags, foldOptionsFor, foldText, invalidatesWeakIndexRefs, isValidPageObjectNumber, knownWeakAnnotationState, matchLiteral, matchRegex, normalizePdfRect, pdfQuadBounds, revisionTokensEqual, serializeError, shiftsExistingAnnotationIndices, sniffBinaryMetadata, subtypeFromCode, textSegmentsForRange, validateSearchQuery, wirePack } from "@embedpdf/engine-core/runtime";
2
2
  import { NULL_PTR } from "@embedpdf/engine-runtime";
3
3
  //#region src/runtime/lifecycle/bootstrap.ts
4
4
  const initialized = /* @__PURE__ */ new WeakSet();
@@ -1211,11 +1211,143 @@ function readI32(mem, ptr, byteOffset = 0) {
1211
1211
  return Number(mem.peek(ptr, "i32", byteOffset));
1212
1212
  }
1213
1213
  //#endregion
1214
+ //#region src/features/destinations/destinationViewCodes.ts
1215
+ /**
1216
+ * `PDFDEST_VIEW_*` fit-type codes (public/fpdf_doc.h) — the shared
1217
+ * vocabulary of `FPDFDest_GetView` (read) and `EPDFDest_CreateView`
1218
+ * (write). XYZ is listed for completeness but never goes through
1219
+ * CreateView (it has its own null-aware constructor).
1220
+ */
1221
+ const DEST_VIEW = {
1222
+ unknown: 0,
1223
+ xyz: 1,
1224
+ fit: 2,
1225
+ fitH: 3,
1226
+ fitV: 4,
1227
+ fitR: 5,
1228
+ fitB: 6,
1229
+ fitBH: 7,
1230
+ fitBV: 8
1231
+ };
1232
+ /** Wire destination kind → PDFDEST_VIEW code, for the non-XYZ kinds. */
1233
+ const VIEW_CODE_BY_KIND = {
1234
+ fit: DEST_VIEW.fit,
1235
+ fitH: DEST_VIEW.fitH,
1236
+ fitV: DEST_VIEW.fitV,
1237
+ fitR: DEST_VIEW.fitR,
1238
+ fitB: DEST_VIEW.fitB,
1239
+ fitBH: DEST_VIEW.fitBH,
1240
+ fitBV: DEST_VIEW.fitBV
1241
+ };
1242
+ //#endregion
1243
+ //#region src/features/destinations/readDestination.ts
1244
+ const MAX_VIEW_PARAMS = 4;
1245
+ /**
1246
+ * Materialise one explicit `PdfDestination` from a live `destPtr`. PDFium
1247
+ * already resolved named destinations before handing us the pointer, so
1248
+ * only explicit arrays arrive here (the rule `PdfDestination` documents).
1249
+ *
1250
+ * Returns `null` when the destination is unreadable: no visible indirect page
1251
+ * in the document, or an unknown fit type. Coordinates stay raw PDF user
1252
+ * space — no conversion at this layer.
1253
+ *
1254
+ * Shared by the link reader, `goto` action-node payloads, and the document
1255
+ * `openDestination`; the outline/bookmark port reuses it too.
1256
+ */
1257
+ function readDestination(fn, mem, docPtr, destPtr) {
1258
+ const pageObjectNumber = fn.EPDFDest_GetPageObjectNumber(docPtr, destPtr);
1259
+ if (pageObjectNumber === 0) return null;
1260
+ const view = withScratchN(mem, [4, MAX_VIEW_PARAMS * 4], ([countPtr, paramsPtr]) => {
1261
+ const code = fn.FPDFDest_GetView(destPtr, countPtr, paramsPtr);
1262
+ const count = Math.min(readI32(mem, countPtr), MAX_VIEW_PARAMS);
1263
+ const params = [];
1264
+ for (let i = 0; i < count; i++) params.push(readF32(mem, paramsPtr, i * 4));
1265
+ return {
1266
+ code,
1267
+ params
1268
+ };
1269
+ });
1270
+ const at = (i) => i < view.params.length ? view.params[i] : null;
1271
+ switch (view.code) {
1272
+ case DEST_VIEW.xyz: return {
1273
+ kind: "xyz",
1274
+ pageObjectNumber,
1275
+ ...readXyzLocation(fn, mem, destPtr)
1276
+ };
1277
+ case DEST_VIEW.fit: return {
1278
+ kind: "fit",
1279
+ pageObjectNumber
1280
+ };
1281
+ case DEST_VIEW.fitH: return {
1282
+ kind: "fitH",
1283
+ pageObjectNumber,
1284
+ top: at(0)
1285
+ };
1286
+ case DEST_VIEW.fitV: return {
1287
+ kind: "fitV",
1288
+ pageObjectNumber,
1289
+ left: at(0)
1290
+ };
1291
+ case DEST_VIEW.fitR:
1292
+ if (view.params.length < 4) return {
1293
+ kind: "fit",
1294
+ pageObjectNumber
1295
+ };
1296
+ return {
1297
+ kind: "fitR",
1298
+ pageObjectNumber,
1299
+ left: view.params[0],
1300
+ bottom: view.params[1],
1301
+ right: view.params[2],
1302
+ top: view.params[3]
1303
+ };
1304
+ case DEST_VIEW.fitB: return {
1305
+ kind: "fitB",
1306
+ pageObjectNumber
1307
+ };
1308
+ case DEST_VIEW.fitBH: return {
1309
+ kind: "fitBH",
1310
+ pageObjectNumber,
1311
+ top: at(0)
1312
+ };
1313
+ case DEST_VIEW.fitBV: return {
1314
+ kind: "fitBV",
1315
+ pageObjectNumber,
1316
+ left: at(0)
1317
+ };
1318
+ default: return null;
1319
+ }
1320
+ }
1321
+ /** `/XYZ` null-aware axes via `FPDFDest_GetLocationInPage` (FPDF_BOOL is 4 bytes). */
1322
+ function readXyzLocation(fn, mem, destPtr) {
1323
+ return withScratchN(mem, [
1324
+ 4,
1325
+ 4,
1326
+ 4,
1327
+ 4,
1328
+ 4,
1329
+ 4
1330
+ ], ([hasLeft, hasTop, hasZoom, left, top, zoom]) => {
1331
+ if (!fn.FPDFDest_GetLocationInPage(destPtr, hasLeft, hasTop, hasZoom, left, top, zoom)) return {
1332
+ left: null,
1333
+ top: null,
1334
+ zoom: null
1335
+ };
1336
+ return {
1337
+ left: readI32(mem, hasLeft) ? readF32(mem, left) : null,
1338
+ top: readI32(mem, hasTop) ? readF32(mem, top) : null,
1339
+ zoom: readI32(mem, hasZoom) ? readF32(mem, zoom) : null
1340
+ };
1341
+ });
1342
+ }
1343
+ //#endregion
1214
1344
  //#region src/features/actions/ActionModelReader.ts
1215
1345
  const DEFAULT_ACTION_READ_BUDGET = {
1216
1346
  maxModels: 512,
1217
1347
  maxNodes: 16384,
1218
- maxScriptCodeUnits: 16e6
1348
+ maxScriptCodeUnits: 16e6,
1349
+ maxTargetEntries: 4096,
1350
+ maxPayloadCodeUnits: 262144
1219
1351
  };
1220
1352
  const ACTION_TYPE_BY_CODE = {
1221
1353
  0: "unknown",
@@ -1243,22 +1375,20 @@ const WARNING_MALFORMED_NEXT = 2;
1243
1375
  const WARNING_INCOMPLETE = 4;
1244
1376
  const INVALID_NODE_U32 = 4294967295;
1245
1377
  /** Decode one `EPDF_ACTION_TYPE_*` code — the shared vocabulary consumers
1246
- * (the link-target projection) use so the two surfaces cannot drift. */
1378
+ * use so action-shaped surfaces cannot drift. */
1247
1379
  function actionTypeFromCode(code) {
1248
1380
  return ACTION_TYPE_BY_CODE[code] ?? "unknown";
1249
1381
  }
1250
- /** Is this `EPDFAction_GetRootNode`/`GetNextAt` result a real node id?
1251
- * (`EPDF_ACTION_NODE_INVALID` may surface as -1 or u32 max via cwrap.) */
1252
- function isValidActionNodeId(raw) {
1253
- return raw !== -1 && raw >>> 0 !== INVALID_NODE_U32;
1254
- }
1255
- /** Mutable aggregate budget shared by every action model in one read job. */
1382
+ /** Mutable aggregate budget shared by every action model in one read job.
1383
+ * Payload budgets are RESERVED before the backing buffer is allocated. */
1256
1384
  var ActionReadBudgetTracker = class {
1257
1385
  constructor(limits = DEFAULT_ACTION_READ_BUDGET) {
1258
1386
  this.limits = limits;
1259
1387
  _defineProperty(this, "models", 0);
1260
1388
  _defineProperty(this, "nodes", 0);
1261
1389
  _defineProperty(this, "scriptCodeUnits", 0);
1390
+ _defineProperty(this, "targetEntries", 0);
1391
+ _defineProperty(this, "payloadCodeUnits", 0);
1262
1392
  }
1263
1393
  addModel(nodeCount) {
1264
1394
  this.models += 1;
@@ -1269,11 +1399,23 @@ var ActionReadBudgetTracker = class {
1269
1399
  this.scriptCodeUnits += script.length;
1270
1400
  this.assertWithinBudget();
1271
1401
  }
1402
+ /** Charge target entries BEFORE the target array is walked. */
1403
+ reserveTargets(count) {
1404
+ this.targetEntries += count;
1405
+ this.assertWithinBudget();
1406
+ }
1407
+ /** Charge payload text BEFORE the scratch buffer for it is allocated. */
1408
+ reservePayloadBytes(length) {
1409
+ this.payloadCodeUnits += length;
1410
+ this.assertWithinBudget();
1411
+ }
1272
1412
  assertWithinBudget() {
1273
- if (this.models > this.limits.maxModels || this.nodes > this.limits.maxNodes || this.scriptCodeUnits > this.limits.maxScriptCodeUnits) throw new EngineError(EngineErrorCode.MalformedPdf, "aggregate PDF action read budget exceeded", { details: {
1413
+ 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 EngineError(EngineErrorCode.MalformedPdf, "aggregate PDF action read budget exceeded", { details: {
1274
1414
  models: this.models,
1275
1415
  nodes: this.nodes,
1276
1416
  scriptCodeUnits: this.scriptCodeUnits,
1417
+ targetEntries: this.targetEntries,
1418
+ payloadCodeUnits: this.payloadCodeUnits,
1277
1419
  limits: this.limits
1278
1420
  } });
1279
1421
  }
@@ -1281,8 +1423,17 @@ var ActionReadBudgetTracker = class {
1281
1423
  /**
1282
1424
  * Convert and close one detached native action model. This is the only
1283
1425
  * native-model walker used by document, page, field, and annotation reads.
1426
+ *
1427
+ * `docPtr` resolves `goto` destinations (page identity + named-destination
1428
+ * lookup); every caller reads within an open document session.
1429
+ *
1430
+ * Payload rule: a node whose payload cannot be materialised (a goto without
1431
+ * a resolvable destination, a javascript node with no `/JS`, a withheld
1432
+ * target list) degrades to `{ type: 'unknown' }` with its original `/S` on
1433
+ * `subtype`, and the tree gains the `'payload-dropped'` warning — impossible
1434
+ * states stay unrepresentable while malformed documents stay readable.
1284
1435
  */
1285
- function readActionModel(fn, mem, modelPtr, budget) {
1436
+ function readActionModel(fn, mem, docPtr, modelPtr, budget) {
1286
1437
  if (modelPtr === NULL_PTR) return null;
1287
1438
  try {
1288
1439
  const nodeCount = fn.EPDFAction_GetNodeCount(modelPtr);
@@ -1292,13 +1443,61 @@ function readActionModel(fn, mem, modelPtr, budget) {
1292
1443
  const incomplete = !fn.EPDFAction_IsComplete(modelPtr);
1293
1444
  const rootId = normalizeOptionalNodeId(fn.EPDFAction_GetRootNode(modelPtr), nodeCount);
1294
1445
  const visiting = /* @__PURE__ */ new Set();
1446
+ let payloadDropped = false;
1447
+ /** Probe the two-call length first and charge the budget BEFORE the
1448
+ * scratch buffer for the value is allocated. */
1449
+ const readPayloadString = (call) => {
1450
+ const length = call(NULL_PTR, 0);
1451
+ if (length <= 0) return null;
1452
+ budget.reservePayloadBytes(length);
1453
+ return readUtf8String(mem, call);
1454
+ };
1455
+ const readOutBool = (read) => withScratchN(mem, [4], ([outPtr]) => read(outPtr) ? readI32(mem, outPtr) !== 0 : null);
1456
+ const readTargetObjectNumber = (nodeId, index) => withScratchN(mem, [4], ([outPtr]) => {
1457
+ if (!fn.EPDFAction_GetNodeTargetObjectNumber(modelPtr, nodeId, index, outPtr)) return null;
1458
+ const value = readI32(mem, outPtr) >>> 0;
1459
+ return value === 0 ? null : value;
1460
+ });
1461
+ const readTargets = (nodeId, count) => {
1462
+ const targets = [];
1463
+ for (let index = 0; index < count; index++) {
1464
+ const name = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeTargetName(modelPtr, nodeId, index, buf, capacity));
1465
+ if (name !== null) {
1466
+ targets.push({
1467
+ kind: "name",
1468
+ name
1469
+ });
1470
+ continue;
1471
+ }
1472
+ const objectNumber = readTargetObjectNumber(nodeId, index);
1473
+ if (objectNumber === null) return null;
1474
+ targets.push({
1475
+ kind: "objectNumber",
1476
+ objectNumber
1477
+ });
1478
+ }
1479
+ return targets;
1480
+ };
1481
+ const readResetFormState = (nodeId) => withScratchN(mem, [4, 4], ([hasFieldsPtr, excludePtr]) => {
1482
+ if (!fn.EPDFAction_GetNodeResetForm(modelPtr, nodeId, hasFieldsPtr, excludePtr)) return null;
1483
+ return {
1484
+ hasFields: readI32(mem, hasFieldsPtr) !== 0,
1485
+ exclude: readI32(mem, excludePtr) !== 0
1486
+ };
1487
+ });
1488
+ const readSubmitFormState = (nodeId) => withScratchN(mem, [4, 4], ([hasFieldsPtr, flagsPtr]) => {
1489
+ if (!fn.EPDFAction_GetNodeSubmitForm(modelPtr, nodeId, hasFieldsPtr, flagsPtr)) return null;
1490
+ return {
1491
+ hasFields: readI32(mem, hasFieldsPtr) !== 0,
1492
+ flags: readI32(mem, flagsPtr) >>> 0
1493
+ };
1494
+ });
1295
1495
  const readNode = (nodeId) => {
1296
1496
  if (visiting.has(nodeId)) throw malformedActionModel("native action model contains a cycle", { nodeId });
1297
1497
  visiting.add(nodeId);
1298
1498
  try {
1499
+ const type = actionTypeFromCode(fn.EPDFAction_GetNodeType(modelPtr, nodeId));
1299
1500
  const subtype = readUtf8String(mem, (buf, capacity) => fn.EPDFAction_GetNodeSubtype(modelPtr, nodeId, buf, capacity)) ?? "";
1300
- const script = fn.EPDFAction_NodeHasJavaScript(modelPtr, nodeId) ? readUtf16String(mem, (buf, capacity) => fn.EPDFAction_GetNodeJavaScript(modelPtr, nodeId, buf, capacity)) ?? "" : void 0;
1301
- if (script !== void 0) budget.addScript(script);
1302
1501
  const nextCount = fn.EPDFAction_GetNextCount(modelPtr, nodeId);
1303
1502
  if (!Number.isInteger(nextCount) || nextCount < 0) throw malformedActionModel("invalid action child count", {
1304
1503
  nodeId,
@@ -1309,21 +1508,157 @@ function readActionModel(fn, mem, modelPtr, budget) {
1309
1508
  const childId = normalizeNodeId(fn.EPDFAction_GetNextAt(modelPtr, nodeId, index), nodeCount);
1310
1509
  next.push(readNode(childId));
1311
1510
  }
1312
- return {
1313
- type: ACTION_TYPE_BY_CODE[fn.EPDFAction_GetNodeType(modelPtr, nodeId)] ?? "unknown",
1314
- subtype,
1315
- ...script !== void 0 ? { script } : {},
1316
- next
1511
+ const degraded = () => {
1512
+ payloadDropped = true;
1513
+ return {
1514
+ type: "unknown",
1515
+ subtype,
1516
+ next
1517
+ };
1317
1518
  };
1519
+ switch (type) {
1520
+ case "javascript": {
1521
+ if (!fn.EPDFAction_NodeHasJavaScript(modelPtr, nodeId)) return degraded();
1522
+ const script = readUtf16String(mem, (buf, capacity) => fn.EPDFAction_GetNodeJavaScript(modelPtr, nodeId, buf, capacity)) ?? "";
1523
+ budget.addScript(script);
1524
+ return {
1525
+ type,
1526
+ subtype,
1527
+ script,
1528
+ next
1529
+ };
1530
+ }
1531
+ case "rendition": {
1532
+ if (!fn.EPDFAction_NodeHasJavaScript(modelPtr, nodeId)) return {
1533
+ type,
1534
+ subtype,
1535
+ next
1536
+ };
1537
+ const script = readUtf16String(mem, (buf, capacity) => fn.EPDFAction_GetNodeJavaScript(modelPtr, nodeId, buf, capacity)) ?? "";
1538
+ budget.addScript(script);
1539
+ return {
1540
+ type,
1541
+ subtype,
1542
+ script,
1543
+ next
1544
+ };
1545
+ }
1546
+ case "goto": {
1547
+ const destPtr = fn.EPDFAction_GetNodeDest(docPtr, modelPtr, nodeId);
1548
+ const destination = destPtr !== NULL_PTR ? readDestination(fn, mem, docPtr, destPtr) : null;
1549
+ return destination ? {
1550
+ type,
1551
+ subtype,
1552
+ destination,
1553
+ next
1554
+ } : degraded();
1555
+ }
1556
+ case "uri": {
1557
+ const uri = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeURI(docPtr, modelPtr, nodeId, buf, capacity));
1558
+ if (uri === null) return degraded();
1559
+ return {
1560
+ type,
1561
+ subtype,
1562
+ uri,
1563
+ isMap: readOutBool((outPtr) => fn.EPDFAction_GetNodeURIIsMap(modelPtr, nodeId, outPtr)) ?? false,
1564
+ next
1565
+ };
1566
+ }
1567
+ case "named": {
1568
+ const name = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeName(modelPtr, nodeId, buf, capacity));
1569
+ return name === null ? degraded() : {
1570
+ type,
1571
+ subtype,
1572
+ name,
1573
+ next
1574
+ };
1575
+ }
1576
+ case "hide": {
1577
+ const count = fn.EPDFAction_GetNodeTargetCount(modelPtr, nodeId);
1578
+ if (!Number.isInteger(count) || count < 0) return degraded();
1579
+ budget.reserveTargets(count);
1580
+ const targets = readTargets(nodeId, count);
1581
+ if (targets === null) return degraded();
1582
+ return {
1583
+ type,
1584
+ subtype,
1585
+ targets,
1586
+ hide: readOutBool((outPtr) => fn.EPDFAction_GetNodeHideFlag(modelPtr, nodeId, outPtr)) ?? true,
1587
+ next
1588
+ };
1589
+ }
1590
+ case "reset-form": {
1591
+ const state = readResetFormState(nodeId);
1592
+ const count = fn.EPDFAction_GetNodeTargetCount(modelPtr, nodeId);
1593
+ if (state === null || !Number.isInteger(count) || count < 0) return degraded();
1594
+ budget.reserveTargets(count);
1595
+ const fields = state.hasFields ? readTargets(nodeId, count) : null;
1596
+ if (state.hasFields && fields === null) return degraded();
1597
+ return {
1598
+ type,
1599
+ subtype,
1600
+ fields,
1601
+ exclude: state.exclude,
1602
+ next
1603
+ };
1604
+ }
1605
+ case "submit-form": {
1606
+ if (typeof fn.EPDFAction_GetNodeSubmitForm !== "function") return {
1607
+ type,
1608
+ subtype,
1609
+ next
1610
+ };
1611
+ const state = readSubmitFormState(nodeId);
1612
+ if (state === null) return degraded();
1613
+ const url = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeSubmitFormURL(modelPtr, nodeId, buf, capacity));
1614
+ if (url === null) return degraded();
1615
+ const count = fn.EPDFAction_GetNodeTargetCount(modelPtr, nodeId);
1616
+ if (!Number.isInteger(count) || count < 0) return degraded();
1617
+ budget.reserveTargets(count);
1618
+ const fields = state.hasFields ? readTargets(nodeId, count) : null;
1619
+ if (state.hasFields && fields === null) return degraded();
1620
+ const charSet = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeSubmitFormCharSet(modelPtr, nodeId, buf, capacity));
1621
+ return {
1622
+ type,
1623
+ subtype,
1624
+ payload: {
1625
+ url,
1626
+ fields,
1627
+ flags: decodeSubmitFormFlags(state.flags),
1628
+ ...charSet === null ? {} : { charSet }
1629
+ },
1630
+ next
1631
+ };
1632
+ }
1633
+ case "goto-remote":
1634
+ case "goto-embedded":
1635
+ case "launch": {
1636
+ const filePath = readPayloadString((buf, capacity) => fn.EPDFAction_GetNodeFilePath(modelPtr, nodeId, buf, capacity));
1637
+ return filePath === null ? degraded() : {
1638
+ type,
1639
+ subtype,
1640
+ filePath,
1641
+ next
1642
+ };
1643
+ }
1644
+ default: return {
1645
+ type,
1646
+ subtype,
1647
+ next
1648
+ };
1649
+ }
1318
1650
  } finally {
1319
1651
  visiting.delete(nodeId);
1320
1652
  }
1321
1653
  };
1654
+ const root = rootId === null ? null : readNode(rootId);
1655
+ const warnings = warningsFromFlags(warningFlags, incomplete);
1656
+ if (payloadDropped) warnings.push("payload-dropped");
1322
1657
  return {
1323
- root: rootId === null ? null : readNode(rootId),
1658
+ root,
1324
1659
  incomplete,
1325
1660
  warningFlags,
1326
- warnings: warningsFromFlags(warningFlags, incomplete)
1661
+ warnings
1327
1662
  };
1328
1663
  } finally {
1329
1664
  fn.EPDFAction_CloseModel(modelPtr);
@@ -1396,15 +1731,16 @@ var PagesReader = class {
1396
1731
  });
1397
1732
  return {
1398
1733
  pageCount: pages.length,
1399
- pages
1734
+ pages,
1735
+ namedPages: readNamedPages(fn, mem, docPtr)
1400
1736
  };
1401
1737
  });
1402
1738
  }
1403
1739
  };
1404
1740
  function readPageActions(fn, mem, docPtr, pageObjectNumber, budget) {
1405
1741
  const actions = {};
1406
- const open = readActionModel(fn, mem, fn.EPDFDoc_GetPageActionModel(docPtr, pageObjectNumber, 0), budget);
1407
- 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);
1408
1744
  if (open) actions.open = open;
1409
1745
  if (close) actions.close = close;
1410
1746
  return open || close ? actions : void 0;
@@ -1461,6 +1797,41 @@ function readUserUnit(fn, mem, docPtr, index, userUnitPtr) {
1461
1797
  function readLabel(fn, mem, docPtr, index) {
1462
1798
  return readUtf16String(mem, (buf, capacity) => fn.FPDF_GetPageLabel(docPtr, index, buf, capacity), null) || null;
1463
1799
  }
1800
+ const NAMED_PAGE_TREES = [0, 1];
1801
+ const NAMED_PAGE_KIND_PAGE = 0;
1802
+ const NAMED_PAGE_KIND_TEMPLATE = 1;
1803
+ /**
1804
+ * The catalog's `/Names /Pages` and `/Names /Templates` registrations, in
1805
+ * tree order, each value classified by the fork (page / template /
1806
+ * dangling). Page-identity data like `label`, so it ships inside the same
1807
+ * snapshot — a registration is only meaningful against the page set that
1808
+ * contains its target.
1809
+ */
1810
+ function readNamedPages(fn, mem, docPtr) {
1811
+ const entries = [];
1812
+ withScratchN(mem, [4, 4], ([objNumPtr, kindPtr]) => {
1813
+ for (const tree of NAMED_PAGE_TREES) {
1814
+ const count = fn.EPDFDoc_GetNamedPageCount(docPtr, tree);
1815
+ for (let index = 0; index < count; index++) {
1816
+ const name = readUtf16String(mem, (buf, capacity) => fn.EPDFDoc_GetNamedPageAt(docPtr, tree, index, buf, capacity, objNumPtr, kindPtr), "");
1817
+ if (name === null) continue;
1818
+ const objectNumber = Number(mem.peek(objNumPtr, "i32")) >>> 0;
1819
+ const kind = Number(mem.peek(kindPtr, "i32"));
1820
+ entries.push({
1821
+ name,
1822
+ target: kind === NAMED_PAGE_KIND_PAGE ? {
1823
+ kind: "page",
1824
+ pageObjectNumber: objectNumber
1825
+ } : kind === NAMED_PAGE_KIND_TEMPLATE ? {
1826
+ kind: "template",
1827
+ objectNumber
1828
+ } : { kind: "dangling" }
1829
+ });
1830
+ }
1831
+ }
1832
+ });
1833
+ return entries;
1834
+ }
1464
1835
  //#endregion
1465
1836
  //#region src/features/pages/PagesMutator.ts
1466
1837
  /**
@@ -1608,6 +1979,38 @@ var PagesMutator = class {
1608
1979
  cache: null
1609
1980
  };
1610
1981
  }
1982
+ /**
1983
+ * Register `name` → page in `/Names /Pages` (create, or replace what the
1984
+ * key points at); with `replace`, drop that other key first — a rename as
1985
+ * one job. Named pages are LAYOUT: page identity and order are untouched
1986
+ * (no registry refresh, no revision bumps) and the fresh snapshot is
1987
+ * returned like `move()`.
1988
+ */
1989
+ setName(input, signal) {
1990
+ throwIfAborted(signal);
1991
+ if (input.name.length === 0) throw new EngineError(EngineErrorCode.InvalidArg, "pages.setName requires a non-empty name");
1992
+ const { fn, mem } = this.runtime;
1993
+ const docPtr = this.session.requireDocPtr();
1994
+ this.session.recordByObjectNumber(input.pageObjectNumber);
1995
+ if (input.replace !== void 0 && input.replace !== input.name && input.replace.length > 0) writeUtf16String(mem, input.replace, (ptr) => fn.EPDFDoc_RemoveNamedPage(docPtr, ptr));
1996
+ if (!writeUtf16String(mem, input.name, (ptr) => fn.EPDFDoc_SetNamedPage(docPtr, ptr, input.pageObjectNumber))) throw new EngineError(EngineErrorCode.Unknown, `EPDFDoc_SetNamedPage rejected '${input.name}' for page ${input.pageObjectNumber}`);
1997
+ return {
1998
+ layout: new PagesReader(this.runtime, this.session).read(signal),
1999
+ cache: null
2000
+ };
2001
+ }
2002
+ /** Remove one `/Names /Pages` registration; the page stays. */
2003
+ removeName(input, signal) {
2004
+ throwIfAborted(signal);
2005
+ if (input.name.length === 0) throw new EngineError(EngineErrorCode.InvalidArg, "pages.removeName requires a non-empty name");
2006
+ const { fn, mem } = this.runtime;
2007
+ const docPtr = this.session.requireDocPtr();
2008
+ if (!writeUtf16String(mem, input.name, (ptr) => fn.EPDFDoc_RemoveNamedPage(docPtr, ptr))) throw new EngineError(EngineErrorCode.NotFound, `pages.removeName: no /Names /Pages registration '${input.name}'`);
2009
+ return {
2010
+ layout: new PagesReader(this.runtime, this.session).read(signal),
2011
+ cache: null
2012
+ };
2013
+ }
1611
2014
  /** Shared input check: non-empty, no duplicate PONs. */
1612
2015
  requireUniquePons(op, pageObjectNumbers) {
1613
2016
  if (pageObjectNumbers.length === 0) throw new EngineError(EngineErrorCode.InvalidArg, `${op} requires at least one page`);
@@ -1620,7 +2023,7 @@ var PagesMutator = class {
1620
2023
  };
1621
2024
  //#endregion
1622
2025
  //#region src/features/pages/PagesExtractor.ts
1623
- const FPDF_NO_INCREMENTAL$1 = 2;
2026
+ const FPDF_NO_INCREMENTAL$2 = 2;
1624
2027
  /**
1625
2028
  * Export a subset of pages as a standalone PDF. A READ over the session:
1626
2029
  * `FPDF_ImportPagesByIndex` copies pages into a scratch document, so the
@@ -1659,7 +2062,7 @@ var PagesExtractor = class {
1659
2062
  for (let i = 0; i < indices.length; i++) mem.poke(idxPtr, "i32", indices[i], 4 * i);
1660
2063
  if (!fn.FPDF_ImportPagesByIndex(destPtr, srcPtr, idxPtr, indices.length, 0)) throw new EngineError(EngineErrorCode.Unknown, `FPDF_ImportPagesByIndex rejected the request (indices=[${indices.join(",")}])`);
1661
2064
  mem.poke(sizePtr, "i32", 0);
1662
- pdfPtr = fn.EPDF_SaveDocumentToOwnedBuffer(destPtr, FPDF_NO_INCREMENTAL$1, sizePtr);
2065
+ pdfPtr = fn.EPDF_SaveDocumentToOwnedBuffer(destPtr, FPDF_NO_INCREMENTAL$2, sizePtr);
1663
2066
  const size = Number(mem.peek(sizePtr, "i32"));
1664
2067
  if (!pdfPtr || size <= 0) throw new EngineError(EngineErrorCode.DocOpenFailed, "failed to save extracted pages");
1665
2068
  const bytes = mem.readBytes(pdfPtr, size);
@@ -2211,6 +2614,13 @@ function readQuadPoints(fn, mem, annotPtr) {
2211
2614
  return out;
2212
2615
  });
2213
2616
  }
2617
+ /**
2618
+ * `/Name` as text, whatever its value (`EPDFAnnot_GetName`): a standard
2619
+ * icon/stamp name or a custom identifier. `null` when absent.
2620
+ */
2621
+ function readAnnotName(fn, mem, annotPtr) {
2622
+ return readUtf8String(mem, (buf, capacity) => fn.EPDFAnnot_GetName(annotPtr, buf, capacity));
2623
+ }
2214
2624
  //#endregion
2215
2625
  //#region src/features/annotations/internal/read/readAnnotationTransformMetadata.ts
2216
2626
  /**
@@ -2460,29 +2870,29 @@ function normalizeChecksum(raw) {
2460
2870
  //#endregion
2461
2871
  //#region src/features/annotations/internal/annotationIcon.ts
2462
2872
  /**
2463
- * `/Name` icon ↔ `FPDF_ANNOT_NAME` enum code mapping for text and
2464
- * file-attachment annotations (`EPDFAnnot_SetName` / `EPDFAnnot_GetName`).
2465
- * Codes mirror the `FPDF_ANNOT_NAME` enum in `public/fpdf_annot.h` —
2466
- * same pattern as `stampName.ts` (whose stamp block starts at 13, right
2467
- * after these) keeping engine-core PDFium-free.
2468
- */
2469
- const NOTE_ICON_TO_CODE = Object.freeze({
2470
- comment: 0,
2471
- key: 1,
2472
- note: 2,
2473
- help: 3,
2474
- "new-paragraph": 4,
2475
- paragraph: 5,
2476
- insert: 6
2873
+ * `/Name` icon vocabulary ↔ PDF name mapping for text and file-attachment
2874
+ * annotations (ISO 32000-2 tables 175 and 184). The wire speaks the
2875
+ * kebab-case ids; `EPDFAnnot_SetName` / `EPDFAnnot_GetName` speak the PDF
2876
+ * names. An unknown PDF name reads as the subtype's default (the reader
2877
+ * draws the default glyph for names it does not know — spec behavior).
2878
+ */
2879
+ const NOTE_ICON_TO_NAME = Object.freeze({
2880
+ comment: "Comment",
2881
+ key: "Key",
2882
+ note: "Note",
2883
+ help: "Help",
2884
+ "new-paragraph": "NewParagraph",
2885
+ paragraph: "Paragraph",
2886
+ insert: "Insert"
2477
2887
  });
2478
- const NOTE_CODE_TO_ICON = Object.freeze(Object.fromEntries(Object.entries(NOTE_ICON_TO_CODE).map(([icon, code]) => [code, icon])));
2479
- const FILE_ICON_TO_CODE = Object.freeze({
2480
- graph: 7,
2481
- "push-pin": 8,
2482
- paperclip: 9,
2483
- tag: 10
2888
+ const NOTE_NAME_TO_ICON = Object.freeze(Object.fromEntries(Object.entries(NOTE_ICON_TO_NAME).map(([icon, name]) => [name, icon])));
2889
+ const FILE_ICON_TO_NAME = Object.freeze({
2890
+ graph: "Graph",
2891
+ "push-pin": "PushPin",
2892
+ paperclip: "Paperclip",
2893
+ tag: "Tag"
2484
2894
  });
2485
- const FILE_CODE_TO_ICON = Object.freeze(Object.fromEntries(Object.entries(FILE_ICON_TO_CODE).map(([icon, code]) => [code, icon])));
2895
+ const FILE_NAME_TO_ICON = Object.freeze(Object.fromEntries(Object.entries(FILE_ICON_TO_NAME).map(([icon, name]) => [name, icon])));
2486
2896
  //#endregion
2487
2897
  //#region src/features/annotations/internal/read/readFileAttachmentAnnotation.ts
2488
2898
  /** Default `/C` — matches the generator's default icon fill and the writer default. */
@@ -2504,7 +2914,7 @@ function readFileAttachment(fn, mem, annotPtr, base) {
2504
2914
  const color = readAnnotColor(fn, mem, annotPtr) ?? { ...DEFAULT_FILE_ATTACHMENT_COLOR$1 };
2505
2915
  const ca = readAnnotOpacity(fn, mem, annotPtr);
2506
2916
  const opacity = ca == null ? 1 : Math.max(0, Math.min(1, ca));
2507
- const icon = FILE_CODE_TO_ICON[fn.EPDFAnnot_GetName(annotPtr)] ?? DEFAULT_FILE_ATTACHMENT_ICON;
2917
+ const icon = FILE_NAME_TO_ICON[readAnnotName(fn, mem, annotPtr) ?? ""] ?? DEFAULT_FILE_ATTACHMENT_ICON;
2508
2918
  const attachmentPtr = fn.FPDFAnnot_GetFileAttachment(annotPtr);
2509
2919
  const file = attachmentPtr ? readAttachmentFileInfo(fn, mem, attachmentPtr) : { name: "" };
2510
2920
  return {
@@ -2822,167 +3232,67 @@ function readLine(fn, mem, annotPtr, base) {
2822
3232
  };
2823
3233
  }
2824
3234
  //#endregion
2825
- //#region src/features/annotations/internal/destinationViewCodes.ts
2826
- /**
2827
- * `PDFDEST_VIEW_*` fit-type codes (public/fpdf_doc.h) — the shared
2828
- * vocabulary of `FPDFDest_GetView` (read) and `EPDFDest_CreateView`
2829
- * (write). XYZ is listed for completeness but never goes through
2830
- * CreateView (it has its own null-aware constructor).
2831
- */
2832
- const DEST_VIEW = {
2833
- unknown: 0,
2834
- xyz: 1,
2835
- fit: 2,
2836
- fitH: 3,
2837
- fitV: 4,
2838
- fitR: 5,
2839
- fitB: 6,
2840
- fitBH: 7,
2841
- fitBV: 8
2842
- };
2843
- /** Wire destination kind → PDFDEST_VIEW code, for the non-XYZ kinds. */
2844
- const VIEW_CODE_BY_KIND = {
2845
- fit: DEST_VIEW.fit,
2846
- fitH: DEST_VIEW.fitH,
2847
- fitV: DEST_VIEW.fitV,
2848
- fitR: DEST_VIEW.fitR,
2849
- fitB: DEST_VIEW.fitB,
2850
- fitBH: DEST_VIEW.fitBH,
2851
- fitBV: DEST_VIEW.fitBV
2852
- };
2853
- //#endregion
2854
- //#region src/features/annotations/internal/read/readDestination.ts
2855
- const MAX_VIEW_PARAMS = 4;
2856
- /**
2857
- * Materialise one explicit `PdfDestination` from a live `destPtr`. PDFium
2858
- * already resolved named destinations before handing us the pointer, so
2859
- * only explicit arrays arrive here (the rule `PdfDestination` documents).
2860
- *
2861
- * Returns `null` when the destination is unreadable: no visible indirect page
2862
- * in the document, or an unknown fit type. Coordinates stay raw PDF user
2863
- * space — no conversion at this layer.
2864
- *
2865
- * Shared by the link reader today; the outline/bookmark port reuses it.
2866
- */
2867
- function readDestination(fn, mem, docPtr, destPtr) {
2868
- const pageObjectNumber = fn.EPDFDest_GetPageObjectNumber(docPtr, destPtr);
2869
- if (pageObjectNumber === 0) return null;
2870
- const view = withScratchN(mem, [4, MAX_VIEW_PARAMS * 4], ([countPtr, paramsPtr]) => {
2871
- const code = fn.FPDFDest_GetView(destPtr, countPtr, paramsPtr);
2872
- const count = Math.min(readI32(mem, countPtr), MAX_VIEW_PARAMS);
2873
- const params = [];
2874
- for (let i = 0; i < count; i++) params.push(readF32(mem, paramsPtr, i * 4));
2875
- return {
2876
- code,
2877
- params
2878
- };
2879
- });
2880
- const at = (i) => i < view.params.length ? view.params[i] : null;
2881
- switch (view.code) {
2882
- case DEST_VIEW.xyz: return {
2883
- kind: "xyz",
2884
- pageObjectNumber,
2885
- ...readXyzLocation(fn, mem, destPtr)
2886
- };
2887
- case DEST_VIEW.fit: return {
2888
- kind: "fit",
2889
- pageObjectNumber
2890
- };
2891
- case DEST_VIEW.fitH: return {
2892
- kind: "fitH",
2893
- pageObjectNumber,
2894
- top: at(0)
2895
- };
2896
- case DEST_VIEW.fitV: return {
2897
- kind: "fitV",
2898
- pageObjectNumber,
2899
- left: at(0)
2900
- };
2901
- case DEST_VIEW.fitR:
2902
- if (view.params.length < 4) return {
2903
- kind: "fit",
2904
- pageObjectNumber
2905
- };
2906
- return {
2907
- kind: "fitR",
2908
- pageObjectNumber,
2909
- left: view.params[0],
2910
- bottom: view.params[1],
2911
- right: view.params[2],
2912
- top: view.params[3]
2913
- };
2914
- case DEST_VIEW.fitB: return {
2915
- kind: "fitB",
2916
- pageObjectNumber
2917
- };
2918
- case DEST_VIEW.fitBH: return {
2919
- kind: "fitBH",
2920
- pageObjectNumber,
2921
- top: at(0)
2922
- };
2923
- case DEST_VIEW.fitBV: return {
2924
- kind: "fitBV",
2925
- pageObjectNumber,
2926
- left: at(0)
2927
- };
2928
- default: return null;
2929
- }
2930
- }
2931
- /** `/XYZ` null-aware axes via `FPDFDest_GetLocationInPage` (FPDF_BOOL is 4 bytes). */
2932
- function readXyzLocation(fn, mem, destPtr) {
2933
- return withScratchN(mem, [
2934
- 4,
2935
- 4,
2936
- 4,
2937
- 4,
2938
- 4,
2939
- 4
2940
- ], ([hasLeft, hasTop, hasZoom, left, top, zoom]) => {
2941
- if (!fn.FPDFDest_GetLocationInPage(destPtr, hasLeft, hasTop, hasZoom, left, top, zoom)) return {
2942
- left: null,
2943
- top: null,
2944
- zoom: null
2945
- };
2946
- return {
2947
- left: readI32(mem, hasLeft) ? readF32(mem, left) : null,
2948
- top: readI32(mem, hasTop) ? readF32(mem, top) : null,
2949
- zoom: readI32(mem, hasZoom) ? readF32(mem, zoom) : null
2950
- };
2951
- });
2952
- }
2953
- //#endregion
2954
3235
  //#region src/features/annotations/internal/read/readLinkAnnotation.ts
2955
3236
  /**
2956
3237
  * Link reader: rect/flags/relationship ride the base (a link grouped to an
2957
3238
  * annotation is plain `inReplyTo` + `replyType: 'group'`); this module only
2958
3239
  * materialises the normalized target.
2959
3240
  *
2960
- * ONE truth, two views: the target is projected off the SAME fork action
2961
- * model the scripting plane reads (`base.actions`) — root classification
2962
- * via the full `EPDF_ACTION_TYPE_*` vocabulary, payloads via the on-demand
2963
- * node getters. The classic `FPDFAction_*` surface (5 types, JavaScript-
2964
- * and Named-blind) is deliberately not consulted; `FPDFLink_GetDest`
2965
- * remains only for the data-only direct `/Dest`, which is not an action.
3241
+ * ONE truth, one projection: the target is a pure function of the SAME
3242
+ * payload-carrying action tree the scripting plane reads
3243
+ * (`base.actions.activate`) — no second native read exists any more, so the
3244
+ * two action-shaped surfaces cannot drift by construction.
2966
3245
  *
2967
- * `/A` wins over `/Dest` — the spec forbids carrying both, and when a
2968
- * malformed file has both, Acrobat gives the action precedence. (Our own
2969
- * writes can't produce that state: `EPDFAnnot_SetAction` strips `/Dest`.)
3246
+ * `/A` precedence, pinned: an activate action that exists but cannot be
3247
+ * executed (an `incomplete` tree, a degraded/unreadable root) projects
3248
+ * `unsupported` — a broken action is a dead link, never an invitation to
3249
+ * guess. The direct `/Dest` (which is data, not an action) is consulted
3250
+ * ONLY when no `/A` exists at all.
2970
3251
  */
2971
3252
  function readLink(fn, mem, annotPtr, base, _rawSubtypeCode, ctx) {
2972
3253
  return {
2973
3254
  ...base,
2974
3255
  subtype: "link",
2975
- target: readLinkTarget(fn, mem, annotPtr, ctx)
3256
+ target: readLinkTarget(fn, mem, annotPtr, base, ctx)
2976
3257
  };
2977
3258
  }
2978
- function readLinkTarget(fn, mem, annotPtr, ctx) {
2979
- const modelPtr = fn.EPDFAnnot_GetActionModel(annotPtr, 0);
2980
- if (modelPtr !== NULL_PTR) try {
2981
- const root = fn.EPDFAction_GetRootNode(modelPtr);
2982
- if (isValidActionNodeId(root)) return readRootTarget(fn, mem, modelPtr, root, ctx);
2983
- } finally {
2984
- fn.EPDFAction_CloseModel(modelPtr);
3259
+ /**
3260
+ * Project the navigation view from a payload-carrying activate tree. Pure —
3261
+ * shared with any consumer that wants the root-level navigation reading of a
3262
+ * tree (the link plugin's no-actions-plugin fallback uses the same law).
3263
+ */
3264
+ function linkTargetFromActionTree(tree) {
3265
+ if (tree.incomplete) return { kind: "unsupported" };
3266
+ const root = tree.root;
3267
+ if (!root) return { kind: "unsupported" };
3268
+ switch (root.type) {
3269
+ case "goto": return {
3270
+ kind: "goto",
3271
+ destination: root.destination
3272
+ };
3273
+ case "uri": return {
3274
+ kind: "uri",
3275
+ uri: root.uri
3276
+ };
3277
+ case "goto-remote": return {
3278
+ kind: "goto-remote",
3279
+ file: root.filePath
3280
+ };
3281
+ case "launch": return {
3282
+ kind: "launch",
3283
+ path: root.filePath
3284
+ };
3285
+ case "javascript": return { kind: "javascript" };
3286
+ case "named": return {
3287
+ kind: "named",
3288
+ name: root.name
3289
+ };
3290
+ default: return { kind: "unsupported" };
2985
3291
  }
3292
+ }
3293
+ function readLinkTarget(fn, mem, annotPtr, base, ctx) {
3294
+ const activate = base.actions?.activate;
3295
+ if (activate) return linkTargetFromActionTree(activate);
2986
3296
  const linkPtr = fn.FPDFAnnot_GetLink(annotPtr);
2987
3297
  if (!linkPtr) return null;
2988
3298
  const destPtr = fn.FPDFLink_GetDest(ctx.docPtr, linkPtr);
@@ -2993,43 +3303,6 @@ function readLinkTarget(fn, mem, annotPtr, ctx) {
2993
3303
  destination
2994
3304
  } : { kind: "unsupported" };
2995
3305
  }
2996
- /** Classify the root action node and fetch its payload — the projection. */
2997
- function readRootTarget(fn, mem, modelPtr, node, ctx) {
2998
- switch (actionTypeFromCode(fn.EPDFAction_GetNodeType(modelPtr, node))) {
2999
- case "goto": {
3000
- const destPtr = fn.EPDFAction_GetNodeDest(ctx.docPtr, modelPtr, node);
3001
- const destination = destPtr ? readDestination(fn, mem, ctx.docPtr, destPtr) : null;
3002
- return destination ? {
3003
- kind: "goto",
3004
- destination
3005
- } : { kind: "unsupported" };
3006
- }
3007
- case "uri": {
3008
- const uri = readUtf8String(mem, (buf, capacity) => fn.EPDFAction_GetNodeURI(ctx.docPtr, modelPtr, node, buf, capacity));
3009
- return uri == null ? { kind: "unsupported" } : {
3010
- kind: "uri",
3011
- uri
3012
- };
3013
- }
3014
- case "goto-remote": return {
3015
- kind: "goto-remote",
3016
- file: readUtf8String(mem, (buf, capacity) => fn.EPDFAction_GetNodeFilePath(modelPtr, node, buf, capacity)) ?? ""
3017
- };
3018
- case "launch": return {
3019
- kind: "launch",
3020
- path: readUtf8String(mem, (buf, capacity) => fn.EPDFAction_GetNodeFilePath(modelPtr, node, buf, capacity)) ?? ""
3021
- };
3022
- case "javascript": return { kind: "javascript" };
3023
- case "named": {
3024
- const name = readUtf8String(mem, (buf, capacity) => fn.EPDFAction_GetNodeName(modelPtr, node, buf, capacity));
3025
- return name == null ? { kind: "unsupported" } : {
3026
- kind: "named",
3027
- name
3028
- };
3029
- }
3030
- default: return { kind: "unsupported" };
3031
- }
3032
- }
3033
3306
  //#endregion
3034
3307
  //#region src/features/annotations/internal/read/readRedactAnnotation.ts
3035
3308
  /** Default `/C` marking outline (red) — the redaction marking convention and
@@ -3110,45 +3383,10 @@ function readSquare(fn, mem, annotPtr, base) {
3110
3383
  };
3111
3384
  }
3112
3385
  //#endregion
3113
- //#region src/features/annotations/internal/stampName.ts
3114
- /**
3115
- * Stamp `/Name` label ↔ `FPDF_ANNOT_NAME` enum code mapping for the
3116
- * `EPDFAnnot_SetName` / `EPDFAnnot_GetName` fork helpers. Labels are the
3117
- * PDF spec / Adobe literal name values (ISO 32000 §12.5.6.12 table 181
3118
- * plus the Adobe SB/SH extended set the fork supports). Codes mirror the
3119
- * `FPDF_ANNOT_NAME` enum in `public/fpdf_annot.h` — same pattern as
3120
- * `lineEnding.ts` keeping engine-core PDFium-free.
3121
- */
3122
- const STAMP_NAME_TO_CODE = Object.freeze({
3123
- Approved: 13,
3124
- Experimental: 14,
3125
- NotApproved: 15,
3126
- AsIs: 16,
3127
- Expired: 17,
3128
- NotForPublicRelease: 18,
3129
- Confidential: 19,
3130
- Final: 20,
3131
- Sold: 21,
3132
- Departmental: 22,
3133
- ForComment: 23,
3134
- TopSecret: 24,
3135
- Draft: 25,
3136
- ForPublicRelease: 26,
3137
- Completed: 27,
3138
- Void: 28,
3139
- PreliminaryResults: 29,
3140
- InformationOnly: 30,
3141
- Rejected: 31,
3142
- Witness: 32,
3143
- InitialHere: 33,
3144
- SignHere: 34,
3145
- Accepted: 35
3146
- });
3147
- const STAMP_CODE_TO_NAME = Object.freeze(Object.fromEntries(Object.entries(STAMP_NAME_TO_CODE).map(([name, code]) => [code, name])));
3148
- //#endregion
3149
3386
  //#region src/features/annotations/internal/read/readStampAnnotation.ts
3150
3387
  /**
3151
- * Stamp DTO: base + `/Name` label + transform metadata. The visual content
3388
+ * Stamp DTO: base + `/Name` (standard or custom identifier, verbatim) +
3389
+ * transform metadata. The visual content
3152
3390
  * stays in the `/AP` stream — rendered via `renderAppearanceImages()`,
3153
3391
  * never surfaced as DTO data.
3154
3392
  */
@@ -3158,16 +3396,11 @@ function readStamp(fn, mem, annotPtr, base) {
3158
3396
  return {
3159
3397
  ...base,
3160
3398
  subtype: "stamp",
3161
- name: readStampName(fn, mem, annotPtr),
3399
+ name: readAnnotName(fn, mem, annotPtr),
3162
3400
  ...rotation != null ? { rotation } : {},
3163
3401
  ...unrotatedRect != null ? { unrotatedRect } : {}
3164
3402
  };
3165
3403
  }
3166
- function readStampName(fn, mem, annotPtr) {
3167
- const known = STAMP_CODE_TO_NAME[fn.EPDFAnnot_GetName(annotPtr)];
3168
- if (known !== void 0) return known;
3169
- return readAnnotString(fn, mem, annotPtr, "Name");
3170
- }
3171
3404
  //#endregion
3172
3405
  //#region src/features/annotations/internal/annotationState.ts
3173
3406
  /**
@@ -3223,7 +3456,7 @@ function readText(fn, mem, annotPtr, base) {
3223
3456
  const color = readAnnotColor(fn, mem, annotPtr) ?? { ...DEFAULT_NOTE_COLOR$1 };
3224
3457
  const ca = readAnnotOpacity(fn, mem, annotPtr);
3225
3458
  const opacity = ca == null ? 1 : Math.max(0, Math.min(1, ca));
3226
- const icon = NOTE_CODE_TO_ICON[fn.EPDFAnnot_GetName(annotPtr)] ?? DEFAULT_NOTE_ICON;
3459
+ const icon = NOTE_NAME_TO_ICON[readAnnotName(fn, mem, annotPtr) ?? ""] ?? DEFAULT_NOTE_ICON;
3227
3460
  const stateRaw = readAnnotString(fn, mem, annotPtr, "State");
3228
3461
  const stateModelRaw = readAnnotString(fn, mem, annotPtr, "StateModel");
3229
3462
  return {
@@ -3539,7 +3772,7 @@ function readOptions(runtime, model, fieldIndex) {
3539
3772
  return options;
3540
3773
  }
3541
3774
  /** Read one field of the native model into its per-family DTO. */
3542
- function readFieldAt(runtime, model, fieldIndex, actionBudget = new ActionReadBudgetTracker()) {
3775
+ function readFieldAt(runtime, model, fieldIndex, docPtr, actionBudget = new ActionReadBudgetTracker()) {
3543
3776
  const { fn } = runtime;
3544
3777
  const fieldObjectNumber = fn.EPDFForm_GetFieldObjNum(model, fieldIndex);
3545
3778
  const name = readWide(runtime, (buf, cap) => fn.EPDFForm_GetFieldName(model, fieldIndex, buf, cap));
@@ -3547,7 +3780,7 @@ function readFieldAt(runtime, model, fieldIndex, actionBudget = new ActionReadBu
3547
3780
  const family = FAMILY_BY_CODE[fn.EPDFForm_GetFieldFamily(model, fieldIndex)] ?? "unknown";
3548
3781
  const valueEntry = readValueEntry(runtime, model, fieldIndex, false);
3549
3782
  const defaultValueEntry = readValueEntry(runtime, model, fieldIndex, true);
3550
- const actions = readFieldActions(runtime, model, fieldIndex, actionBudget);
3783
+ const actions = readFieldActions(runtime, model, fieldIndex, docPtr, actionBudget);
3551
3784
  const base = {
3552
3785
  ref: fieldObjectNumber > 0 ? {
3553
3786
  kind: "objectNumber",
@@ -3646,12 +3879,12 @@ function readFieldAt(runtime, model, fieldIndex, actionBudget = new ActionReadBu
3646
3879
  }
3647
3880
  }
3648
3881
  /** Read the whole native model into a detached {@link FormSnapshot}. */
3649
- function readFormSnapshot(runtime, model) {
3882
+ function readFormSnapshot(runtime, model, docPtr) {
3650
3883
  const { fn } = runtime;
3651
3884
  const count = fn.EPDFForm_CountFields(model);
3652
3885
  const fields = [];
3653
3886
  const actionBudget = new ActionReadBudgetTracker();
3654
- for (let i = 0; i < count; i++) fields.push(readFieldAt(runtime, model, i, actionBudget));
3887
+ for (let i = 0; i < count; i++) fields.push(readFieldAt(runtime, model, i, docPtr, actionBudget));
3655
3888
  const calculationOrder = [];
3656
3889
  const calculationCount = fn.EPDFForm_CountCalculationOrder(model);
3657
3890
  for (let index = 0; index < calculationCount; index++) {
@@ -3687,7 +3920,7 @@ function entryScalar(entry) {
3687
3920
  if (entry.kind === "array") return entry.values[0] ?? "";
3688
3921
  return "";
3689
3922
  }
3690
- function readFieldActions(runtime, model, fieldIndex, budget) {
3923
+ function readFieldActions(runtime, model, fieldIndex, docPtr, budget) {
3691
3924
  const { fn, mem } = runtime;
3692
3925
  const entries = [
3693
3926
  ["keystroke", 0],
@@ -3697,7 +3930,7 @@ function readFieldActions(runtime, model, fieldIndex, budget) {
3697
3930
  ];
3698
3931
  const actions = {};
3699
3932
  for (const [key, event] of entries) {
3700
- const action = readActionModel(fn, mem, fn.EPDFForm_GetFieldActionModel(model, fieldIndex, event), budget);
3933
+ const action = readActionModel(fn, mem, docPtr, fn.EPDFForm_GetFieldActionModel(model, fieldIndex, event), budget);
3701
3934
  if (action) actions[key] = action;
3702
3935
  }
3703
3936
  return Object.keys(actions).length > 0 ? actions : void 0;
@@ -3943,7 +4176,7 @@ function blendModeFromCode(code) {
3943
4176
  * per-subtype reader builds its DTO by extending this with its own
3944
4177
  * fields and `subtype: '...'` discriminator.
3945
4178
  */
3946
- function readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision, actionBudget = new ActionReadBudgetTracker()) {
4179
+ function readAnnotationBase(fn, mem, docPtr, annotPtr, pageObjectNumber, index, revision, actionBudget = new ActionReadBudgetTracker()) {
3947
4180
  const identity = readAnnotationIdentity(fn, mem, annotPtr, pageObjectNumber, index, revision);
3948
4181
  const rect = readAnnotRect(fn, mem, annotPtr);
3949
4182
  const flags = readAnnotFlags(fn, annotPtr);
@@ -3955,7 +4188,7 @@ function readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision
3955
4188
  const blendMode = blendModeFromCode(fn.EPDFAnnot_GetBlendMode(annotPtr));
3956
4189
  const embd = readEmbedMetadata(fn, mem, annotPtr);
3957
4190
  const relationship = readAnnotationRelationship(fn, mem, annotPtr, pageObjectNumber);
3958
- const actions = readAnnotationActions(fn, mem, annotPtr, actionBudget);
4191
+ const actions = readAnnotationActions(fn, mem, docPtr, annotPtr, actionBudget);
3959
4192
  return {
3960
4193
  ref: identity.ref,
3961
4194
  pageObjectNumber,
@@ -3979,7 +4212,7 @@ function readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision
3979
4212
  ...actions ? { actions } : {}
3980
4213
  };
3981
4214
  }
3982
- function readAnnotationActions(fn, mem, annotPtr, budget) {
4215
+ function readAnnotationActions(fn, mem, docPtr, annotPtr, budget) {
3983
4216
  const events = [
3984
4217
  ["activate", 0],
3985
4218
  ["cursorEnter", 1],
@@ -3995,7 +4228,7 @@ function readAnnotationActions(fn, mem, annotPtr, budget) {
3995
4228
  ];
3996
4229
  const actions = {};
3997
4230
  for (const [key, event] of events) {
3998
- const action = readActionModel(fn, mem, fn.EPDFAnnot_GetActionModel(annotPtr, event), budget);
4231
+ const action = readActionModel(fn, mem, docPtr, fn.EPDFAnnot_GetActionModel(annotPtr, event), budget);
3999
4232
  if (action) actions[key] = action;
4000
4233
  }
4001
4234
  return Object.keys(actions).length > 0 ? actions : void 0;
@@ -4026,7 +4259,7 @@ function collectPageAnnotations(input) {
4026
4259
  const annotPtr = getAnnotPtrAt(i);
4027
4260
  if (!annotPtr) continue;
4028
4261
  try {
4029
- const base = readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, i, revision, actionBudget);
4262
+ const base = readAnnotationBase(fn, mem, readCtx.docPtr, annotPtr, pageObjectNumber, i, revision, actionBudget);
4030
4263
  const subtypeCode = fn.FPDFAnnot_GetSubtype(annotPtr);
4031
4264
  const { reader } = pickReader(subtypeCode);
4032
4265
  const dto = reader(fn, mem, annotPtr, base, subtypeCode, readCtx);
@@ -4964,7 +5197,7 @@ function computeMutationImpact(inputs) {
4964
5197
  * `AnnotationUpdateResult.updated`.
4965
5198
  */
4966
5199
  function readAnnotationFromPtr(fn, mem, annotPtr, pageObjectNumber, index, revision, ctx) {
4967
- const base = readAnnotationBase(fn, mem, annotPtr, pageObjectNumber, index, revision);
5200
+ const base = readAnnotationBase(fn, mem, ctx.docPtr, annotPtr, pageObjectNumber, index, revision);
4968
5201
  const subtypeCode = fn.FPDFAnnot_GetSubtype(annotPtr);
4969
5202
  const { reader } = pickReader(subtypeCode);
4970
5203
  return reader(fn, mem, annotPtr, base, subtypeCode, ctx);
@@ -5245,7 +5478,7 @@ function isFileAttachmentSubtype(subtype) {
5245
5478
  return subtype === "file-attachment";
5246
5479
  }
5247
5480
  function setFileAttachmentIcon(fn, annotPtr, icon) {
5248
- if (!fn.EPDFAnnot_SetName(annotPtr, FILE_ICON_TO_CODE[icon])) throw new EngineError(EngineErrorCode.Unknown, "EPDFAnnot_SetName returned false");
5481
+ if (!fn.EPDFAnnot_SetName(annotPtr, FILE_ICON_TO_NAME[icon])) throw new EngineError(EngineErrorCode.Unknown, "EPDFAnnot_SetName returned false");
5249
5482
  }
5250
5483
  function requireFileResource(file, ctx) {
5251
5484
  const resource = ctx?.resources?.[file.resource];
@@ -5907,12 +6140,12 @@ const STAMP_FIT_TO_CODE = {
5907
6140
  * creating an annotation or strengthening a weak annotation id.
5908
6141
  */
5909
6142
  function preflightStampDraft(draft, ctx) {
5910
- if (draft.name !== void 0) requireStampNameCode(draft.name);
6143
+ if (draft.name !== void 0) requireStampName(draft.name);
5911
6144
  requireStampFit(draft.fit ?? "contain");
5912
6145
  requireStampContent(draft.source, ctx);
5913
6146
  }
5914
6147
  function preflightStampPatch(patch, ctx) {
5915
- if (patch.name !== void 0) requireStampNameCode(patch.name);
6148
+ if (patch.name !== void 0 && patch.name !== null) requireStampName(patch.name);
5916
6149
  requireStampFit(patch.fit ?? "contain");
5917
6150
  if (patch.source !== void 0) requireStampContent(patch.source, ctx);
5918
6151
  }
@@ -5938,7 +6171,8 @@ function applyStampDraft(fn, mem, annotPtr, draft, ctx) {
5938
6171
  }
5939
6172
  function applyStampPatch(fn, mem, annotPtr, patch, ctx) {
5940
6173
  applyAnnotationBasePatch(fn, mem, annotPtr, patch);
5941
- if (patch.name !== void 0) setStampName(fn, annotPtr, patch.name);
6174
+ if (patch.name === null) fn.EPDFAnnot_RemoveKey(annotPtr, "Name");
6175
+ else if (patch.name !== void 0) setStampName(fn, annotPtr, patch.name);
5942
6176
  if (patch.source !== void 0) {
5943
6177
  const rect = patch.rect ?? readAnnotRect(fn, mem, annotPtr);
5944
6178
  authorStampAppearance(fn, mem, annotPtr, patch.source, patch.fit ?? "contain", {
@@ -5958,15 +6192,14 @@ function applyStampPatch(fn, mem, annotPtr, patch, ctx) {
5958
6192
  function isStampSubtype(subtype) {
5959
6193
  return subtype === "stamp";
5960
6194
  }
6195
+ /** Any non-empty name: standard ('Approved') or custom ('#LBGiYhk8V…') —
6196
+ * the fork writes a name object and escapes it; predefined sets are a
6197
+ * reader concern (ISO 32000-2 table 187 allows additional names). */
5961
6198
  function setStampName(fn, annotPtr, name) {
5962
- const code = STAMP_NAME_TO_CODE[name];
5963
- if (code === void 0) throw new EngineError(EngineErrorCode.Unknown, "stamp name preflight invariant broken");
5964
- if (!fn.EPDFAnnot_SetName(annotPtr, code)) throw new EngineError(EngineErrorCode.Unknown, "EPDFAnnot_SetName returned false");
6199
+ if (!fn.EPDFAnnot_SetName(annotPtr, name)) throw new EngineError(EngineErrorCode.Unknown, "EPDFAnnot_SetName returned false");
5965
6200
  }
5966
- function requireStampNameCode(name) {
5967
- const code = STAMP_NAME_TO_CODE[name];
5968
- if (code === void 0) throw new EngineError(EngineErrorCode.InvalidArg, `unknown stamp name '${name}'; supported names: ${Object.keys(STAMP_NAME_TO_CODE).join(", ")}`);
5969
- return code;
6201
+ function requireStampName(name) {
6202
+ if (name.length === 0) throw new EngineError(EngineErrorCode.InvalidArg, "stamp name must be non-empty");
5970
6203
  }
5971
6204
  function requireStampFit(fit) {
5972
6205
  if (STAMP_FIT_TO_CODE[fit] === void 0) throw new EngineError(EngineErrorCode.InvalidArg, `unknown stamp fit '${String(fit)}'; expected contain, cover, or fill`);
@@ -6154,7 +6387,7 @@ function isTextSubtype(subtype) {
6154
6387
  return subtype === "text";
6155
6388
  }
6156
6389
  function setNoteIcon(fn, annotPtr, icon) {
6157
- if (!fn.EPDFAnnot_SetName(annotPtr, NOTE_ICON_TO_CODE[icon])) throw new EngineError(EngineErrorCode.Unknown, "EPDFAnnot_SetName returned false");
6390
+ if (!fn.EPDFAnnot_SetName(annotPtr, NOTE_ICON_TO_NAME[icon])) throw new EngineError(EngineErrorCode.Unknown, "EPDFAnnot_SetName returned false");
6158
6391
  }
6159
6392
  //#endregion
6160
6393
  //#region src/features/annotations/internal/write/writeVertexAnnotation.ts
@@ -6881,6 +7114,141 @@ var AnnotationMutator = class {
6881
7114
  }
6882
7115
  };
6883
7116
  //#endregion
7117
+ //#region src/features/annotations/AnnotationFlattener.ts
7118
+ const FLATTEN_FAIL$1 = 0;
7119
+ const FLATTEN_SUCCESS$1 = 1;
7120
+ const FLATTEN_NOTHING_TO_DO$1 = 2;
7121
+ const STATUS_APPLIED = 0;
7122
+ const STATUS_NOT_ON_PAGE = 2;
7123
+ const FPDF_NO_INCREMENTAL$1 = 2;
7124
+ /**
7125
+ * The annotation-plane flatten verbs — `pages.flatten` for a chosen set:
7126
+ * in place (`flatten`) or into a new single-page document
7127
+ * (`exportAppearance`). Both resolve refs to native handles on the page,
7128
+ * hand the SET to the fork (one candidate plan, one placement writer — the
7129
+ * same code whole-page flatten runs), and release every handle afterwards.
7130
+ */
7131
+ var AnnotationFlattener = class {
7132
+ constructor(runtime, session) {
7133
+ this.runtime = runtime;
7134
+ this.session = session;
7135
+ }
7136
+ flatten(pageObjectNumber, refs, usage, signal) {
7137
+ throwIfAborted(signal);
7138
+ this.requireRefs("annotations.flatten", pageObjectNumber, refs);
7139
+ const { fn, mem } = this.runtime;
7140
+ const pool = this.session.pagePool();
7141
+ const pagePtr = pool.acquire(pageObjectNumber);
7142
+ try {
7143
+ const annotPtrs = this.resolveAll(pagePtr, refs);
7144
+ let code;
7145
+ let statuses;
7146
+ try {
7147
+ [code, statuses] = this.withHandleArray(annotPtrs, (arrayPtr) => withScratch(mem, 4 * refs.length, (statusPtr) => {
7148
+ const rc = fn.EPDFPage_FlattenAnnotations(pagePtr, arrayPtr, refs.length, usage === "print" ? 1 : 0, statusPtr);
7149
+ const out = [];
7150
+ for (let i = 0; i < refs.length; i++) out.push(Number(mem.peek(statusPtr, "i32", 4 * i)));
7151
+ return [rc, out];
7152
+ }));
7153
+ } finally {
7154
+ for (const annotPtr of annotPtrs) fn.FPDFPage_CloseAnnot(annotPtr);
7155
+ }
7156
+ if (code === FLATTEN_FAIL$1) {
7157
+ const foreign = statuses.findIndex((status) => status === STATUS_NOT_ON_PAGE);
7158
+ throw new EngineError(foreign >= 0 ? EngineErrorCode.InvalidArg : EngineErrorCode.Unknown, foreign >= 0 ? `annotations.flatten: ref ${foreign} is not an annotation of page ${pageObjectNumber}` : "native annotation flatten failed after preflight");
7159
+ }
7160
+ const results = refs.map((ref, i) => ({
7161
+ ref,
7162
+ status: statuses[i] === STATUS_APPLIED ? "applied" : "skipped"
7163
+ }));
7164
+ if (code === FLATTEN_NOTHING_TO_DO$1 || code !== FLATTEN_SUCCESS$1) return {
7165
+ pageObjectNumber,
7166
+ usage,
7167
+ results,
7168
+ meta: null
7169
+ };
7170
+ this.session.noteMutation();
7171
+ this.session.bumpRevision(pageObjectNumber);
7172
+ try {
7173
+ new AnnotationReader(this.runtime, this.session).list(pageObjectNumber, signal);
7174
+ } catch {}
7175
+ return {
7176
+ pageObjectNumber,
7177
+ usage,
7178
+ results,
7179
+ meta: {
7180
+ affectedPages: [this.session.pageState(pageObjectNumber)],
7181
+ cacheDelta: null
7182
+ }
7183
+ };
7184
+ } finally {
7185
+ pool.release(pageObjectNumber);
7186
+ }
7187
+ }
7188
+ exportAppearance(pageObjectNumber, refs, signal) {
7189
+ throwIfAborted(signal);
7190
+ this.requireRefs("annotations.exportAppearance", pageObjectNumber, refs);
7191
+ const { fn, mem } = this.runtime;
7192
+ const pool = this.session.pagePool();
7193
+ const pagePtr = pool.acquire(pageObjectNumber);
7194
+ let exportedPtr = null;
7195
+ let pdfPtr = null;
7196
+ try {
7197
+ const annotPtrs = this.resolveAll(pagePtr, refs);
7198
+ try {
7199
+ exportedPtr = this.withHandleArray(annotPtrs, (arrayPtr) => fn.EPDFPage_ExportAnnotationsAsDocument(pagePtr, arrayPtr, refs.length));
7200
+ } finally {
7201
+ for (const annotPtr of annotPtrs) fn.FPDFPage_CloseAnnot(annotPtr);
7202
+ }
7203
+ if (!exportedPtr) throw new EngineError(EngineErrorCode.InvalidArg, "annotations.exportAppearance: every ref must be a visible annotation of this page with a normal appearance");
7204
+ return withScratch(mem, 4, (sizePtr) => {
7205
+ mem.poke(sizePtr, "i32", 0);
7206
+ pdfPtr = fn.EPDF_SaveDocumentToOwnedBuffer(exportedPtr, FPDF_NO_INCREMENTAL$1, sizePtr);
7207
+ const size = Number(mem.peek(sizePtr, "i32"));
7208
+ if (!pdfPtr || size <= 0) throw new EngineError(EngineErrorCode.DocOpenFailed, "failed to save exported appearances");
7209
+ const bytes = mem.readBytes(pdfPtr, size);
7210
+ const buffer = new ArrayBuffer(bytes.byteLength);
7211
+ new Uint8Array(buffer).set(bytes);
7212
+ return {
7213
+ bytes: buffer,
7214
+ size
7215
+ };
7216
+ });
7217
+ } finally {
7218
+ if (pdfPtr) fn.EPDF_FreeBuffer(pdfPtr);
7219
+ if (exportedPtr) fn.FPDF_CloseDocument(exportedPtr);
7220
+ pool.release(pageObjectNumber);
7221
+ }
7222
+ }
7223
+ /** Resolve every ref on `pagePtr` (NotFound / InvalidReference from the
7224
+ * resolver on caller error); on any failure, close what was opened. */
7225
+ resolveAll(pagePtr, refs) {
7226
+ const { fn } = this.runtime;
7227
+ const annotPtrs = [];
7228
+ try {
7229
+ for (const ref of refs) annotPtrs.push(resolveAnnotPtr(this.runtime, this.session, pagePtr, ref));
7230
+ return annotPtrs;
7231
+ } catch (error) {
7232
+ for (const annotPtr of annotPtrs) fn.FPDFPage_CloseAnnot(annotPtr);
7233
+ throw error;
7234
+ }
7235
+ }
7236
+ /** Marshal an `FPDF_ANNOTATION*` array for the call's duration. */
7237
+ withHandleArray(annotPtrs, body) {
7238
+ const { mem } = this.runtime;
7239
+ const isWasm = this.runtime.kind === "wasm";
7240
+ const ptrSize = isWasm ? 4 : 8;
7241
+ return withScratch(mem, Math.max(1, annotPtrs.length * ptrSize), (arrayPtr) => {
7242
+ annotPtrs.forEach((ptr, i) => isWasm ? mem.poke(arrayPtr, "i32", Number(ptr), i * 4) : mem.poke(arrayPtr, "i64", ptr, i * 8));
7243
+ return body(arrayPtr);
7244
+ });
7245
+ }
7246
+ requireRefs(op, pageObjectNumber, refs) {
7247
+ if (refs.length === 0) throw new EngineError(EngineErrorCode.InvalidArg, `${op} requires at least one ref`);
7248
+ for (const ref of refs) if (ref.pageObjectNumber !== pageObjectNumber) throw new EngineError(EngineErrorCode.InvalidArg, `${op} refs must all target page ${pageObjectNumber}; got ref on page ${ref.pageObjectNumber}`);
7249
+ }
7250
+ };
7251
+ //#endregion
6884
7252
  //#region src/features/pages/PagesFlattener.ts
6885
7253
  const FLATTEN_FAIL = 0;
6886
7254
  const FLATTEN_SUCCESS = 1;
@@ -8182,13 +8550,13 @@ var FormReader = class {
8182
8550
  snapshot(signal) {
8183
8551
  throwIfAborted(signal);
8184
8552
  const model = acquireFormModel(this.runtime, this.session);
8185
- return readFormSnapshot(this.runtime, model);
8553
+ return readFormSnapshot(this.runtime, model, this.session.requireDocPtr());
8186
8554
  }
8187
8555
  field(ref, signal) {
8188
8556
  throwIfAborted(signal);
8189
8557
  const model = acquireFormModel(this.runtime, this.session);
8190
8558
  const { fieldIndex } = resolveFieldRef(this.runtime, model, ref);
8191
- return readFieldAt(this.runtime, model, fieldIndex);
8559
+ return readFieldAt(this.runtime, model, fieldIndex, this.session.requireDocPtr());
8192
8560
  }
8193
8561
  /**
8194
8562
  * Serialize form data. Exports read the reconciled view (recovered
@@ -8344,7 +8712,7 @@ var FormMutator = class {
8344
8712
  const model = acquireFormModel(this.runtime, this.session);
8345
8713
  const resolved = resolveFieldRef(this.runtime, model, ref);
8346
8714
  this.assertWritable(resolved.fieldObjectNumber);
8347
- const before = readFieldAt(this.runtime, model, resolved.fieldIndex);
8715
+ const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
8348
8716
  if (!FAMILY_BY_VALUE_TYPE[value.type].includes(before.family)) throw new EngineError(EngineErrorCode.InvalidArg, `value type '${value.type}' does not apply to a '${before.family}' field`);
8349
8717
  const changed = this.applyWrite(resolved.fieldObjectNumber, value);
8350
8718
  return this.readBack(resolved.fieldObjectNumber, changed);
@@ -8380,7 +8748,7 @@ var FormMutator = class {
8380
8748
  const fresh = acquireFormModel(this.runtime, this.session);
8381
8749
  return {
8382
8750
  ...counters,
8383
- snapshot: readFormSnapshot(this.runtime, fresh),
8751
+ snapshot: readFormSnapshot(this.runtime, fresh, this.session.requireDocPtr()),
8384
8752
  meta: EMPTY_META$1
8385
8753
  };
8386
8754
  }
@@ -8453,7 +8821,7 @@ var FormMutator = class {
8453
8821
  const model = acquireFormModel(this.runtime, this.session);
8454
8822
  const resolved = resolveFieldRef(this.runtime, model, ref);
8455
8823
  this.assertWritable(resolved.fieldObjectNumber);
8456
- const before = readFieldAt(this.runtime, model, resolved.fieldIndex);
8824
+ const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
8457
8825
  if (before.family !== patch.family) throw new EngineError(EngineErrorCode.InvalidArg, `patch family '${patch.family}' does not match field family '${before.family}'`);
8458
8826
  const fieldObjectNumber = resolved.fieldObjectNumber;
8459
8827
  if (patch.name !== void 0) this.applyWideSetter(fn.EPDFForm_SetFieldName, fieldObjectNumber, patch.name, `cannot rename to "${patch.name}" (sibling conflict or invalid)`);
@@ -8484,7 +8852,7 @@ var FormMutator = class {
8484
8852
  const model = acquireFormModel(this.runtime, this.session);
8485
8853
  const resolved = resolveFieldRef(this.runtime, model, ref);
8486
8854
  this.assertWritable(resolved.fieldObjectNumber);
8487
- const before = readFieldAt(this.runtime, model, resolved.fieldIndex);
8855
+ const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
8488
8856
  if (!withScratchN(mem, [256 * 4, 4], ([buf, countPtr]) => {
8489
8857
  mem.poke(countPtr, "i32", 0);
8490
8858
  return fn.EPDFForm_DeleteField(this.session.requireDocPtr(), resolved.fieldObjectNumber, buf, 256, countPtr);
@@ -8504,7 +8872,7 @@ var FormMutator = class {
8504
8872
  const model = acquireFormModel(this.runtime, this.session);
8505
8873
  const resolved = resolveFieldRef(this.runtime, model, ref);
8506
8874
  this.assertWritable(resolved.fieldObjectNumber);
8507
- const before = readFieldAt(this.runtime, model, resolved.fieldIndex);
8875
+ const before = readFieldAt(this.runtime, model, resolved.fieldIndex, this.session.requireDocPtr());
8508
8876
  const toggle = before.family === "checkbox" || before.family === "radio";
8509
8877
  const state = toggle ? onState ?? (before.family === "checkbox" ? "Yes" : "") : "";
8510
8878
  if (toggle && (!state || state === "Off")) throw new EngineError(EngineErrorCode.InvalidArg, "attaching to a radio group needs a non-\"Off\" onState");
@@ -8550,7 +8918,7 @@ var FormMutator = class {
8550
8918
  const fresh = acquireFormModel(this.runtime, this.session);
8551
8919
  const fieldIndex = this.runtime.fn.EPDFForm_GetFieldIndexByObjNum(fresh, fieldObjectNumber);
8552
8920
  if (fieldIndex < 0) throw new EngineError(EngineErrorCode.Unknown, "form field vanished after write");
8553
- return readFieldAt(this.runtime, fresh, fieldIndex);
8921
+ return readFieldAt(this.runtime, fresh, fieldIndex, this.session.requireDocPtr());
8554
8922
  }
8555
8923
  assertWritable(fieldObjectNumber) {
8556
8924
  if (fieldObjectNumber === 0) throw new EngineError(EngineErrorCode.InvalidArg, "form field is stored as a direct object and cannot be written");
@@ -8596,7 +8964,7 @@ var FormMutator = class {
8596
8964
  const fresh = acquireFormModel(this.runtime, this.session);
8597
8965
  const fieldIndex = this.runtime.fn.EPDFForm_GetFieldIndexByObjNum(fresh, fieldObjectNumber);
8598
8966
  if (fieldIndex < 0) throw new EngineError(EngineErrorCode.Unknown, "form field vanished after write");
8599
- const field = readFieldAt(this.runtime, fresh, fieldIndex);
8967
+ const field = readFieldAt(this.runtime, fresh, fieldIndex, this.session.requireDocPtr());
8600
8968
  const changedSet = new Set(changedObjNums);
8601
8969
  return {
8602
8970
  field,
@@ -8742,7 +9110,7 @@ var FormsEffectsApplier = class {
8742
9110
  if (refs.length === 0) throw new EngineError(EngineErrorCode.InvalidArg, "reset effect requires at least one field");
8743
9111
  const model = acquireFormModel(this.runtime, this.session);
8744
9112
  const resolved = refs.map((ref) => this.resolve(model, ref));
8745
- const fields = resolved.map(({ fieldIndex }) => readFieldAt(this.runtime, model, fieldIndex, actionBudget));
9113
+ const fields = resolved.map(({ fieldIndex }) => readFieldAt(this.runtime, model, fieldIndex, this.session.requireDocPtr(), actionBudget));
8746
9114
  validateEffect(effect, fields);
8747
9115
  return {
8748
9116
  effect,
@@ -8767,7 +9135,7 @@ var FormsEffectsApplier = class {
8767
9135
  const model = acquireFormModel(this.runtime, this.session);
8768
9136
  const index = this.runtime.fn.EPDFForm_GetFieldIndexByObjNum(model, fieldObjectNumber);
8769
9137
  if (index < 0) throw new EngineError(EngineErrorCode.InvalidReference, "form field disappeared");
8770
- return readFieldAt(this.runtime, model, index, actionBudget);
9138
+ return readFieldAt(this.runtime, model, index, this.session.requireDocPtr(), actionBudget);
8771
9139
  }
8772
9140
  readFieldsBestEffort(fieldObjectNumbers, actionBudget) {
8773
9141
  const fields = [];
@@ -9134,22 +9502,26 @@ var DocumentActionsReader = class {
9134
9502
  } finally {
9135
9503
  fn.FPDFDoc_CloseJavaScriptAction(namedPtr);
9136
9504
  }
9137
- const action = requireAction(readActionModel(fn, mem, fn.EPDFDoc_GetNamedJavaScriptActionModel(docPtr, index), budget), "name-tree script", index);
9505
+ budget.reservePayloadBytes(name.length);
9506
+ const action = requireAction(readActionModel(fn, mem, docPtr, fn.EPDFDoc_GetNamedJavaScriptActionModel(docPtr, index), budget), "name-tree script", index);
9138
9507
  nameTreeScripts.push({
9139
9508
  name,
9140
9509
  action
9141
9510
  });
9142
9511
  }
9143
- const openAction = readActionModel(fn, mem, fn.EPDFDoc_GetOpenActionModel(docPtr), budget);
9512
+ const openAction = readActionModel(fn, mem, docPtr, fn.EPDFDoc_GetOpenActionModel(docPtr), budget);
9513
+ const openDestPtr = openAction ? NULL_PTR : fn.EPDFDoc_GetOpenActionDest(docPtr);
9514
+ const openDestination = openDestPtr !== NULL_PTR ? readDestination(fn, mem, docPtr, openDestPtr) : null;
9144
9515
  const additional = {};
9145
9516
  for (const [key, event] of DOCUMENT_ACTION_EVENTS) {
9146
9517
  throwIfAborted(signal);
9147
- const action = readActionModel(fn, mem, fn.EPDFDoc_GetAdditionalActionModel(docPtr, event), budget);
9518
+ const action = readActionModel(fn, mem, docPtr, fn.EPDFDoc_GetAdditionalActionModel(docPtr, event), budget);
9148
9519
  if (action) additional[key] = action;
9149
9520
  }
9150
9521
  return {
9151
9522
  nameTreeScripts,
9152
9523
  openAction,
9524
+ openDestination,
9153
9525
  ...additional
9154
9526
  };
9155
9527
  }
@@ -9483,6 +9855,18 @@ var WorkerHost = class {
9483
9855
  case "pages.delete":
9484
9856
  resultPack = this.handlePagesDelete(msg, ctrl.signal);
9485
9857
  break;
9858
+ case "pages.setName":
9859
+ resultPack = this.handlePagesSetName(msg, ctrl.signal);
9860
+ break;
9861
+ case "annotations.flatten":
9862
+ resultPack = this.handleAnnotationsFlatten(msg, ctrl.signal);
9863
+ break;
9864
+ case "annotations.exportAppearance":
9865
+ resultPack = this.handleAnnotationsExportAppearance(msg, ctrl.signal);
9866
+ break;
9867
+ case "pages.removeName":
9868
+ resultPack = this.handlePagesRemoveName(msg, ctrl.signal);
9869
+ break;
9486
9870
  case "pages.flatten":
9487
9871
  resultPack = this.handlePagesFlatten(msg, ctrl.signal);
9488
9872
  break;
@@ -9710,6 +10094,27 @@ var WorkerHost = class {
9710
10094
  result
9711
10095
  }, req.artifactPath);
9712
10096
  }
10097
+ handleAnnotationsFlatten(req, signal) {
10098
+ const session = this.requireSession(req);
10099
+ const result = new AnnotationFlattener(this.runtime, session).flatten(req.pageObjectNumber, req.refs, req.usage, signal);
10100
+ if (result.meta === null) return wirePack({
10101
+ tag: "annotations.flatten",
10102
+ result
10103
+ });
10104
+ return this.finishMutation(session, {
10105
+ tag: "annotations.flatten",
10106
+ result
10107
+ }, req.artifactPath);
10108
+ }
10109
+ handleAnnotationsExportAppearance(req, signal) {
10110
+ const session = this.requireSession(req);
10111
+ const exported = new AnnotationFlattener(this.runtime, session).exportAppearance(req.pageObjectNumber, req.refs, signal);
10112
+ return wirePack({
10113
+ tag: "annotations.exportAppearance",
10114
+ bytes: exported.bytes,
10115
+ size: exported.size
10116
+ }, [exported.bytes]);
10117
+ }
9713
10118
  handleAnnotationsMove(req, signal) {
9714
10119
  const session = this.requireSession(req);
9715
10120
  const result = new AnnotationMutator(this.runtime, session).move(req.pageObjectNumber, req.refs, req.toIndex, signal);
@@ -9749,6 +10154,26 @@ var WorkerHost = class {
9749
10154
  result
9750
10155
  }, req.artifactPath);
9751
10156
  }
10157
+ handlePagesSetName(req, signal) {
10158
+ const session = this.requireSession(req);
10159
+ const result = new PagesMutator(this.runtime, session).setName({
10160
+ name: req.name,
10161
+ pageObjectNumber: req.pageObjectNumber,
10162
+ ...req.replace !== void 0 ? { replace: req.replace } : {}
10163
+ }, signal);
10164
+ return this.finishMutation(session, {
10165
+ tag: "pages.setName",
10166
+ result
10167
+ }, req.artifactPath);
10168
+ }
10169
+ handlePagesRemoveName(req, signal) {
10170
+ const session = this.requireSession(req);
10171
+ const result = new PagesMutator(this.runtime, session).removeName({ name: req.name }, signal);
10172
+ return this.finishMutation(session, {
10173
+ tag: "pages.removeName",
10174
+ result
10175
+ }, req.artifactPath);
10176
+ }
9752
10177
  handlePagesFlatten(req, signal) {
9753
10178
  const session = this.requireSession(req);
9754
10179
  const result = new PagesFlattener(this.runtime, session).flatten(req.pageObjectNumbers, req.usage, signal);
@@ -10323,6 +10748,6 @@ function passwordRequiredProbe() {
10323
10748
  };
10324
10749
  }
10325
10750
  //#endregion
10326
- export { ALL_STANDARD_SECURITY_PERMISSIONS, ActionReadBudgetTracker, AnnotationAppearanceReader, AnnotationMutator, AnnotationReader, AttachmentMutator, AttachmentReader, BaseDocumentRegistry, CloseStack, DocumentActionsReader, PagesMutator as DocumentPagesMutator, PagesMutator, DocumentSaver, DocumentSession, EPDF_CHAR_GEOMETRY_LAYOUT, EventHub, FontRegistrar, FormMutator, FormReader, FormsEffectsApplier, LocalRevisionAuthority, MetadataMutator, MetadataReader, PageGeometryReader, PagesReader as PageLayoutReader, PagesReader, PagePtrPool, PageRenderReader as PageRasterReader, PageRenderReader, PageTextReader, PagesExtractor, PagesFlattener, PagesInserter, RawAnnotationReader, RedactionApplier, SearchReader, SecurityReader, SessionEventPublisher, WorkerHost, buildRunsFromRawGlyphs, computeMutationImpact, destroyLibrary, disposeFormModel, ensureInitialized, formatPdfDate, generateUuid, hasAllStandardSecurityPermissions, normalizeU32, openFatMemoryDocument, openLayerDocument, pdfDateToIso, readActionModel, readAttachmentFileInfo, throwIfAborted, writeAttachmentFilePayload };
10751
+ export { ALL_STANDARD_SECURITY_PERMISSIONS, ActionReadBudgetTracker, AnnotationAppearanceReader, AnnotationFlattener, AnnotationMutator, AnnotationReader, AttachmentMutator, AttachmentReader, BaseDocumentRegistry, CloseStack, DocumentActionsReader, PagesMutator as DocumentPagesMutator, PagesMutator, DocumentSaver, DocumentSession, EPDF_CHAR_GEOMETRY_LAYOUT, EventHub, FontRegistrar, FormMutator, FormReader, FormsEffectsApplier, LocalRevisionAuthority, MetadataMutator, MetadataReader, PageGeometryReader, PagesReader as PageLayoutReader, PagesReader, PagePtrPool, PageRenderReader as PageRasterReader, PageRenderReader, PageTextReader, PagesExtractor, PagesFlattener, PagesInserter, RawAnnotationReader, RedactionApplier, SearchReader, SecurityReader, SessionEventPublisher, WorkerHost, buildRunsFromRawGlyphs, computeMutationImpact, destroyLibrary, disposeFormModel, ensureInitialized, formatPdfDate, generateUuid, hasAllStandardSecurityPermissions, normalizeU32, openFatMemoryDocument, openLayerDocument, pdfDateToIso, readActionModel, readAttachmentFileInfo, throwIfAborted, writeAttachmentFilePayload };
10327
10752
 
10328
10753
  //# sourceMappingURL=index.js.map