@cloudpdf/engine 3.0.0-next.15 → 3.0.0-next.16

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 CHANGED
@@ -241,97 +241,23 @@ var CloudDocumentAnnotationsService = class {
241
241
  }
242
242
  };
243
243
  //#endregion
244
- //#region src/document/buildMutationForm.ts
245
- /**
246
- * The multipart envelope of a write that carries files, in the shape every
247
- * multipart message has: the `body` part holds the JSON the plain request
248
- * would have been, which names each file by role
249
- * (`resources: { <role>: <key> }`), and each file is the part
250
- * `resource:<key>`.
251
- */
252
- function buildMutationForm(body, resources) {
253
- const form = new FormData();
254
- form.append("body", JSON.stringify(body));
255
- for (const [key, resource] of Object.entries(resources)) form.append(`resource:${key}`, new Blob([resource.bytes], { type: resource.mimeType ?? "application/octet-stream" }), resource.name ?? key);
256
- return form;
257
- }
258
- /**
259
- * The envelope of a write with at most one file per role (an annotation's
260
- * drawing or attached file, a page insert's source PDF, a signature's
261
- * mark): each file's key is its role's name, and the body names it so,
262
- * `resources: { <role>: '<role>' }`.
263
- */
264
- function buildRoleMutationForm(body, files) {
265
- const parts = {};
266
- const resources = {};
267
- for (const [role, file] of Object.entries(files)) {
268
- if (!file) continue;
269
- parts[role] = file;
270
- resources[role] = role;
271
- }
272
- return buildMutationForm(Object.keys(resources).length > 0 ? {
273
- ...body,
274
- resources
275
- } : body, parts);
276
- }
277
- /** An annotation write's envelope: its drawing and attached file, by role. */
278
- function buildAnnotationMutationForm(body, resources) {
279
- return buildRoleMutationForm(body, {
280
- appearance: resources.appearance && { bytes: resources.appearance },
281
- file: resources.file && { bytes: resources.file }
282
- });
283
- }
284
- //#endregion
285
- //#region src/document/parseAttachmentContent.ts
286
- /** Header carrying the file's name, token-text encoded (names are
287
- * arbitrary UTF-8; HTTP header values are not). */
288
- const FILE_NAME_HEADER = "X-EmbedPDF-File-Name";
289
- /** Header carrying the file's declared type; absent when it has none. */
290
- const FILE_TYPE_HEADER = "X-EmbedPDF-File-Type";
291
- /**
292
- * Project an attachment-file response into `AttachmentContent`. The body
293
- * is the decoded bytes; the metadata rides as headers — `X-EmbedPDF-File-Type`
294
- * for the declared type (`Content-Type` is only what HTTP needs), and
295
- * `X-EmbedPDF-File-Name` for the file name.
296
- * Used by the document-level `attachments.download()`; the annotation-level
297
- * `annotations.downloadResource(ref, 'file')` reads the same response's bytes.
298
- */
299
- function parseAttachmentContent(file) {
300
- const encodedName = file.headers.get(FILE_NAME_HEADER);
301
- if (encodedName === null) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `attachment response missing ${FILE_NAME_HEADER} header`);
302
- let name;
303
- try {
304
- name = (0, _embedpdf_engine_core_wire.decodeTokenText)(encodedName);
305
- } catch (err) {
306
- throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.WireFormat, `malformed ${FILE_NAME_HEADER} header: ${err?.message ?? err}`, { cause: err });
307
- }
308
- return {
309
- bytes: file.bytes,
310
- name,
311
- mimeType: file.headers.get(FILE_TYPE_HEADER)
312
- };
313
- }
314
- //#endregion
315
244
  //#region src/document/CloudDocumentAttachmentsService.ts
316
- /** The `/attachments@…` listing leaf is a bare array of name-tree entries. */
317
245
  /**
318
- * Cloud-side document-level attachments (the catalog's `/EmbeddedFiles`
319
- * name tree). Reads use immutable leaves pinned by the manifest's
320
- * `attachmentsVersion`: `/attachments@…` for the metadata listing and
321
- * `/attachment-files/…@…` for one file's decoded bytes — a separate
322
- * capability tier, so a CDN credential for the listing can never egress
323
- * content. Mutations use unversioned collection/member URLs and are
324
- * never cached.
246
+ * The reads of document-level attachments (the catalog's `/EmbeddedFiles`
247
+ * name tree) on the cloud engine; `create` and `delete` are changes
248
+ * (`attachmentVerbs`, through `POST …/changes`). Reads use immutable leaves
249
+ * pinned by the manifest's `attachmentsVersion`: `/attachments@…` for the
250
+ * metadata listing and `/attachment-files/…@…` for one file's decoded
251
+ * bytes — a separate capability tier, so a CDN credential for the listing
252
+ * can never egress content.
325
253
  */
326
254
  var CloudDocumentAttachmentsService = class {
327
- constructor(http, docId, layerName, isClosed, manifest, publisher, writes) {
255
+ constructor(http, docId, layerName, isClosed, manifest) {
328
256
  this.http = http;
329
257
  this.docId = docId;
330
258
  this.layerName = layerName;
331
259
  this.isClosed = isClosed;
332
260
  this.manifest = manifest;
333
- this.publisher = publisher;
334
- this.writes = writes;
335
261
  }
336
262
  /**
337
263
  * Snapshot of the name tree, in tree (key-sorted) order. Pulls
@@ -353,10 +279,9 @@ var CloudDocumentAttachmentsService = class {
353
279
  });
354
280
  }
355
281
  /**
356
- * Decode and return one embedded file's bytes. The body is the decoded
357
- * stream; the name and mime type ride as response headers (see
358
- * `parseAttachmentContent`). Same versioned-leaf + refresh-retry rails
359
- * as {@link list}.
282
+ * One embedded file's decoded bytes; its name and type are what the
283
+ * listing says. Same versioned-leaf + refresh-retry rails as
284
+ * {@link list}. The server's limit on decoded size applies.
360
285
  */
361
286
  download(ref) {
362
287
  if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
@@ -365,51 +290,54 @@ var CloudDocumentAttachmentsService = class {
365
290
  const manifest = await this.manifest.get(s);
366
291
  return planesInherited(manifest, ["attachments"]) ? _embedpdf_engine_core_wire.wirePaths.docAttachmentFile(this.docId, ref.key, manifest.attachmentsVersion) : _embedpdf_engine_core_wire.wirePaths.layerAttachmentFile(this.docId, this.layerName, ref.key, manifest.attachmentsVersion);
367
292
  };
368
- return parseAttachmentContent(await this.http.getFileWithRefresh(buildPath, async (s) => {
293
+ return (await this.http.getFileWithRefresh(buildPath, async (s) => {
369
294
  await this.manifest.refresh(s);
370
- }, signal));
371
- });
372
- }
373
- /**
374
- * Create an embedded file in the name tree: metadata into the `body`
375
- * part, naming its file `resources: { file: 'file' }`, and the bytes in
376
- * the part `resource:file`.
377
- */
378
- create(file, options) {
379
- if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
380
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
381
- const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
382
- return this.writes.run(opId, signal, async (write) => {
383
- const { wireFile, resource } = await (0, _embedpdf_engine_core_runtime.normalizeAttachmentFileSource)(file, "file");
384
- const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerAttachmentsCollection(this.docId, this.layerName), buildMutationForm(wireFile, { file: resource }), (raw) => _embedpdf_engine_core_wire.AttachmentCreateResultSchema.parse(raw), signal, sent));
385
- this.manifest.apply(result.meta, ["attachments"]);
386
- this.publisher.publishWrite(opId, {
387
- type: "attachments.created",
388
- ...result
389
- });
390
- return result;
391
- });
392
- });
393
- }
394
- /** Delete an embedded file from the name tree by its durable key. */
395
- delete(ref, options) {
396
- if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
397
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
398
- const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
399
- return this.writes.run(opId, signal, async (write) => {
400
- const result = await write.send((sent) => this.http.deleteJson(_embedpdf_engine_core_wire.wirePaths.layerAttachmentItem(this.docId, this.layerName, ref.key), (raw) => _embedpdf_engine_core_wire.AttachmentDeleteResultSchema.parse(raw), signal, sent));
401
- this.manifest.apply(result.meta, ["attachments"]);
402
- this.publisher.publishWrite(opId, {
403
- type: "attachments.deleted",
404
- deleted: (0, _embedpdf_engine_core_runtime.deletedAttachmentOf)(result),
405
- ...result
406
- });
407
- return result;
408
- });
295
+ }, signal)).bytes;
409
296
  });
410
297
  }
411
298
  };
412
299
  //#endregion
300
+ //#region src/document/buildMutationForm.ts
301
+ /**
302
+ * The multipart envelope of a write that carries files, in the shape every
303
+ * multipart message has: the `body` part holds the JSON the plain request
304
+ * would have been, which names each file by role
305
+ * (`resources: { <role>: <key> }`), and each file is the part
306
+ * `resource:<key>`.
307
+ */
308
+ function buildMutationForm(body, resources) {
309
+ const form = new FormData();
310
+ form.append("body", JSON.stringify(body));
311
+ for (const [key, resource] of Object.entries(resources)) form.append(`resource:${key}`, new Blob([resource.bytes], { type: resource.mimeType ?? "application/octet-stream" }), resource.name ?? key);
312
+ return form;
313
+ }
314
+ /**
315
+ * The envelope of a write with at most one file per role (an annotation's
316
+ * drawing or attached file, a page insert's source PDF, a signature's
317
+ * mark): each file's key is its role's name, and the body names it so,
318
+ * `resources: { <role>: '<role>' }`.
319
+ */
320
+ function buildRoleMutationForm(body, files) {
321
+ const parts = {};
322
+ const resources = {};
323
+ for (const [role, file] of Object.entries(files)) {
324
+ if (!file) continue;
325
+ parts[role] = file;
326
+ resources[role] = role;
327
+ }
328
+ return buildMutationForm(Object.keys(resources).length > 0 ? {
329
+ ...body,
330
+ resources
331
+ } : body, parts);
332
+ }
333
+ /** An annotation write's envelope: its drawing and attached file, by role. */
334
+ function buildAnnotationMutationForm(body, resources) {
335
+ return buildRoleMutationForm(body, {
336
+ appearance: resources.appearance && { bytes: resources.appearance },
337
+ file: resources.file && { bytes: resources.file }
338
+ });
339
+ }
340
+ //#endregion
413
341
  //#region \0@oxc-project+runtime@0.140.0/helpers/esm/typeof.js
414
342
  function _typeof(o) {
415
343
  "@babel/helpers - typeof";
@@ -504,6 +432,8 @@ var CloudDocumentChanges = class {
504
432
  */
505
433
  absorb(opId, change, result) {
506
434
  this.manifest.apply(result.meta, planesOf(result));
435
+ for (const item of result.items) if (item.type === "pages.delete") this.manifest.applyPageDelete(result.meta, item.pages);
436
+ else if (item.type === "pages.insert" || item.type === "pages.insertBlank" || item.type === "pages.restore") this.manifest.applyPageInsert(result.meta);
507
437
  if (this.published.has(opId)) return;
508
438
  this.published.add(opId);
509
439
  if (this.published.size > PUBLISHED_MEMORY) this.published.delete(this.published.values().next().value);
@@ -573,6 +503,20 @@ function changeRequestOf(changes) {
573
503
  resources: { appearance: part(appearance.pdf.slice().buffer) }
574
504
  };
575
505
  }
506
+ case "pages.insert": {
507
+ const { source, ...rest } = op;
508
+ return {
509
+ ...rest,
510
+ resources: { source: part(source.slice().buffer) }
511
+ };
512
+ }
513
+ case "attachments.create": {
514
+ const { file } = op.resources;
515
+ return {
516
+ ...op,
517
+ resources: file ? { file: part(file) } : {}
518
+ };
519
+ }
576
520
  default: return op;
577
521
  }
578
522
  };
@@ -587,10 +531,13 @@ function changeRequestOf(changes) {
587
531
  parts
588
532
  };
589
533
  }
590
- /** Whether a change carries bytes: a drawing, an attached file, a signature's artwork. */
534
+ /**
535
+ * Whether a change carries bytes: a drawing, an attached file, a signature's
536
+ * artwork, a PDF whose pages come in.
537
+ */
591
538
  function carriesBytes(change) {
592
539
  if ((0, _embedpdf_engine_core_runtime.isUndoChange)(change)) return false;
593
- return change.ops.some((op) => op.type === "forms.setSignatureAppearance" || (op.type === "annotations.create" || op.type === "annotations.update") && op.resources !== void 0 && Object.values(op.resources).some((resource) => resource !== void 0));
540
+ return change.ops.some((op) => op.type === "forms.setSignatureAppearance" || op.type === "pages.insert" || op.type === "attachments.create" || (op.type === "annotations.create" || op.type === "annotations.update") && op.resources !== void 0 && Object.values(op.resources).some((resource) => resource !== void 0));
594
541
  }
595
542
  /** The planes a change's writes took over from the base, each op by its family. */
596
543
  function planesOf(result) {
@@ -598,7 +545,10 @@ function planesOf(result) {
598
545
  for (const item of result.items) {
599
546
  if ((0, _embedpdf_engine_core_runtime.isSkippedItem)(item)) continue;
600
547
  if (item.type.startsWith("metadata.")) planes.add("metadata");
548
+ else if (item.type.startsWith("outline.")) planes.add("outline");
549
+ else if (item.type.startsWith("pages.")) planes.add("layout");
601
550
  else if (item.type.startsWith("forms.")) planes.add("forms");
551
+ else if (item.type.startsWith("attachments.")) planes.add("attachments");
602
552
  else planes.add("annotations");
603
553
  }
604
554
  return [...planes];
@@ -969,6 +919,33 @@ var CloudDocumentFormsService = class {
969
919
  }
970
920
  };
971
921
  //#endregion
922
+ //#region src/document/CloudDocumentOutlineService.ts
923
+ /**
924
+ * The read behind the cloud `doc.outline`; its verbs are one-op changes
925
+ * through `doc.apply` (`outlineVerbs`). The outline lives at a
926
+ * content-addressed leaf pinned by `outlineVersion` and `layoutVersion` (it
927
+ * is read in page space). The read takes both pins from the cached
928
+ * manifest, and on a 404 (a stale pin) refreshes the manifest and tries
929
+ * once more. While the layer inherits both planes, one doc-level URL serves
930
+ * every visitor.
931
+ */
932
+ function readCloudOutline(http, docId, layerName, isClosed, manifest) {
933
+ if (isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${docId} is closed`));
934
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
935
+ const buildPath = async (s) => {
936
+ const current = await manifest.get(s);
937
+ const token = {
938
+ outlineVersion: current.outlineVersion,
939
+ layoutVersion: current.layoutVersion
940
+ };
941
+ return planesInherited(current, ["outline", "layout"]) ? _embedpdf_engine_core_wire.wirePaths.docOutline(docId, token) : _embedpdf_engine_core_wire.wirePaths.layerOutline(docId, layerName, token);
942
+ };
943
+ return http.getJsonWithRefresh(buildPath, (raw) => _embedpdf_engine_core_wire.DocumentOutlineSchema.parse(raw), async (s) => {
944
+ await manifest.refresh(s);
945
+ }, signal);
946
+ });
947
+ }
948
+ //#endregion
972
949
  //#region src/shared/awaitSignal.ts
973
950
  /**
974
951
  * Wait for a shared promise under one caller's signal: an abort rejects
@@ -992,16 +969,10 @@ function awaitSignal(promise, signal) {
992
969
  }
993
970
  //#endregion
994
971
  //#region src/document/CloudDocumentPagesService.ts
995
- /** Detach a Uint8Array view into a standalone ArrayBuffer (the resource-map
996
- * shape) without disturbing a larger buffer the caller still owns. */
997
- function copyToExactBuffer(view) {
998
- const copy = new ArrayBuffer(view.byteLength);
999
- new Uint8Array(copy).set(view);
1000
- return copy;
1001
- }
1002
972
  /**
1003
- * Cloud-side document pages service. Mirrors `LocalDocumentPagesService`
1004
- * over HTTP: GET /pages for `list`, POST /pages/reorder for the reorder.
973
+ * Cloud-side document pages service: the reads, extract, flatten and the page
974
+ * names, over HTTP, as `LocalDocumentPagesService` has them. The structure
975
+ * verbs are changes (`pageVerbs`), so they can be undone.
1005
976
  *
1006
977
  * Page identity rule (locked with the user, do not change):
1007
978
  * - Pages are addressed exclusively by `PageRef` (their indirect
@@ -1009,8 +980,8 @@ function copyToExactBuffer(view) {
1009
980
  * The wire never sends a page index for a mutation. This keeps
1010
981
  * multi-call client logic from having to account for index drift
1011
982
  * between requests.
1012
- * - Successful `reorder()` returns the new `layout` (order + geometry) plus
1013
- * cloud coherence pins. A page reorder bumps only `docVersion` +
983
+ * - A page reorder (a change) returns the new `layout` (order + geometry)
984
+ * plus cloud coherence pins. It bumps only `docVersion` +
1014
985
  * `layoutVersion`; every per-page pin stays warm.
1015
986
  */
1016
987
  var CloudDocumentPagesService = class {
@@ -1076,24 +1047,6 @@ var CloudDocumentPagesService = class {
1076
1047
  await this.manifest.refresh(s);
1077
1048
  }, signal);
1078
1049
  }
1079
- reorder(pages, position, options) {
1080
- if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
1081
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1082
- const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1083
- return this.writes.run(opId, signal, async (write) => {
1084
- const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesReorder(this.docId, this.layerName), {
1085
- pages,
1086
- position
1087
- }, (raw) => _embedpdf_engine_core_wire.PageReorderResultSchema.parse(raw), signal, sent));
1088
- this.manifest.apply(result.meta, ["layout"]);
1089
- this.publisher.publishWrite(opId, {
1090
- type: "pages.reordered",
1091
- ...result
1092
- });
1093
- return result;
1094
- });
1095
- });
1096
- }
1097
1050
  setName(input, options) {
1098
1051
  return this.runNameMutation(_embedpdf_engine_core_wire.wirePaths.layerPagesNames(this.docId, this.layerName), input, input.name, input.page, options);
1099
1052
  }
@@ -1122,86 +1075,13 @@ var CloudDocumentPagesService = class {
1122
1075
  });
1123
1076
  });
1124
1077
  }
1125
- rotate(pages, rotation, options) {
1126
- if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
1127
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1128
- const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1129
- return this.writes.run(opId, signal, async (write) => {
1130
- const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesRotate(this.docId, this.layerName), {
1131
- pages,
1132
- rotation
1133
- }, (raw) => _embedpdf_engine_core_wire.PageRotateResultSchema.parse(raw), signal, sent));
1134
- this.manifest.apply(result.meta, ["layout"]);
1135
- this.publisher.publishWrite(opId, {
1136
- type: "pages.rotated",
1137
- pages,
1138
- rotation,
1139
- ...result
1140
- });
1141
- return result;
1142
- });
1143
- });
1144
- }
1145
- delete(pages, options) {
1146
- if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
1147
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1148
- const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1149
- return this.writes.run(opId, signal, async (write) => {
1150
- const result = await write.send((sent) => this.http.postJson(_embedpdf_engine_core_wire.wirePaths.layerPagesDelete(this.docId, this.layerName), { pages }, (raw) => _embedpdf_engine_core_wire.PageDeleteResultSchema.parse(raw), signal, sent));
1151
- this.manifest.applyPageDelete(result.meta, pages);
1152
- this.publisher.publishWrite(opId, {
1153
- type: "pages.deleted",
1154
- pages,
1155
- ...result
1156
- });
1157
- return result;
1158
- });
1159
- });
1160
- }
1161
- insert(bytes, position = "end", options) {
1162
- if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
1163
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1164
- const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1165
- return this.writes.run(opId, signal, async (write) => {
1166
- const buffer = bytes instanceof ArrayBuffer ? bytes : copyToExactBuffer(bytes);
1167
- const form = buildRoleMutationForm({ position }, { source: {
1168
- bytes: buffer,
1169
- mimeType: "application/pdf",
1170
- name: "source.pdf"
1171
- } });
1172
- const result = await write.send((sent) => this.http.postMultipartJson(_embedpdf_engine_core_wire.wirePaths.layerPagesInsert(this.docId, this.layerName), form, (raw) => _embedpdf_engine_core_wire.PageInsertResultSchema.parse(raw), signal, sent));
1173
- this.manifest.applyPageInsert(result.meta);
1174
- this.publisher.publishWrite(opId, {
1175
- type: "pages.inserted",
1176
- ...result
1177
- });
1178
- return result;
1179
- });
1180
- });
1181
- }
1182
- insertBlank(spec, position = "end", options = {}) {
1183
- if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
1184
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
1185
- const opId = (0, _embedpdf_engine_core_runtime.opIdOf)(options);
1186
- const { objectNumbers } = options;
1187
- return this.writes.run(opId, signal, async (write) => {
1188
- const result = await write.send((sent) => this.http.postJson(withObjectNumbers(_embedpdf_engine_core_wire.wirePaths.layerPagesInsertBlank(this.docId, this.layerName), { objectNumbers }), {
1189
- size: spec.size,
1190
- ...spec.count !== void 0 ? { count: spec.count } : {},
1191
- position
1192
- }, (raw) => _embedpdf_engine_core_wire.PageInsertResultSchema.parse(raw), signal, sent));
1193
- this.manifest.applyPageInsert(result.meta);
1194
- this.publisher.publishWrite(opId, {
1195
- type: "pages.inserted",
1196
- ...result
1197
- });
1198
- return result;
1199
- }, objectNumbers ?? []);
1200
- });
1201
- }
1202
- extract(pages) {
1078
+ extract(pages, options) {
1203
1079
  if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
1204
- return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPagesExtract(this.docId, this.layerName), { pages }, signal));
1080
+ const body = {
1081
+ pages,
1082
+ ...options
1083
+ };
1084
+ return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => this.http.postJsonBytes(_embedpdf_engine_core_wire.wirePaths.layerPagesExtract(this.docId, this.layerName), body, signal));
1205
1085
  }
1206
1086
  flatten(pages, options) {
1207
1087
  const usage = options?.usage ?? "display";
@@ -1420,6 +1300,17 @@ var CloudDocumentSecurityService = class {
1420
1300
  formFields: this.allows("doc.forms.read")
1421
1301
  };
1422
1302
  }
1303
+ /**
1304
+ * What the caller may print, when the client can know it (as for
1305
+ * {@link pageLayerRights}). Null leaves it to the server.
1306
+ */
1307
+ pagePrintRights() {
1308
+ if (!this.access && !this.tokenHasDocScope) return null;
1309
+ return {
1310
+ print: this.allows("doc.print"),
1311
+ printHigh: this.allows("doc.print.high")
1312
+ };
1313
+ }
1423
1314
  get state() {
1424
1315
  return this.securityState;
1425
1316
  }
@@ -2394,7 +2285,7 @@ var CloudPageFormsService = class {
2394
2285
  //#endregion
2395
2286
  //#region src/document/CloudPageRenderService.ts
2396
2287
  var CloudPageRenderService = class {
2397
- constructor(http, docId, layerName, pageRef, isClosed, manifest, layout, layerRights) {
2288
+ constructor(http, docId, layerName, pageRef, isClosed, manifest, layout, layerRights, printRights) {
2398
2289
  this.http = http;
2399
2290
  this.docId = docId;
2400
2291
  this.layerName = layerName;
@@ -2403,28 +2294,32 @@ var CloudPageRenderService = class {
2403
2294
  this.manifest = manifest;
2404
2295
  this.layout = layout;
2405
2296
  this.layerRights = layerRights;
2297
+ this.printRights = printRights;
2406
2298
  }
2407
2299
  image(options = {}) {
2408
2300
  if (this.isClosed()) return _embedpdf_engine_core_runtime.AbortablePromise.rejectReason(new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.DocNotOpen, `document ${this.docId} is closed`));
2409
2301
  return _embedpdf_engine_core_runtime.AbortablePromise.run(async (signal) => {
2410
2302
  (0, _embedpdf_engine_core_runtime.checkImageQuality)(options.quality);
2411
2303
  const format = normalizeFormat(options.format);
2412
- const family = _embedpdf_engine_core_wire.PAGE_RENDER_FAMILIES[(0, _embedpdf_engine_core_wire.pageRenderFamilyOf)((0, _embedpdf_engine_core_runtime.resolvePageLayers)(options, this.layerRights() ?? {
2304
+ (0, _embedpdf_engine_core_runtime.checkRenderUsage)(options, this.printRights());
2305
+ const picture = (0, _embedpdf_engine_core_wire.pagePictureOf)((0, _embedpdf_engine_core_runtime.resolvePageLayers)(options, this.layerRights() ?? {
2413
2306
  annotations: true,
2414
2307
  formFields: true
2415
- }))];
2308
+ }), (0, _embedpdf_engine_core_runtime.printDpiOf)(options));
2416
2309
  const buildPath = async (s) => {
2417
2310
  const manifest = await this.manifest.get(s);
2418
2311
  const pageObjectNumber = this.pageRef.objectNumber;
2419
2312
  const page = manifest.pages.find((p) => p.page.objectNumber === pageObjectNumber);
2420
2313
  if (!page) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.NotFound, `no page with object number ${pageObjectNumber} in document ${this.docId}`);
2421
2314
  const pins = { contentVersion: page.cache.contentVersion };
2422
- for (const pin of family.pins) pins[pin] = page.cache[pin];
2315
+ for (const pin of picture.pins) pins[pin] = page.cache[pin];
2423
2316
  const wireToken = (0, _embedpdf_engine_core_wire.renderImageOptionsToWire)({
2424
2317
  ...options,
2425
2318
  format
2426
2319
  }, pins);
2427
- return planesInherited(manifest, family.planes) ? _embedpdf_engine_core_wire.wirePaths.docPageRender(this.docId, family.family, this.pageRef, wireToken) : _embedpdf_engine_core_wire.wirePaths.layerPageRender(this.docId, this.layerName, family.family, this.pageRef, wireToken);
2320
+ const print = picture.print;
2321
+ if (planesInherited(manifest, picture.planes)) return print ? _embedpdf_engine_core_wire.wirePaths.docPagePrint(this.docId, print.tier, picture.family, this.pageRef, wireToken) : _embedpdf_engine_core_wire.wirePaths.docPageRender(this.docId, picture.family, this.pageRef, wireToken);
2322
+ return print ? _embedpdf_engine_core_wire.wirePaths.layerPagePrint(this.docId, this.layerName, print.tier, picture.family, this.pageRef, wireToken) : _embedpdf_engine_core_wire.wirePaths.layerPageRender(this.docId, this.layerName, picture.family, this.pageRef, wireToken);
2428
2323
  };
2429
2324
  const [requestPath, transform] = await Promise.all([buildPath(signal), this.transformOf(options, signal)]);
2430
2325
  return {
@@ -2557,7 +2452,7 @@ var CloudPageMeasureService = class {
2557
2452
  * that number in events.
2558
2453
  */
2559
2454
  var CloudPageHandle = class {
2560
- constructor(ref, http, docId, layerName, isClosed, manifest, publisher, writes, layout, layerRights) {
2455
+ constructor(ref, http, docId, layerName, isClosed, manifest, publisher, writes, layout, layerRights, printRights) {
2561
2456
  this.ref = ref;
2562
2457
  _defineProperty(this, "annotations", void 0);
2563
2458
  _defineProperty(this, "forms", void 0);
@@ -2568,7 +2463,7 @@ var CloudPageHandle = class {
2568
2463
  this.annotations = new CloudPageAnnotationsService(http, docId, layerName, ref, isClosed, manifest, publisher, writes);
2569
2464
  this.forms = new CloudPageFormsService(http, docId, layerName, ref, isClosed, manifest);
2570
2465
  this.text = new CloudPageTextService(http, docId, layerName, ref, isClosed, manifest);
2571
- this.render = new CloudPageRenderService(http, docId, layerName, ref, isClosed, manifest, layout, layerRights);
2466
+ this.render = new CloudPageRenderService(http, docId, layerName, ref, isClosed, manifest, layout, layerRights, printRights);
2572
2467
  }
2573
2468
  };
2574
2469
  //#endregion
@@ -2719,12 +2614,8 @@ function untilTurn(before, signal) {
2719
2614
  * this engine version doesn't know (a newer server's events degrade to
2720
2615
  * "ignored", never to a crash).
2721
2616
  *
2722
- * Context-field fidelity differs by op, by design of the audit row:
2723
- * - rotate/delete: `affectedPages` is exactly the op's page set; rotation
2724
- * is recovered from the layout (it's absolute — every affected page
2725
- * carries the value).
2726
- * - reorder: the payload is the result, as the originator got it: the
2727
- * pages that moved and the new `layout`.
2617
+ * A page name write's row is a `pages.reorder` that moved no page: its
2618
+ * payload is the result, as the originator got it, with the new `layout`.
2728
2619
  *
2729
2620
  * The audit row keys pages by object number (its storage identity); the
2730
2621
  * event carries them as `PageRef` addresses, like every other event.
@@ -2799,7 +2690,6 @@ function factsOf(row, origin) {
2799
2690
  /** The one event a single-fact row records, or `null` for a kind this version doesn't know. */
2800
2691
  function eventOf(row, origin) {
2801
2692
  const rowPage = () => (0, _embedpdf_engine_core_runtime.toPageRef)(row.pageObjectNumber ?? row.affectedPages[0] ?? 0);
2802
- const affectedPages = () => row.affectedPages.map((pageObjectNumber) => (0, _embedpdf_engine_core_runtime.toPageRef)(pageObjectNumber));
2803
2693
  switch (row.kind) {
2804
2694
  case "measure.setScale": return {
2805
2695
  type: "pages.scaleSet",
@@ -2836,29 +2726,6 @@ function eventOf(row, origin) {
2836
2726
  origin,
2837
2727
  ...row.payload
2838
2728
  };
2839
- case "pages.rotate": {
2840
- const payload = row.payload;
2841
- const rotation = payload.layout.pages.find((page) => page.ref.objectNumber === row.affectedPages[0])?.rotation ?? 0;
2842
- return {
2843
- type: "pages.rotated",
2844
- pages: affectedPages(),
2845
- rotation,
2846
- origin,
2847
- ...payload
2848
- };
2849
- }
2850
- case "pages.delete": return {
2851
- type: "pages.deleted",
2852
- pages: affectedPages(),
2853
- origin,
2854
- ...row.payload
2855
- };
2856
- case "pages.insert":
2857
- case "pages.insertBlank": return {
2858
- type: "pages.inserted",
2859
- origin,
2860
- ...row.payload
2861
- };
2862
2729
  case "pages.flatten": return {
2863
2730
  type: "pages.flattened",
2864
2731
  origin,
@@ -2874,17 +2741,6 @@ function eventOf(row, origin) {
2874
2741
  origin,
2875
2742
  ...row.payload
2876
2743
  };
2877
- case "attachment.create": return {
2878
- type: "attachments.created",
2879
- origin,
2880
- ...row.payload
2881
- };
2882
- case "attachment.delete": return {
2883
- type: "attachments.deleted",
2884
- origin,
2885
- deleted: (0, _embedpdf_engine_core_runtime.deletedAttachmentOf)(row.payload),
2886
- ...row.payload
2887
- };
2888
2744
  case "form.setValue": return {
2889
2745
  type: "forms.valueSet",
2890
2746
  origin,
@@ -3086,11 +2942,13 @@ var CloudDocumentHandle = class {
3086
2942
  _defineProperty(this, "metadata", void 0);
3087
2943
  _defineProperty(this, "annotations", void 0);
3088
2944
  _defineProperty(this, "actions", void 0);
2945
+ _defineProperty(this, "outline", void 0);
3089
2946
  _defineProperty(this, "attachments", void 0);
3090
2947
  _defineProperty(this, "signatures", void 0);
3091
2948
  _defineProperty(this, "forms", void 0);
3092
2949
  _defineProperty(this, "search", void 0);
3093
2950
  _defineProperty(this, "pages", void 0);
2951
+ _defineProperty(this, "pageReads", void 0);
3094
2952
  _defineProperty(this, "redaction", void 0);
3095
2953
  _defineProperty(this, "security", void 0);
3096
2954
  _defineProperty(this, "render", void 0);
@@ -3174,15 +3032,17 @@ var CloudDocumentHandle = class {
3174
3032
  const importLimits = async () => (security.currentAccess ?? (await security.establishAccess()).access)?.bundleLimits ?? _embedpdf_engine_core_runtime.DEFAULT_BUNDLE_LIMITS;
3175
3033
  this.annotations = new CloudDocumentAnnotationsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, importLimits);
3176
3034
  this.actions = new CloudDocumentActionsService(http, id, layerName, () => this.closed, this.manifestAccessor);
3177
- this.attachments = new CloudDocumentAttachmentsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
3035
+ this.outline = (0, _embedpdf_engine_core_runtime.outlineVerbs)(() => readCloudOutline(http, id, layerName, () => this.closed, this.manifestAccessor), (change, options) => this.apply(change, options));
3036
+ this.attachments = (0, _embedpdf_engine_core_runtime.attachmentVerbs)(new CloudDocumentAttachmentsService(http, id, layerName, () => this.closed, this.manifestAccessor), (change, options) => this.apply(change, options));
3178
3037
  this.signatures = new CloudDocumentSignaturesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
3179
3038
  this.forms = new CloudDocumentFormsService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, importLimits);
3180
3039
  this.search = new CloudDocumentSearchService(http, id, layerName, () => this.closed, this.manifestAccessor);
3181
- this.pages = new CloudDocumentPagesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
3040
+ this.pageReads = new CloudDocumentPagesService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
3041
+ this.pages = (0, _embedpdf_engine_core_runtime.pageVerbs)(this.pageReads, (change, options) => this.apply(change, options));
3182
3042
  this.redaction = new CloudDocumentRedactionService(http, id, layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes);
3183
3043
  }
3184
3044
  page(ref) {
3185
- return new CloudPageHandle(ref, this.http, this.id, this.layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, (signal) => this.pages.pageLayout(ref, signal), () => this.cloudSecurity.pageLayerRights());
3045
+ return new CloudPageHandle(ref, this.http, this.id, this.layerName, () => this.closed, this.manifestAccessor, this.publisher, this.writes, (signal) => this.pageReads.pageLayout(ref, signal), () => this.cloudSecurity.pageLayerRights(), () => this.cloudSecurity.pagePrintRights());
3186
3046
  }
3187
3047
  /**
3188
3048
  * This handle: the cloud engine sends every call as it's made, in
@@ -3303,6 +3163,7 @@ var CloudDocumentHandle = class {
3303
3163
  ...delta?.layoutVersion !== void 0 ? { layoutVersion: delta.layoutVersion } : {},
3304
3164
  ...delta?.metadataVersion !== void 0 ? { metadataVersion: delta.metadataVersion } : {},
3305
3165
  ...delta?.attachmentsVersion !== void 0 ? { attachmentsVersion: delta.attachmentsVersion } : {},
3166
+ ...delta?.outlineVersion !== void 0 ? { outlineVersion: delta.outlineVersion } : {},
3306
3167
  ...delta?.layerVersion !== void 0 ? { layerVersion: delta.layerVersion } : {},
3307
3168
  ...delta?.working !== void 0 ? { working: delta.working } : {},
3308
3169
  pages: Array.from(byPageObjectNumber.values()).sort((a, b) => a.page.objectNumber - b.page.objectNumber)
@@ -3447,7 +3308,9 @@ var CloudDocumentHandle = class {
3447
3308
  }
3448
3309
  /** Patch the cached manifest from a remote event's coherence pins — the
3449
3310
  * same absorb rails local mutations use, so reads stay warm no matter
3450
- * whose hand caused the change. */
3311
+ * whose hand caused the change. Every event kind is named below, so a new
3312
+ * one can't be forgotten: a missed one leaves this client reading the
3313
+ * old version from an immutable, cached URL. */
3451
3314
  absorbRemoteEvent(event) {
3452
3315
  switch (event.type) {
3453
3316
  case "pages.scaleSet":
@@ -3456,9 +3319,17 @@ var CloudDocumentHandle = class {
3456
3319
  case "annotations.created":
3457
3320
  case "annotations.updated":
3458
3321
  case "annotations.deleted":
3322
+ case "annotations.restored":
3459
3323
  case "annotations.reordered":
3460
3324
  this.absorbMutation(event.meta, ["annotations"]);
3461
3325
  return;
3326
+ case "outline.created":
3327
+ case "outline.updated":
3328
+ case "outline.moved":
3329
+ case "outline.deleted":
3330
+ case "outline.restored":
3331
+ this.absorbMutation(event.meta, ["outline"]);
3332
+ return;
3462
3333
  case "pages.reordered":
3463
3334
  case "pages.rotated":
3464
3335
  case "pages.named":
@@ -3468,6 +3339,7 @@ var CloudDocumentHandle = class {
3468
3339
  this.absorbPageDelete(event.meta, event.pages);
3469
3340
  return;
3470
3341
  case "pages.inserted":
3342
+ case "pages.restored":
3471
3343
  this.absorbPageInsert(event.meta);
3472
3344
  return;
3473
3345
  case "metadata.updated":
@@ -3476,6 +3348,7 @@ var CloudDocumentHandle = class {
3476
3348
  return;
3477
3349
  case "attachments.created":
3478
3350
  case "attachments.deleted":
3351
+ case "attachments.restored":
3479
3352
  this.absorbMutation(event.meta, ["attachments"]);
3480
3353
  return;
3481
3354
  case "forms.valueSet":
@@ -3513,6 +3386,10 @@ var CloudDocumentHandle = class {
3513
3386
  "forms"
3514
3387
  ]);
3515
3388
  return;
3389
+ case "signatures.completed": return;
3390
+ case "signatures.cancelled": return;
3391
+ case "stream.desynced": return;
3392
+ default:
3516
3393
  }
3517
3394
  }
3518
3395
  };