@laminapdf/wasm 0.2.0-alpha.6 → 0.3.0

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.
@@ -14,11 +14,146 @@ export interface Diagnostic {
14
14
  }
15
15
 
16
16
 
17
+ /**
18
+ * A bookmark's look: its colour (`/C`; `None` is the viewer's default, black) and type style
19
+ * (`/F`: bit 1 italic, bit 2 bold).
20
+ *
21
+ * Added in 0.3.0.
22
+ */
23
+ export interface OutlineStyle {
24
+ /**
25
+ * The colour.
26
+ */
27
+ color?: string | null;
28
+ /**
29
+ * Bold.
30
+ */
31
+ bold?: boolean;
32
+ /**
33
+ * Italic.
34
+ */
35
+ italic?: boolean;
36
+ }
37
+
38
+ /**
39
+ * A change a signature's restriction forbids.
40
+ */
41
+ export interface ViolationJs {
42
+ /**
43
+ * The kind of change.
44
+ */
45
+ class: ModificationClass;
46
+ /**
47
+ * `docMdp`, `fieldMdp`, `usageRights`, `signedBytes` or `signedWidget`.
48
+ */
49
+ restriction: string;
50
+ /**
51
+ * The certification's `/P` (1, 2 or 3), for a `docMdp` restriction.
52
+ */
53
+ permission?: number;
54
+ /**
55
+ * The signature that carries the restriction.
56
+ */
57
+ signature: SignatureJs;
58
+ /**
59
+ * The form field whose widget a page edit deletes or reshows (its ID), for a
60
+ * `signedWidget` restriction or a `fieldMdp` one that a page edit breaks.
61
+ */
62
+ field?: string;
63
+ }
64
+
65
+ /**
66
+ * A colour of 8 bits a channel, `"#rrggbb"` on the wire. A bookmark's `/C` (§12.3.3,
67
+ * Table 151) holds it as three numbers from 0 to 1.
68
+ *
69
+ * Added in 0.3.0.
70
+ */
71
+ export type Rgb = string;
72
+
73
+ /**
74
+ * A conformance claim the document's metadata no longer meets.
75
+ *
76
+ * Added in 0.2.0.
77
+ */
78
+ export type ConformanceWarning = { code: "pdfa-one-author"; authors: number } | { code: "pdfua-title-missing" } | { code: "pdfa-info-mismatch"; keys: MetadataKey[] } | { code: "duplicate-tags-incomplete" } | { code: "imported-tags-dropped" } | { code: "imported-pdfa-unchecked" };
79
+
17
80
  /**
18
81
  * A conformance profile.
19
82
  */
20
83
  export type ProfileInit = "PDF/UA-1" | "PDF/A-2b" | "PDF/A-2u" | "PDF/A-3b";
21
84
 
85
+ /**
86
+ * A document's metadata as one record, read from the Info dictionary and the XMP packet.
87
+ *
88
+ * When the two disagree, the values come from the XMP packet in a PDF 2.0 file (ISO 32000-2
89
+ * §14.3.3 deprecates the Info entries) or a file that claims PDF/A (whose packet is
90
+ * authoritative, ISO 19005-2 §6.6.2.3.1), and otherwise from the side whose modification date
91
+ * is newer, Info winning a tie. A field the winning side lacks is taken from the other.
92
+ * [`disagreements`](DocumentMetadata::disagreements) names the fields where the two differ, so
93
+ * a properties view can say so.
94
+ *
95
+ * Added in 0.2.0.
96
+ */
97
+ export interface DocumentMetadata {
98
+ /**
99
+ * The title.
100
+ */
101
+ title?: string;
102
+ /**
103
+ * The authors, in order (Info's `/Author` split at `;`).
104
+ */
105
+ authors: string[];
106
+ /**
107
+ * The subject.
108
+ */
109
+ subject?: string;
110
+ /**
111
+ * The keywords.
112
+ */
113
+ keywords?: string;
114
+ /**
115
+ * The application the content came from.
116
+ */
117
+ creator?: string;
118
+ /**
119
+ * The application that wrote the PDF.
120
+ */
121
+ producer?: string;
122
+ /**
123
+ * The creation date (a PDF date string on the wire).
124
+ */
125
+ created?: string;
126
+ /**
127
+ * The modification date.
128
+ */
129
+ modified?: string;
130
+ /**
131
+ * Which side the values come from.
132
+ */
133
+ source: MetadataSource;
134
+ /**
135
+ * The fields where Info and XMP hold different values.
136
+ */
137
+ disagreements: MetadataKey[];
138
+ /**
139
+ * What `/Metadata` holds.
140
+ */
141
+ packet: PacketStatus;
142
+ /**
143
+ * The PDF/A part and conformance the packet claims (`pdfaid:`), e.g. `"2B"`.
144
+ */
145
+ pdfa?: string;
146
+ /**
147
+ * The PDF/UA part the packet claims (`pdfuaid:part`).
148
+ */
149
+ pdfua?: number;
150
+ /**
151
+ * In a PDF 2.0 file, the Info entries besides the two dates: deprecated, kept as they
152
+ * were for older readers, and never written by an edit.
153
+ */
154
+ keptInfoKeys: MetadataKey[];
155
+ }
156
+
22
157
  /**
23
158
  * A job's progress (`laminapdf::Step::Progress`).
24
159
  */
@@ -52,6 +187,8 @@ export interface ProgressJs {
52
187
  * ```
53
188
  *
54
189
  * [ADR 0006]: https://github.com/laudebugs/laminapdf/blob/main/docs/decisions/0006-op-log-and-undo.md
190
+ *
191
+ * Added in 0.2.0.
55
192
  */
56
193
  export interface OpLog {
57
194
  v: 1;
@@ -59,147 +196,955 @@ export interface OpLog {
59
196
  }
60
197
 
61
198
  /**
62
- * A page-label numbering style.
199
+ * A metadata field Lamina PDF keeps in step in the Info dictionary and the XMP packet: what
200
+ * [`RemoveMetadata`] removes and [`DocumentMetadata::disagreements`] names.
201
+ *
202
+ * | Key | Info | XMP |
203
+ * | --- | --- | --- |
204
+ * | `title` | `/Title` | `dc:title` |
205
+ * | `authors` | `/Author` (joined with `; `) | `dc:creator` |
206
+ * | `subject` | `/Subject` | `dc:description` |
207
+ * | `keywords` | `/Keywords` | `pdf:Keywords` |
208
+ * | `creator` | `/Creator` | `xmp:CreatorTool` |
209
+ * | `producer` | `/Producer` | `pdf:Producer` |
210
+ * | `created` | `/CreationDate` | `xmp:CreateDate` |
211
+ * | `modified` | `/ModDate` | `xmp:ModifyDate` |
212
+ *
213
+ * Added in 0.2.0.
214
+ */
215
+ export type MetadataKey = "title" | "authors" | "subject" | "keywords" | "creator" | "producer" | "created" | "modified";
216
+
217
+ /**
218
+ * A move in user space (ISO 32000-2 §8.3.2.3: y up), in user-space units. Both distances are
219
+ * finite: [`Offset::new`] and the wire form refuse anything else, so a move never writes a
220
+ * coordinate a PDF can't hold.
221
+ *
222
+ * Added in 0.3.0.
223
+ */
224
+ export interface Offset {
225
+ dx: number;
226
+ dy: number;
227
+ }
228
+
229
+ /**
230
+ * A named destination brought in under another name.
231
+ */
232
+ export interface RenamedDestinationJs {
233
+ /**
234
+ * Its name in the source.
235
+ */
236
+ from: string;
237
+ /**
238
+ * Its name in this document.
239
+ */
240
+ to: string;
241
+ }
242
+
243
+ /**
244
+ * A page of this document and how to show it: what `/Dest [page /XYZ …]` holds.
245
+ *
246
+ * Added in 0.3.0.
247
+ */
248
+ export interface ExplicitDestination {
249
+ /**
250
+ * The page.
251
+ */
252
+ page: string;
253
+ /**
254
+ * How to show it.
255
+ */
256
+ view: DestinationView;
257
+ }
258
+
259
+ /**
260
+ * A page-label numbering style.
261
+ */
262
+ export type LabelStyleInit = "decimal" | "upperRoman" | "lowerRoman" | "upperLetters" | "lowerLetters";
263
+
264
+ /**
265
+ * A patch of the window entries of `/ViewerPreferences` ([`WindowPreferences`]): each field
266
+ * left out keeps what the file has, `true` sets the entry, `false` removes it.
267
+ *
268
+ * Added in 0.3.0.
269
+ */
270
+ export interface WindowPreferencesPatch {
271
+ /**
272
+ * `/HideToolbar`.
273
+ */
274
+ hideToolbar?: boolean;
275
+ /**
276
+ * `/HideMenubar`.
277
+ */
278
+ hideMenubar?: boolean;
279
+ /**
280
+ * `/HideWindowUI`.
281
+ */
282
+ hideWindowUi?: boolean;
283
+ /**
284
+ * `/FitWindow`.
285
+ */
286
+ fitWindow?: boolean;
287
+ /**
288
+ * `/CenterWindow`.
289
+ */
290
+ centerWindow?: boolean;
291
+ /**
292
+ * `/DisplayDocTitle`.
293
+ */
294
+ displayDocTitle?: boolean;
295
+ }
296
+
297
+ /**
298
+ * A signature the planner found.
299
+ */
300
+ export interface SignatureJs {
301
+ /**
302
+ * `approval`, `certification`, `docTimeStamp` or `usageRights`.
303
+ */
304
+ kind: string;
305
+ /**
306
+ * The signature field's ID, when a field holds it.
307
+ */
308
+ field?: string;
309
+ /**
310
+ * The 1-based revision the signature closes, when known.
311
+ */
312
+ revision?: number;
313
+ }
314
+
315
+ /**
316
+ * Adds a bookmark under `parent` (`null`: the top level) at `at`, with its title, target and
317
+ * style. It has no children, so it has no open state yet (`/Count`, §12.3.3): set it with
318
+ * [`UpdateOutlineItem::open`] once it has some. The new item's ID is in
319
+ * [`Applied::created_outline_items`](crate::Applied::created_outline_items).
320
+ *
321
+ * On the wire: `{ "op": "addOutlineItem", "args": { "parent": null, "at": "last", "title":
322
+ * "Chapter 1", "target": { "destination": { "page": "p3.0", "view": { "kind": "fit" } } } } }`.
323
+ *
324
+ * Added in 0.3.0.
325
+ */
326
+ export interface AddOutlineItem {
327
+ /**
328
+ * The parent item; `None` for the top level.
329
+ */
330
+ parent?: string | null;
331
+ /**
332
+ * Where among the parent's children.
333
+ */
334
+ at: "first" | "last" | { before: string } | { after: string };
335
+ /**
336
+ * The title.
337
+ */
338
+ title: string;
339
+ /**
340
+ * Where the bookmark goes.
341
+ */
342
+ target: OutlineTarget;
343
+ /**
344
+ * Its colour and type style.
345
+ */
346
+ style?: OutlineStyle;
347
+ }
348
+
349
+ /**
350
+ * Adds a named destination, or points an existing one at `destination`. A name the catalog's
351
+ * `/Dests` dictionary holds stays there; any other goes into the `/Names /Dests` tree.
352
+ *
353
+ * On the wire: `{ "op": "setNamedDestination", "args": { "name": "results", "destination": {
354
+ * "page": "p7.0", "view": { "kind": "fitH", "top": 700 } } } }`.
355
+ *
356
+ * Added in 0.3.0.
357
+ */
358
+ export interface SetNamedDestination {
359
+ /**
360
+ * The name.
361
+ */
362
+ name: string;
363
+ /**
364
+ * Where it points.
365
+ */
366
+ destination: ExplicitDestination;
367
+ }
368
+
369
+ /**
370
+ * An XMP property declared in a PDF/A file's extension schemas.
371
+ */
372
+ export interface ExtensionPropertyJs {
373
+ /**
374
+ * The schema's namespace URI.
375
+ */
376
+ namespace: string;
377
+ /**
378
+ * The prefix the packet writes the namespace with.
379
+ */
380
+ prefix: string;
381
+ /**
382
+ * The property's local name.
383
+ */
384
+ name: string;
385
+ /**
386
+ * Its declared type, e.g. `Text` or `Seq Integer`.
387
+ */
388
+ valueType: string;
389
+ }
390
+
391
+ /**
392
+ * An attached file's options.
393
+ */
394
+ export interface AttachmentInit {
395
+ /**
396
+ * How the file relates to the document (default `unspecified`).
397
+ */
398
+ relationship?: AfRelationshipInit;
399
+ /**
400
+ * `/Desc`, shown next to the file.
401
+ */
402
+ description?: string;
403
+ /**
404
+ * When the file last changed, a PDF date (default: the document's creation date).
405
+ */
406
+ modDate?: string;
407
+ }
408
+
409
+ /**
410
+ * An editing operation ([ADR 0006] §1): typed and semantic (it names things by
411
+ * [`LaminaPdfId`](laminapdf_model::LaminaPdfId), never by object reference), serialisable, and free
412
+ * of ambient inputs (a modification date is in the payload), so the same op on the same
413
+ * document gives the same result on every platform.
414
+ *
415
+ * Apply it inside a transaction ([`Tx::apply`](crate::Tx::apply)); its JSON form is
416
+ * adjacently tagged, `{ "op": "rotatePage", "args": { … } }`, and unknown ops and fields
417
+ * are rejected, never skipped.
418
+ *
419
+ * ```
420
+ * use laminapdf_ops::Op;
421
+ *
422
+ * let op: Op = serde_json::from_str(r#"{ "op": "rotatePage", "args": { "page": "p3.0", "rotate": 90 } }"#)?;
423
+ * assert_eq!(serde_json::to_string(&op)?, r#"{"op":"rotatePage","args":{"page":"p3.0","rotate":90}}"#);
424
+ * assert!(serde_json::from_str::<Op>(r#"{ "op": "explode", "args": {} }"#).is_err());
425
+ * # Ok::<(), serde_json::Error>(())
426
+ * ```
427
+ *
428
+ * [ADR 0006]: https://github.com/laudebugs/laminapdf/blob/main/docs/decisions/0006-op-log-and-undo.md
429
+ *
430
+ * Added in 0.2.0.
431
+ */
432
+ export type Op = { op: "setInfo"; args: SetInfo } | { op: "rotatePage"; args: RotatePage } | { op: "setMetadata"; args: SetMetadata } | { op: "removeMetadata"; args: RemoveMetadata } | { op: "setInitialView"; args: SetInitialView } | { op: "addOutlineItem"; args: AddOutlineItem } | { op: "updateOutlineItem"; args: UpdateOutlineItem } | { op: "moveOutlineItem"; args: MoveOutlineItem } | { op: "deleteOutlineItem"; args: DeleteOutlineItem } | { op: "setNamedDestination"; args: SetNamedDestination } | { op: "renameNamedDestination"; args: RenameNamedDestination } | { op: "deleteNamedDestination"; args: DeleteNamedDestination } | { op: "setXmpProperty"; args: SetXmpProperty } | { op: "removeXmpProperty"; args: RemoveXmpProperty } | { op: "replaceXmpPacket"; args: ReplaceXmpPacket } | { op: "insertBlankPages"; args: InsertBlankPages } | { op: "deletePages"; args: DeletePages } | { op: "movePages"; args: MovePages } | { op: "rotatePages"; args: RotatePages } | { op: "reversePages"; args: ReversePages } | { op: "duplicatePages"; args: DuplicatePages } | { op: "insertPagesFrom"; args: InsertPagesFrom } | { op: "replacePages"; args: ReplacePages } | { op: "mergeDocuments"; args: MergeDocuments } | { op: "updateAnnot"; args: UpdateAnnot } | { op: "deleteAnnot"; args: DeleteAnnot } | { op: "setPageLabels"; args: SetPageLabels };
433
+
434
+ /**
435
+ * Changes a bookmark's title, target, style or open state. An absent field is left alone. A
436
+ * target held as another action (JavaScript, `Launch`, …) is replaced only when `target` is
437
+ * given; the item's other entries are kept.
438
+ *
439
+ * On the wire: `{ "op": "updateOutlineItem", "args": { "item": "o12.0", "title": "Results",
440
+ * "open": true } }`.
441
+ *
442
+ * Added in 0.3.0.
443
+ */
444
+ export interface UpdateOutlineItem {
445
+ /**
446
+ * The item.
447
+ */
448
+ item: string;
449
+ /**
450
+ * A new title.
451
+ */
452
+ title?: string;
453
+ /**
454
+ * A new target.
455
+ */
456
+ target?: OutlineTarget;
457
+ /**
458
+ * A new style (colour and type style together).
459
+ */
460
+ style?: OutlineStyle;
461
+ /**
462
+ * Open (children shown) or closed; a bookmark without children has nowhere to keep it
463
+ * (`/Count`, §12.3.3), so it writes nothing there.
464
+ */
465
+ open?: boolean;
466
+ }
467
+
468
+ /**
469
+ * Changes an annotation's common and markup entries and moves it.
470
+ *
471
+ * An absent field leaves the entry alone and `null` removes it. `translate` moves `/Rect` and
472
+ * the subtype's geometry (`/QuadPoints`, `/InkList`, `/Vertices`, `/L`, `/CL`) and the
473
+ * annotation's Popup with it, and keeps the appearance, which §12.5.5 maps onto the new
474
+ * `/Rect`. A `Locked` annotation refuses everything but a change of its flags and its
475
+ * contents; a `LockedContents` one refuses a change of its contents (`annotation-locked`).
476
+ * Widgets and annotations stored as direct dictionaries are refused (`annotation-not-editable`).
477
+ *
478
+ * On the wire: `{ "op": "updateAnnot", "args": { "id": "a12.0", "contents": "Checked",
479
+ * "translate": { "dx": 0, "dy": -10 } } }`.
480
+ *
481
+ * Added in 0.3.0.
482
+ */
483
+ export interface UpdateAnnot {
484
+ /**
485
+ * The annotation.
486
+ */
487
+ id: string;
488
+ /**
489
+ * `/Contents`: the comment, or the alternative description.
490
+ */
491
+ contents?: string | null;
492
+ /**
493
+ * `/T`: the author.
494
+ */
495
+ author?: string | null;
496
+ /**
497
+ * `/Subj`.
498
+ */
499
+ subject?: string | null;
500
+ /**
501
+ * `/M`, the modification date the edit writes.
502
+ */
503
+ modified?: string;
504
+ /**
505
+ * `/F`, all the flags at once (Table 167).
506
+ */
507
+ flags?: number;
508
+ /**
509
+ * A move in user space.
510
+ */
511
+ translate?: Offset;
512
+ }
513
+
514
+ /**
515
+ * Changes how the document asks to be shown when it opens. Each field is a patch: left out,
516
+ * it keeps what the file has; `null` removes the entry, so readers use their default; a value
517
+ * sets it.
518
+ *
519
+ * * `open`: the page and zoom to open at, written as an explicit destination in
520
+ * `/OpenAction` (§12.3.2.2). An `/OpenAction` that is an action other than `GoTo`
521
+ * (JavaScript, a named action, a link to another file) is never replaced or removed: the op
522
+ * fails with [`Error::OpenActionNotDestination`].
523
+ * * `page_mode`, `page_layout`: the panel to show and how to lay the pages out.
524
+ * * `preferences`: the window entries of `/ViewerPreferences` (§12.2, Table 147); `true` sets
525
+ * one, `false` removes it (its default), and other entries (`/Direction`, `/PrintScaling`, …)
526
+ * are kept.
527
+ *
528
+ * When a value needs a later PDF version than the file declares (`UseAttachments` 1.6,
529
+ * `UseOC` and the two-page layouts 1.5, `/DisplayDocTitle` 1.4), the catalog's `/Version` is
530
+ * raised to it. The op is class [`Document`](crate::ModificationClass::Document) and touches no
531
+ * page.
532
+ *
533
+ * On the wire:
534
+ * `{ "op": "setInitialView", "args": { "open": { "page": "p12.0", "zoom": "fitWidth" },
535
+ * "pageMode": "outlines", "pageLayout": null, "preferences": { "displayDocTitle": true } } }`.
536
+ *
537
+ * ```
538
+ * use laminapdf_ops::{OpenAt, OpenZoom, SetInitialView, WindowPreferencesPatch};
539
+ * use laminapdf_model::PageMode;
540
+ *
541
+ * let op = SetInitialView::default()
542
+ * .with_open(Some(OpenAt::new("p3.0".parse()?, OpenZoom::FitWidth)))
543
+ * .with_page_mode(Some(PageMode::Outlines))
544
+ * .with_preferences(WindowPreferencesPatch::default().with_display_doc_title(true));
545
+ * assert_eq!(
546
+ * serde_json::to_string(&op)?,
547
+ * r#"{"open":{"page":"p3.0","zoom":"fitWidth"},"pageMode":"outlines","preferences":{"displayDocTitle":true}}"#
548
+ * );
549
+ * # Ok::<(), Box<dyn std::error::Error>>(())
550
+ * ```
551
+ *
552
+ * Added in 0.3.0.
553
+ */
554
+ export interface SetInitialView {
555
+ /**
556
+ * The page and zoom to open at.
557
+ */
558
+ open?: OpenAt | null;
559
+ /**
560
+ * The panel to show (`/PageMode`).
561
+ */
562
+ pageMode?: "none" | "outlines" | "thumbnails" | "fullScreen" | "optionalContent" | "attachments" | null;
563
+ /**
564
+ * How to lay the pages out (`/PageLayout`).
565
+ */
566
+ pageLayout?: "singlePage" | "oneColumn" | "twoColumnLeft" | "twoColumnRight" | "twoPageLeft" | "twoPageRight" | null;
567
+ /**
568
+ * The window entries of `/ViewerPreferences`.
569
+ */
570
+ preferences?: WindowPreferencesPatch;
571
+ }
572
+
573
+ /**
574
+ * Changes to the Info dictionary's text entries. For each field, `None` leaves the entry
575
+ * alone, `Some(None)` removes it and `Some(Some(text))` sets it (encoded as
576
+ * PDFDocEncoding when it can be, else UTF-16BE, §7.9.2.2).
577
+ *
578
+ * On the wire an absent field is left alone and `null` clears: `{ "title": "Report",
579
+ * "author": null }`.
580
+ *
581
+ * Added in 0.2.0.
582
+ */
583
+ export interface InfoPatch {
584
+ /**
585
+ * `/Title`.
586
+ */
587
+ title?: string | null;
588
+ /**
589
+ * `/Author`.
590
+ */
591
+ author?: string | null;
592
+ /**
593
+ * `/Subject`.
594
+ */
595
+ subject?: string | null;
596
+ /**
597
+ * `/Keywords`.
598
+ */
599
+ keywords?: string | null;
600
+ /**
601
+ * `/Creator`: the application that created the original document.
602
+ */
603
+ creator?: string | null;
604
+ /**
605
+ * `/Producer`: the application that converted it to PDF.
606
+ */
607
+ producer?: string | null;
608
+ }
609
+
610
+ /**
611
+ * Changes to the document's metadata. For each field, `None` leaves it alone, `Some(None)`
612
+ * removes it and `Some(Some(value))` sets it, in Info and XMP alike.
613
+ *
614
+ * On the wire an absent field is left alone and `null` removes: `{ "title": "Report",
615
+ * "authors": ["Ada Lovelace", "Charles Babbage"], "keywords": null }`; dates are PDF date
616
+ * strings (`"D:20261110093000+01'00'"`).
617
+ *
618
+ * ```
619
+ * use laminapdf_ops::MetadataPatch;
620
+ *
621
+ * let patch = MetadataPatch::new().title(Some("Quarterly figures")).authors(&["Ada Lovelace", "Charles Babbage"]);
622
+ * assert_eq!(patch.authors, Some(Some(vec!["Ada Lovelace".to_owned(), "Charles Babbage".to_owned()])));
623
+ * assert_eq!(serde_json::to_string(&patch)?, r#"{"title":"Quarterly figures","authors":["Ada Lovelace","Charles Babbage"]}"#);
624
+ * # Ok::<(), serde_json::Error>(())
625
+ * ```
626
+ *
627
+ * Added in 0.2.0.
628
+ */
629
+ export interface MetadataPatch {
630
+ /**
631
+ * `/Title`, `dc:title`.
632
+ */
633
+ title?: string | null;
634
+ /**
635
+ * `/Author` (joined with `; `), `dc:creator` (in order). An empty list removes them.
636
+ */
637
+ authors?: string[] | null;
638
+ /**
639
+ * `/Subject`, `dc:description`.
640
+ */
641
+ subject?: string | null;
642
+ /**
643
+ * `/Keywords`, `pdf:Keywords`.
644
+ */
645
+ keywords?: string | null;
646
+ /**
647
+ * `/Creator`, `xmp:CreatorTool`: the application the content came from.
648
+ */
649
+ creator?: string | null;
650
+ /**
651
+ * `/Producer`, `pdf:Producer`: the application that wrote the PDF.
652
+ */
653
+ producer?: string | null;
654
+ /**
655
+ * `/CreationDate`, `xmp:CreateDate`.
656
+ */
657
+ created?: string | null;
658
+ /**
659
+ * `/ModDate`, `xmp:ModifyDate`.
660
+ */
661
+ modified?: string | null;
662
+ }
663
+
664
+ /**
665
+ * Copies `pages` (each once, in the order given) to `at`. A copy shares its source's content
666
+ * streams and resources and shows what it shows, inherited attributes included (ISO 32000-2
667
+ * §7.7.3.4). Its annotations are new objects with a new `/NM`; a form field's widget becomes
668
+ * a new field named `<partial>#<n>` with its own value, and a signature's widget is not copied
669
+ * (both in [`Applied::fields`](crate::Applied::fields)). In a tagged document the copy gets
670
+ * its own `/StructParents` key and clones of the elements whose content lies only on its
671
+ * source; content of elements that span pages is left untagged and the copy is listed in
672
+ * [`Applied::tags_incomplete`](crate::Applied::tags_incomplete). Page labels continue the range
673
+ * each copy lands in.
674
+ *
675
+ * On the wire: `{ "op": "duplicatePages", "args": { "pages": ["p7.0"], "at": { "after": "p7.0" } } }`.
676
+ *
677
+ * Added in 0.3.0.
678
+ */
679
+ export interface DuplicatePages {
680
+ /**
681
+ * The pages to copy.
682
+ */
683
+ pages: string[];
684
+ /**
685
+ * Where the copies go.
686
+ */
687
+ at: "start" | "end" | { before: string } | { after: string };
688
+ }
689
+
690
+ /**
691
+ * Copies pages of a source document into this one at `at`, in the order given (each once).
692
+ * The copies are new objects with new IDs, sharing nothing with the source. Their
693
+ * annotations point at them and get a new `/NM`; a link to a page that didn't come along is
694
+ * removed ([`Applied::links_removed`](crate::Applied::links_removed)), and the named
695
+ * destinations the others use come along, renamed `<name>#<n>` on a clash. Form fields join
696
+ * the form under their own names, or per [`Carry::fields`] when the name is taken
697
+ * ([`Applied::fields`](crate::Applied::fields)); signature fields are left off. The copies
698
+ * lose their tags ([`Applied::tags_dropped`](crate::Applied::tags_dropped)), page labels
699
+ * continue the range each copy lands in, and the document's version rises to the source's
700
+ * when that is newer.
701
+ *
702
+ * On the wire: `{ "op": "insertPagesFrom", "args": { "source": "<64 hex digits>", "pages":
703
+ * ["p3.0"], "at": "end" } }`, with `carry` optional.
704
+ *
705
+ * Added in 0.3.0.
706
+ */
707
+ export interface InsertPagesFrom {
708
+ /**
709
+ * The source document.
710
+ */
711
+ source: string;
712
+ /**
713
+ * The source's pages to copy, by their IDs in the source.
714
+ */
715
+ pages: string[];
716
+ /**
717
+ * Where the copies go.
718
+ */
719
+ at: "start" | "end" | { before: string } | { after: string };
720
+ /**
721
+ * What comes along with the pages.
722
+ */
723
+ carry?: Carry;
724
+ }
725
+
726
+ /**
727
+ * Deletes `pages` (each once; unknown pages are an error). Outline items, links and `GoTo`
728
+ * actions on surviving pages, named destinations and `/OpenAction` that named a deleted page
729
+ * follow `references` ([`ReferencePolicy`]); structure elements whose content was all on
730
+ * deleted pages are removed with their `/ParentTree` entries, and the widgets on deleted pages
731
+ * leave the form (a field left with none leaves it too). The page objects stay in the file's
732
+ * earlier revisions. Deleting every page is [`Error::NoPagesLeft`].
733
+ *
734
+ * On the wire: `{ "op": "deletePages", "args": { "pages": ["p7.0"], "references": "retarget" } }`.
735
+ *
736
+ * Added in 0.3.0.
737
+ */
738
+ export interface DeletePages {
739
+ /**
740
+ * The pages.
741
+ */
742
+ pages: string[];
743
+ /**
744
+ * What happens to destinations that name them; `retarget` when left out.
745
+ */
746
+ references?: ReferencePolicy;
747
+ }
748
+
749
+ /**
750
+ * Deletes a bookmark, and its children with it or moved up into its place.
751
+ *
752
+ * On the wire: `{ "op": "deleteOutlineItem", "args": { "item": "o12.0", "children":
753
+ * "promote" } }`.
754
+ *
755
+ * Added in 0.3.0.
756
+ */
757
+ export interface DeleteOutlineItem {
758
+ /**
759
+ * The item.
760
+ */
761
+ item: string;
762
+ /**
763
+ * What happens to its children.
764
+ */
765
+ children: ChildItems;
766
+ }
767
+
768
+ /**
769
+ * Deletes a named destination, from both stores when both hold it. Its uses in this file are left as they are and reported
770
+ * ([`Applied::destination_uses`](crate::Applied::destination_uses)): a bookmark or link that
771
+ * names it no longer goes anywhere.
772
+ *
773
+ * On the wire: `{ "op": "deleteNamedDestination", "args": { "name": "draft-notes" } }`.
774
+ *
775
+ * Added in 0.3.0.
776
+ */
777
+ export interface DeleteNamedDestination {
778
+ /**
779
+ * The name.
780
+ */
781
+ name: string;
782
+ }
783
+
784
+ /**
785
+ * Deletes an annotation with what belongs to it: its Popup, every reply whose `/IRT` chain
786
+ * reaches it, and, for a member of a group (`/RT /Group`, §12.5.6.2), the whole group, which
787
+ * is one unit. Chains are followed through cycles once. In a tagged document, the structure
788
+ * element's object reference to each deleted annotation is removed, and so is an element left
789
+ * with nothing in it. Each deleted object is freed.
790
+ *
791
+ * A `Locked` annotation is refused (`annotation-locked`), and so are widgets and direct
792
+ * dictionaries (`annotation-not-editable`). Deleting a Popup alone removes the `/Popup` entry
793
+ * that names it.
794
+ *
795
+ * On the wire: `{ "op": "deleteAnnot", "args": { "id": "a12.0" } }`.
796
+ *
797
+ * Added in 0.3.0.
798
+ */
799
+ export interface DeleteAnnot {
800
+ /**
801
+ * The annotation.
802
+ */
803
+ id: string;
804
+ }
805
+
806
+ /**
807
+ * Edits the Info dictionary: applies `patch` and, when `at` is given, sets `/ModDate` to it.
808
+ *
809
+ * **Deprecated:** use [`SetMetadata`](crate::SetMetadata), which writes authors as a list and
810
+ * the dates, and edits files whose metadata is in XMP alone. `SetInfo` stays, with its v1 wire
811
+ * form (`setInfo`) and meaning, so op logs already written still apply (ADR 0006 §6). It now
812
+ * goes through the same writer, so a document's XMP packet, when it has one, changes with it.
813
+ * It adds a packet only to a PDF 2.0 file, where the fields other than the dates belong in XMP.
814
+ * The date is part of the op, never read from a clock, so the same op gives the same bytes
815
+ * everywhere (ADR 0006 §1).
816
+ *
817
+ * An Info dictionary written directly in the trailer, or missing, becomes a new indirect
818
+ * object that the trailer's `/Info` points to (the first time the op changes something).
819
+ *
820
+ * Added in 0.2.0.
821
+ */
822
+ export interface SetInfo {
823
+ /**
824
+ * The entries to set or clear.
825
+ */
826
+ patch?: InfoPatch;
827
+ /**
828
+ * The modification date to record as `/ModDate`.
829
+ */
830
+ at?: string;
831
+ }
832
+
833
+ /**
834
+ * Edits the document's metadata: applies `patch` to the Info dictionary and the XMP packet
835
+ * together and, when `at` is given, records it as the modification date (`/ModDate`,
836
+ * `xmp:ModifyDate`) and as `xmp:MetadataDate`, unless the patch sets the modification date
837
+ * itself. The date is part of the op, never read from a clock (ADR 0006 §1).
838
+ *
839
+ * ```
840
+ * use laminapdf_ops::{MetadataPatch, Op, SetMetadata};
841
+ *
842
+ * let op = Op::from(SetMetadata::new(MetadataPatch::new().title(Some("Minutes")), Some("D:20261110093000Z".parse()?)));
843
+ * assert_eq!(
844
+ * serde_json::to_string(&op)?,
845
+ * r#"{"op":"setMetadata","args":{"patch":{"title":"Minutes"},"at":"D:20261110093000Z"}}"#
846
+ * );
847
+ * # Ok::<(), Box<dyn std::error::Error>>(())
848
+ * ```
849
+ *
850
+ * Added in 0.2.0.
851
+ */
852
+ export interface SetMetadata {
853
+ /**
854
+ * The fields to set or remove.
855
+ */
856
+ patch?: MetadataPatch;
857
+ /**
858
+ * The modification date the edit writes.
859
+ */
860
+ at?: string;
861
+ /**
862
+ * Replace an XMP packet that can't be read with a new one, losing whatever it held.
863
+ * Without it, such a packet makes the op fail with `malformed-xmp`, so nothing is lost
864
+ * without the user saying so.
865
+ */
866
+ replaceMalformed?: boolean;
867
+ }
868
+
869
+ /**
870
+ * How [`RotatePages`] reads its angle.
871
+ *
872
+ * Added in 0.3.0.
873
+ */
874
+ export type RotateMode = "set" | "turn";
875
+
876
+ /**
877
+ * How a destination shows its page (§12.3.2.2).
878
+ */
879
+ export type FitInit = { type: "page" } | { type: "width"; top?: number } | { type: "height"; left?: number } | { type: "xyz"; left?: number; top?: number; zoom?: number } | { type: "rect"; x: number; y: number; width: number; height: number } | { type: "bounds" };
880
+
881
+ /**
882
+ * How a destination shows its page (§12.3.2.2, Table 149), in the page's user space. A `None`
883
+ * coordinate keeps the viewer's current one (`null` in the file).
884
+ *
885
+ * Every number must be finite: the ops refuse any other with `view-not-finite`.
886
+ *
887
+ * Added in 0.3.0.
888
+ */
889
+ export type DestinationView = { kind: "xyz"; left: number | undefined; top: number | undefined; zoom: number | undefined } | { kind: "fit" } | { kind: "fitH"; top: number | undefined } | { kind: "fitV"; left: number | undefined } | { kind: "fitR"; left: number; bottom: number; right: number; top: number } | { kind: "fitB" } | { kind: "fitBH"; top: number | undefined } | { kind: "fitBV"; left: number | undefined };
890
+
891
+ /**
892
+ * How a merge brings in what the sources hold besides their pages.
893
+ *
894
+ * Added in 0.3.0.
895
+ */
896
+ export interface MergeOptions {
897
+ /**
898
+ * What becomes of the sources' bookmarks; `perFile` when left out.
899
+ */
900
+ outline?: OutlineMerge;
901
+ /**
902
+ * What happens to a form field whose name is already used; `rename` when left out.
903
+ */
904
+ fields?: FieldClash;
905
+ }
906
+
907
+ /**
908
+ * How a save would go (`laminapdf::PlannedSave`).
909
+ */
910
+ export interface PlannedSaveJs {
911
+ /**
912
+ * The mode the save resolves to; absent when it would be refused.
913
+ */
914
+ mode?: SaveModeJs;
915
+ /**
916
+ * The code the save would be refused with.
917
+ */
918
+ refusal?: string;
919
+ /**
920
+ * `free`, `appendOnly`, `requiresRewrite` or `conflict`.
921
+ */
922
+ constraint: string;
923
+ /**
924
+ * Every signature, in the order found.
925
+ */
926
+ signatures: SignatureJs[];
927
+ /**
928
+ * The certification's `/P` (1, 2 or 3), when the document is certified.
929
+ */
930
+ docMdp?: number;
931
+ /**
932
+ * Why only a full rewrite is safe: `repairedBase`, `encryptedBase`, `noBase`,
933
+ * `opRequiresRewrite`.
934
+ */
935
+ rewriteReasons: string[];
936
+ /**
937
+ * The pending changes a signature's restriction forbids.
938
+ */
939
+ violations: ViolationJs[];
940
+ /**
941
+ * The signatures the save would invalidate (only with `invalidateSignatures`).
942
+ */
943
+ wouldInvalidate: SignatureJs[];
944
+ /**
945
+ * Whether the save writes an encrypted document as plaintext.
946
+ */
947
+ removesEncryption: boolean;
948
+ /**
949
+ * What the save does with the save-time metadata: `none`, `stamp` or `skipped`.
950
+ */
951
+ metadata: string;
952
+ /**
953
+ * Whether an edit to save rewrote the XMP packet whole.
954
+ */
955
+ packetRewritten: boolean;
956
+ /**
957
+ * The PDF/A and PDF/UA claims the edited metadata breaks (warnings that never block).
958
+ */
959
+ conformanceWarnings: Diagnostic[];
960
+ /**
961
+ * The document has no changes.
962
+ */
963
+ unchanged: boolean;
964
+ }
965
+
966
+ /**
967
+ * How a viewer lays pages out.
968
+ */
969
+ export type PageLayoutInit = "singlePage" | "oneColumn" | "twoColumnLeft" | "twoColumnRight" | "twoPageLeft" | "twoPageRight";
970
+
971
+ /**
972
+ * How an attached file relates to the document (`/AFRelationship`).
973
+ */
974
+ export type AfRelationshipInit = "source" | "data" | "alternative" | "supplement" | "unspecified";
975
+
976
+ /**
977
+ * How an undo or redo went (`laminapdf::UndoOutcome`).
63
978
  */
64
- export type LabelStyleInit = "decimal" | "upperRoman" | "lowerRoman" | "upperLetters" | "lowerLetters";
979
+ export type UndoOutcomeJs = "applied" | "partial" | "skipped";
65
980
 
66
981
  /**
67
- * An attached file's options.
982
+ * How viewers present the document when it opens.
68
983
  */
69
- export interface AttachmentInit {
984
+ export interface ViewerPreferencesInit {
70
985
  /**
71
- * How the file relates to the document (default `unspecified`).
986
+ * Show the title, not the file name.
72
987
  */
73
- relationship?: AfRelationshipInit;
988
+ displayDocTitle?: boolean;
74
989
  /**
75
- * `/Desc`, shown next to the file.
990
+ * The panel shown.
76
991
  */
77
- description?: string;
992
+ pageMode?: PageModeInit;
78
993
  /**
79
- * When the file last changed, a PDF date (default: the document's creation date).
994
+ * The page layout.
80
995
  */
81
- modDate?: string;
996
+ pageLayout?: PageLayoutInit;
82
997
  }
83
998
 
84
999
  /**
85
- * An editing operation ([ADR 0006] §1): typed and semantic (it names things by
86
- * [`LaminaPdfId`](laminapdf_model::LaminaPdfId), never by object reference), serialisable, and free
87
- * of ambient inputs (a modification date is in the payload), so the same op on the same
88
- * document gives the same result on every platform.
89
- *
90
- * Apply it inside a transaction ([`Tx::apply`](crate::Tx::apply)); its JSON form is
91
- * adjacently tagged, `{ "op": "rotatePage", "args": { … } }`, and unknown ops and fields
92
- * are rejected, never skipped.
93
- *
94
- * ```
95
- * use laminapdf_ops::Op;
96
- *
97
- * let op: Op = serde_json::from_str(r#"{ "op": "rotatePage", "args": { "page": "p3.0", "rotate": 90 } }"#)?;
98
- * assert_eq!(serde_json::to_string(&op)?, r#"{"op":"rotatePage","args":{"page":"p3.0","rotate":90}}"#);
99
- * assert!(serde_json::from_str::<Op>(r#"{ "op": "explode", "args": {} }"#).is_err());
100
- * # Ok::<(), serde_json::Error>(())
101
- * ```
1000
+ * Inserts `count` blank pages of `size` at `at`, each showing at `rotate`. Each new page
1001
+ * gets a new object and [`PageId`] (ADR 0005 §5), an empty `/Resources` and no content; in
1002
+ * a labelled document they continue the label range they land in. A count outside 1 to
1003
+ * 10,000, or a side that isn't above 0 and at most 14,400 points, is
1004
+ * [`Error::InvalidOp`](crate::Error::InvalidOp).
102
1005
  *
103
- * [ADR 0006]: https://github.com/laudebugs/laminapdf/blob/main/docs/decisions/0006-op-log-and-undo.md
104
- */
105
- export type Op = { op: "setInfo"; args: SetInfo } | { op: "rotatePage"; args: RotatePage };
106
-
107
- /**
108
- * Changes to the Info dictionary's text entries. For each field, `None` leaves the entry
109
- * alone, `Some(None)` removes it and `Some(Some(text))` sets it (encoded as
110
- * PDFDocEncoding when it can be, else UTF-16BE, §7.9.2.2).
1006
+ * On the wire: `{ "op": "insertBlankPages", "args": { "at": { "after": "p3.0" }, "count": 2,
1007
+ * "size": { "width": 612, "height": 792 }, "rotate": 0 } }`.
111
1008
  *
112
- * On the wire an absent field is left alone and `null` clears: `{ "title": "Report",
113
- * "author": null }`.
1009
+ * Added in 0.3.0.
114
1010
  */
115
- export interface InfoPatch {
1011
+ export interface InsertBlankPages {
116
1012
  /**
117
- * `/Title`.
1013
+ * Where the pages go.
118
1014
  */
119
- title?: string | null;
1015
+ at: "start" | "end" | { before: string } | { after: string };
120
1016
  /**
121
- * `/Author`.
1017
+ * How many pages, at least 1.
122
1018
  */
123
- author?: string | null;
1019
+ count: number;
124
1020
  /**
125
- * `/Subject`.
1021
+ * Their `/MediaBox` size, in points: `{ "width": 612, "height": 792 }`.
126
1022
  */
127
- subject?: string | null;
1023
+ size: { width: number; height: number };
128
1024
  /**
129
- * `/Keywords`.
1025
+ * Their rotation in degrees clockwise: 0, 90, 180 or 270.
130
1026
  */
131
- keywords?: string | null;
1027
+ rotate: 0 | 90 | 180 | 270;
1028
+ }
1029
+
1030
+ /**
1031
+ * Merges source documents into this one at `at`, one after another, as one edit. Each
1032
+ * source's pages come in as [`InsertPagesFrom`](crate::InsertPagesFrom) brings them (their
1033
+ * annotations, links and the named destinations those use, form fields under the clash rule
1034
+ * of `options.fields`, signature fields left off, tags dropped), and with them what belongs to
1035
+ * the whole source:
1036
+ *
1037
+ * - its bookmarks, per `options.outline`, going to the copies of their pages; a bookmark whose
1038
+ * page didn't come along is left out unless one of its children came
1039
+ * ([`Applied::bookmarks_dropped`](crate::Applied::bookmarks_dropped));
1040
+ * - its layers (optional content groups the copied pages use), added to this document's list
1041
+ * in the source's order and visible or hidden as the source showed them;
1042
+ * - its page labels: each copy keeps the label it had in its source, and this document's
1043
+ * pages keep theirs (a document without labels numbers its pages from 1);
1044
+ * - its form's default appearance and `/NeedAppearances`, and its PDF version when newer.
1045
+ *
1046
+ * Named destinations that clash are renamed `<name>#<n>`
1047
+ * ([`Applied::renamed_destinations`](crate::Applied::renamed_destinations)).
1048
+ *
1049
+ * On the wire: `{ "op": "mergeDocuments", "args": { "sources": [{ "source": "<64 hex
1050
+ * digits>" }], "at": "end" } }`, with each source's `pages` and `title`, and `options`,
1051
+ * optional.
1052
+ *
1053
+ * Added in 0.3.0.
1054
+ */
1055
+ export interface MergeDocuments {
132
1056
  /**
133
- * `/Creator`: the application that created the original document.
1057
+ * The documents to bring in, in order.
134
1058
  */
135
- creator?: string | null;
1059
+ sources: MergeSource[];
136
1060
  /**
137
- * `/Producer`: the application that converted it to PDF.
1061
+ * Where their pages go.
138
1062
  */
139
- producer?: string | null;
1063
+ at: "start" | "end" | { before: string } | { after: string };
1064
+ /**
1065
+ * What comes along besides the pages.
1066
+ */
1067
+ options?: MergeOptions;
140
1068
  }
141
1069
 
142
1070
  /**
143
- * Edits the Info dictionary: applies `patch` and, when `at` is given, sets `/ModDate` to it.
144
- * The date is part of the op, never read from a clock, so the same op gives the same bytes
145
- * everywhere (ADR 0006 §1).
1071
+ * Moves `pages` to `to`. They keep their order among themselves (the order given, each page
1072
+ * once) and their IDs; an anchor that is itself moved is replaced by the nearest page that
1073
+ * stays.
146
1074
  *
147
- * An Info dictionary written directly in the trailer, or missing, becomes a new indirect
148
- * object that the trailer's `/Info` points to (the first time the op changes something).
1075
+ * On the wire: `{ "op": "movePages", "args": { "pages": ["p7.0", "p8.0"], "to": "start" } }`.
1076
+ *
1077
+ * Added in 0.3.0.
149
1078
  */
150
- export interface SetInfo {
1079
+ export interface MovePages {
151
1080
  /**
152
- * The entries to set or clear.
1081
+ * The pages, in the order they take at `to`.
153
1082
  */
154
- patch?: InfoPatch;
1083
+ pages: string[];
155
1084
  /**
156
- * The modification date to record as `/ModDate`.
1085
+ * Where they go.
157
1086
  */
158
- at?: string;
1087
+ to: "start" | "end" | { before: string } | { after: string };
159
1088
  }
160
1089
 
161
1090
  /**
162
- * How a destination shows its page (§12.3.2.2).
163
- */
164
- export type FitInit = { type: "page" } | { type: "width"; top?: number } | { type: "height"; left?: number } | { type: "xyz"; left?: number; top?: number; zoom?: number } | { type: "rect"; x: number; y: number; width: number; height: number } | { type: "bounds" };
165
-
166
- /**
167
- * How a viewer lays pages out.
168
- */
169
- export type PageLayoutInit = "singlePage" | "oneColumn" | "twoColumnLeft" | "twoColumnRight" | "twoPageLeft" | "twoPageRight";
170
-
171
- /**
172
- * How an attached file relates to the document (`/AFRelationship`).
1091
+ * Moves a bookmark, with its children, under `parent` (`null`: the top level) at `at`. The
1092
+ * item keeps its ID.
1093
+ *
1094
+ * On the wire: `{ "op": "moveOutlineItem", "args": { "item": "o12.0", "parent": "o9.0", "at":
1095
+ * { "after": "o10.0" } } }`.
1096
+ *
1097
+ * Added in 0.3.0.
173
1098
  */
174
- export type AfRelationshipInit = "source" | "data" | "alternative" | "supplement" | "unspecified";
1099
+ export interface MoveOutlineItem {
1100
+ /**
1101
+ * The item.
1102
+ */
1103
+ item: string;
1104
+ /**
1105
+ * The new parent; `None` for the top level.
1106
+ */
1107
+ parent?: string | null;
1108
+ /**
1109
+ * Where among the new parent's children.
1110
+ */
1111
+ at: "first" | "last" | { before: string } | { after: string };
1112
+ }
175
1113
 
176
1114
  /**
177
- * How an undo or redo went (`laminapdf::UndoOutcome`).
1115
+ * Object numbering in a full rewrite.
178
1116
  */
179
- export type UndoOutcomeJs = "applied" | "partial" | "skipped";
1117
+ export type RenumberJs = "keep" | "compact";
180
1118
 
181
1119
  /**
182
- * How viewers present the document when it opens.
1120
+ * One document a merge brings in.
1121
+ *
1122
+ * Added in 0.3.0.
183
1123
  */
184
- export interface ViewerPreferencesInit {
1124
+ export interface MergeSource {
185
1125
  /**
186
- * Show the title, not the file name.
1126
+ * The source document, added with [`Document::add_source`](crate::Document::add_source).
187
1127
  */
188
- displayDocTitle?: boolean;
1128
+ source: string;
189
1129
  /**
190
- * The panel shown.
1130
+ * The source's pages to bring in, by their IDs in the source, in order; all of them when
1131
+ * left out.
191
1132
  */
192
- pageMode?: PageModeInit;
1133
+ pages?: string[];
193
1134
  /**
194
- * The page layout.
1135
+ * The name of the bookmark [`OutlineMerge::PerFile`] gives the source's bookmarks (a file
1136
+ * name, say); the source's own title when left out.
195
1137
  */
196
- pageLayout?: PageLayoutInit;
1138
+ title?: string;
197
1139
  }
198
1140
 
199
1141
  /**
200
- * Object numbering in a full rewrite.
1142
+ * One entry of a language alternative, under the name TypeScript gives it.
201
1143
  */
202
- export type RenumberJs = "keep" | "compact";
1144
+ export interface XmpLangText {
1145
+ lang: string;
1146
+ text: string;
1147
+ }
203
1148
 
204
1149
  /**
205
1150
  * One outline item and its children.
@@ -287,6 +1232,59 @@ export interface PageLabelInit {
287
1232
  first?: number;
288
1233
  }
289
1234
 
1235
+ /**
1236
+ * One range of [`SetPageLabels`]: the labels from `page` up to the next range's page.
1237
+ *
1238
+ * Added in 0.3.0.
1239
+ */
1240
+ export interface PageLabelRange {
1241
+ /**
1242
+ * The range's first page.
1243
+ */
1244
+ page: string;
1245
+ /**
1246
+ * The numbering style; without one, each label is the prefix alone. On the wire:
1247
+ * `decimal`, `upperRoman`, `lowerRoman`, `upperLetters` or `lowerLetters`.
1248
+ */
1249
+ style?: "decimal" | "upperRoman" | "lowerRoman" | "upperLetters" | "lowerLetters";
1250
+ /**
1251
+ * Text before each number (`A-` in `A-1`).
1252
+ */
1253
+ prefix?: string;
1254
+ /**
1255
+ * The number of the range's first page, 1 or more (a `u32`, as the builder's page labels
1256
+ * take).
1257
+ */
1258
+ first?: number;
1259
+ }
1260
+
1261
+ /**
1262
+ * One reference to a deleted page and what the deletion did to it.
1263
+ */
1264
+ export interface ReferenceEditJs {
1265
+ /**
1266
+ * The deleted page it named.
1267
+ */
1268
+ page: string;
1269
+ /**
1270
+ * `outlineItem`, `link`, `namedDestination`, `openAction`, `structureElement`,
1271
+ * `structureKid` or `widget`.
1272
+ */
1273
+ kind: "outlineItem" | "link" | "namedDestination" | "openAction" | "structureElement" | "structureKid" | "widget";
1274
+ /**
1275
+ * The named destination's name, for a `namedDestination`.
1276
+ */
1277
+ name?: string;
1278
+ /**
1279
+ * `retargeted` or `removed`.
1280
+ */
1281
+ outcome: "retargeted" | "removed";
1282
+ /**
1283
+ * The surviving page it now names, when retargeted.
1284
+ */
1285
+ to?: string;
1286
+ }
1287
+
290
1288
  /**
291
1289
  * Options for `WasmDocument.openBytes`.
292
1290
  */
@@ -300,76 +1298,296 @@ export interface OpenOptionsInit {
300
1298
  */
301
1299
  strict?: boolean;
302
1300
  /**
303
- * Resource limits (ADR 0003 §3); each one left out keeps its default.
1301
+ * Resource limits (ADR 0003 §3); each one left out keeps its default.
1302
+ */
1303
+ limits?: LimitsInit;
1304
+ }
1305
+
1306
+ /**
1307
+ * Options for a new document: everything that would come from the environment is given,
1308
+ * so the same calls give the same bytes.
1309
+ */
1310
+ export interface BuildOptionsInit {
1311
+ /**
1312
+ * The header version (default 1.7).
1313
+ */
1314
+ version?: PdfVersionInit;
1315
+ /**
1316
+ * The first `/ID` entry: 16 bytes as 32 hex digits (default: a content hash).
1317
+ */
1318
+ id?: string;
1319
+ /**
1320
+ * `/Producer`.
1321
+ */
1322
+ producer?: string;
1323
+ /**
1324
+ * `/CreationDate`, a PDF date (`D:20261030120000Z`).
1325
+ */
1326
+ creationDate?: string;
1327
+ /**
1328
+ * Flate-compress content streams (default `true`).
1329
+ */
1330
+ compress?: boolean;
1331
+ /**
1332
+ * A tagged document (structure tree, `/MarkInfo`) even before any element (default
1333
+ * `false`; a page using `beginTag` or `beginArtifact` makes it tagged either way).
1334
+ */
1335
+ tagged?: boolean;
1336
+ /**
1337
+ * Conformance profiles: `PDF/UA-1` with at most one PDF/A profile. `finish` then fails
1338
+ * with a coded error for each machine-checkable rule the document breaks.
1339
+ */
1340
+ profiles?: ProfileInit[];
1341
+ }
1342
+
1343
+ /**
1344
+ * Options for saving.
1345
+ */
1346
+ export interface SaveOptionsInit {
1347
+ /**
1348
+ * The mode (default `auto`).
1349
+ */
1350
+ mode?: SaveModeJs;
1351
+ /**
1352
+ * Save even when it invalidates signatures (removing them from the output).
1353
+ */
1354
+ invalidateSignatures?: boolean;
1355
+ /**
1356
+ * Write an encrypted document as a plaintext full rewrite.
1357
+ */
1358
+ removeEncryption?: boolean;
1359
+ /**
1360
+ * What an incremental save returns.
1361
+ */
1362
+ output?: OutputJs;
1363
+ /**
1364
+ * Numbering in a full rewrite.
1365
+ */
1366
+ renumber?: RenumberJs;
1367
+ /**
1368
+ * The cross-reference form.
1369
+ */
1370
+ xref?: XrefJs;
1371
+ /**
1372
+ * When the save happens, as the app's clock reads it: a PDF date (`D:20261110093000Z`)
1373
+ * or an ISO 8601 date-time. A save that writes changes stamps it as the modification and
1374
+ * metadata dates and gives the XMP packet a new instance ID.
1375
+ */
1376
+ metadataDate?: string;
1377
+ /**
1378
+ * The XMP instance ID the save writes, a UUID (`uuid:` optional); without it, one is
1379
+ * derived from the content.
1380
+ */
1381
+ instanceId?: string;
1382
+ }
1383
+
1384
+ /**
1385
+ * Removes an XMP property, every copy of it in the packet; a property the Info dictionary
1386
+ * mirrors is removed from both. The value stays in the file's earlier revisions until a full
1387
+ * rewrite ([`Applied::still_in_earlier_revisions`](crate::Applied::still_in_earlier_revisions)).
1388
+ *
1389
+ * ```
1390
+ * use laminapdf_ops::{Op, RemoveXmpProperty};
1391
+ *
1392
+ * let op = Op::from(RemoveXmpProperty::new("http://example.com/ns/", "batch"));
1393
+ * assert_eq!(
1394
+ * serde_json::to_string(&op)?,
1395
+ * r#"{"op":"removeXmpProperty","args":{"namespace":"http://example.com/ns/","name":"batch"}}"#
1396
+ * );
1397
+ * # Ok::<(), serde_json::Error>(())
1398
+ * ```
1399
+ *
1400
+ * Added in 0.3.0.
1401
+ */
1402
+ export interface RemoveXmpProperty {
1403
+ /**
1404
+ * The property's namespace URI.
1405
+ */
1406
+ namespace: string;
1407
+ /**
1408
+ * The property's local name.
1409
+ */
1410
+ name: string;
1411
+ /**
1412
+ * As [`SetMetadata::replace_malformed`](crate::SetMetadata::replace_malformed).
1413
+ */
1414
+ replaceMalformed?: boolean;
1415
+ }
1416
+
1417
+ /**
1418
+ * Removes metadata fields from the Info dictionary and the XMP packet; the packet's other
1419
+ * properties stay (J.10).
1420
+ *
1421
+ * The values stay in the file's earlier revisions until a full rewrite: an incremental save
1422
+ * appends the change and keeps the original bytes. When it removed anything, the op's result
1423
+ * says so ([`Applied::still_in_earlier_revisions`](crate::Applied::still_in_earlier_revisions)),
1424
+ * so the caller can offer a full rewrite (on a signed file, the override that removes the
1425
+ * signatures).
1426
+ *
1427
+ * ```
1428
+ * use laminapdf_ops::{MetadataKey, Op, RemoveMetadata};
1429
+ *
1430
+ * let op = Op::from(RemoveMetadata::new(vec![MetadataKey::Authors, MetadataKey::Creator]));
1431
+ * assert_eq!(serde_json::to_string(&op)?, r#"{"op":"removeMetadata","args":{"keys":["authors","creator"]}}"#);
1432
+ * # Ok::<(), serde_json::Error>(())
1433
+ * ```
1434
+ *
1435
+ * Added in 0.2.0.
1436
+ */
1437
+ export interface RemoveMetadata {
1438
+ /**
1439
+ * The fields to remove.
1440
+ */
1441
+ keys: MetadataKey[];
1442
+ /**
1443
+ * As [`SetMetadata::replace_malformed`].
304
1444
  */
305
- limits?: LimitsInit;
1445
+ replaceMalformed?: boolean;
306
1446
  }
307
1447
 
308
1448
  /**
309
- * Options for a new document: everything that would come from the environment is given,
310
- * so the same calls give the same bytes.
1449
+ * Renames a named destination, keeping it where it is. With `links: follow` (the default) the
1450
+ * bookmarks, links and `GoTo` actions of this file that name it are renamed too, in the form
1451
+ * they named it in.
1452
+ *
1453
+ * On the wire: `{ "op": "renameNamedDestination", "args": { "from": "sec2", "to": "methods" } }`.
1454
+ *
1455
+ * Added in 0.3.0.
311
1456
  */
312
- export interface BuildOptionsInit {
1457
+ export interface RenameNamedDestination {
313
1458
  /**
314
- * The header version (default 1.7).
1459
+ * The current name.
315
1460
  */
316
- version?: PdfVersionInit;
1461
+ from: string;
317
1462
  /**
318
- * The first `/ID` entry: 16 bytes as 32 hex digits (default: a content hash).
1463
+ * The new name; no destination may have it yet.
319
1464
  */
320
- id?: string;
1465
+ to: string;
321
1466
  /**
322
- * `/Producer`.
1467
+ * Whether the uses follow.
323
1468
  */
324
- producer?: string;
1469
+ links?: RenameLinks;
1470
+ }
1471
+
1472
+ /**
1473
+ * Replaces pages of this document with pages of a source document, pair by pair: the first
1474
+ * source page takes the first target's place, and so on. What pointed at a replaced page
1475
+ * (outline items, links, named destinations, `GoTo` actions, `/OpenAction`) now points at
1476
+ * the page that took its place, with the view `/Fit`, since coordinates on the old page mean
1477
+ * nothing on the new one. Targets left over are deleted (their references retargeted to the
1478
+ * nearest surviving page); source pages left over go after the last copy. The copies come
1479
+ * in as with [`InsertPagesFrom`]; the replaced pages are deleted as with
1480
+ * [`DeletePages`](crate::DeletePages), structure, widgets and earlier revisions included.
1481
+ *
1482
+ * On the wire: `{ "op": "replacePages", "args": { "targets": ["p7.0"], "source": "<64 hex
1483
+ * digits>", "pages": ["p3.0"] } }`, with `carry` optional.
1484
+ *
1485
+ * Added in 0.3.0.
1486
+ */
1487
+ export interface ReplacePages {
325
1488
  /**
326
- * `/CreationDate`, a PDF date (`D:20261030120000Z`).
1489
+ * The pages to replace, in order.
327
1490
  */
328
- creationDate?: string;
1491
+ targets: string[];
329
1492
  /**
330
- * Flate-compress content streams (default `true`).
1493
+ * The source document.
331
1494
  */
332
- compress?: boolean;
1495
+ source: string;
333
1496
  /**
334
- * A tagged document (structure tree, `/MarkInfo`) even before any element (default
335
- * `false`; a page using `beginTag` or `beginArtifact` makes it tagged either way).
1497
+ * The source's pages that take their places, by their IDs in the source.
336
1498
  */
337
- tagged?: boolean;
1499
+ pages: string[];
338
1500
  /**
339
- * Conformance profiles: `PDF/UA-1` with at most one PDF/A profile. `finish` then fails
340
- * with a coded error for each machine-checkable rule the document breaks.
1501
+ * What comes along with the pages.
341
1502
  */
342
- profiles?: ProfileInit[];
1503
+ carry?: Carry;
343
1504
  }
344
1505
 
345
1506
  /**
346
- * Options for saving.
1507
+ * Replaces the document's page labels with `ranges`; an empty list removes them, so viewers
1508
+ * show page numbers again.
1509
+ *
1510
+ * Each range starts at a page, named by its ID, and labels that page and the ones after it up
1511
+ * to the next range. The ranges must be in page order, two never on one page, the first on
1512
+ * the document's first page (the number tree must have an entry for page 0, §12.4.2), and
1513
+ * each numbered from 1 or more; otherwise the op fails with `invalid-label-ranges`. Nothing
1514
+ * is written when the catalog already holds exactly these labels
1515
+ * ([`PageLabels::is_written`]); any other tree, a damaged one included, is replaced.
1516
+ *
1517
+ * On the wire:
1518
+ * `{ "op": "setPageLabels", "args": { "ranges": [{ "page": "p3.0", "style": "lowerRoman" }] } }`.
1519
+ *
1520
+ * Added in 0.3.0.
347
1521
  */
348
- export interface SaveOptionsInit {
1522
+ export interface SetPageLabels {
349
1523
  /**
350
- * The mode (default `auto`).
1524
+ * The ranges, in page order.
351
1525
  */
352
- mode?: SaveModeJs;
1526
+ ranges: PageLabelRange[];
1527
+ }
1528
+
1529
+ /**
1530
+ * Replaces the whole XMP packet with `xml`, for what the property ops can't write
1531
+ * (structures, qualifiers, comments). The packet is parsed before it applies and refused if it
1532
+ * doesn't parse (`invalid-xmp-packet`) or changes the PDF/A or PDF/UA identification
1533
+ * (`xmp-claim-read-only`). Info's mirrored entries follow the new packet; in a file that
1534
+ * claims PDF/A, the extension schema entries its properties lack are added and reported
1535
+ * ([`Applied::declared`](crate::Applied::declared)). The old packet stays in the file's
1536
+ * earlier revisions until a full rewrite.
1537
+ *
1538
+ * ```
1539
+ * use laminapdf_ops::{Op, ReplaceXmpPacket};
1540
+ *
1541
+ * let op = Op::from(ReplaceXmpPacket::new("<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">…</x:xmpmeta>"));
1542
+ * assert!(serde_json::to_string(&op)?.starts_with(r#"{"op":"replaceXmpPacket","args":{"xml":"<x:xmpmeta"#));
1543
+ * # Ok::<(), serde_json::Error>(())
1544
+ * ```
1545
+ *
1546
+ * Added in 0.3.0.
1547
+ */
1548
+ export interface ReplaceXmpPacket {
353
1549
  /**
354
- * Save even when it invalidates signatures (removing them from the output).
1550
+ * The new packet's XML, with or without its `<?xpacket … ?>` wrapper.
355
1551
  */
356
- invalidateSignatures?: boolean;
1552
+ xml: string;
1553
+ }
1554
+
1555
+ /**
1556
+ * Reverses the order of `pages` within the places they hold: the first of them (in page
1557
+ * order) takes the last one's place, and so on; the other pages don't move.
1558
+ *
1559
+ * On the wire: `{ "op": "reversePages", "args": { "pages": ["p3.0", "p4.0", "p5.0"] } }`.
1560
+ *
1561
+ * Added in 0.3.0.
1562
+ */
1563
+ export interface ReversePages {
357
1564
  /**
358
- * Write an encrypted document as a plaintext full rewrite.
1565
+ * The pages; their order here doesn't matter.
359
1566
  */
360
- removeEncryption?: boolean;
1567
+ pages: string[];
1568
+ }
1569
+
1570
+ /**
1571
+ * Rotates `pages` by `angle`, set or turned ([`RotateMode`]); each page as
1572
+ * [`RotatePage`] does, keeping its object and [`PageId`].
1573
+ *
1574
+ * On the wire: `{ "op": "rotatePages", "args": { "pages": ["p3.0"], "angle": 90, "mode": "turn" } }`.
1575
+ *
1576
+ * Added in 0.3.0.
1577
+ */
1578
+ export interface RotatePages {
361
1579
  /**
362
- * What an incremental save returns.
1580
+ * The pages.
363
1581
  */
364
- output?: OutputJs;
1582
+ pages: string[];
365
1583
  /**
366
- * Numbering in a full rewrite.
1584
+ * The angle in degrees clockwise: 0, 90, 180 or 270.
367
1585
  */
368
- renumber?: RenumberJs;
1586
+ angle: 0 | 90 | 180 | 270;
369
1587
  /**
370
- * The cross-reference form.
1588
+ * Whether the angle is set or added; `set` when left out.
371
1589
  */
372
- xref?: XrefJs;
1590
+ mode?: RotateMode;
373
1591
  }
374
1592
 
375
1593
  /**
@@ -384,6 +1602,8 @@ export interface SaveOptionsInit {
384
1602
  * On the wire: `{ "op": "rotatePage", "args": { "page": "p12.0", "rotate": 90 } }`.
385
1603
  *
386
1604
  * [ADR 0005]: https://github.com/laudebugs/laminapdf/blob/main/docs/decisions/0005-document-model.md
1605
+ *
1606
+ * Added in 0.2.0.
387
1607
  */
388
1608
  export interface RotatePage {
389
1609
  /**
@@ -396,6 +1616,60 @@ export interface RotatePage {
396
1616
  rotate: 0 | 90 | 180 | 270;
397
1617
  }
398
1618
 
1619
+ /**
1620
+ * Sets any XMP property: text, a URI, a number, a boolean, a date, a language alternative,
1621
+ * or an ordered or unordered array of simple values. A file without a packet gets one.
1622
+ *
1623
+ * `prefix` names the namespace when the packet binds no prefix to it. A property the Info
1624
+ * dictionary mirrors (`dc:title`, `pdf:Producer`, …) is set in both, as
1625
+ * [`SetMetadata`](crate::SetMetadata) sets it, and must have its shape: text, or a language
1626
+ * alternative of the `x-default` entry alone, for `dc:title` and `dc:description`; an array of
1627
+ * text for `dc:creator`; a date for the dates.
1628
+ *
1629
+ * ```
1630
+ * use laminapdf_ops::{Op, SetXmpProperty};
1631
+ * use laminapdf_model::xmp::XmpValue;
1632
+ *
1633
+ * let op = Op::from(SetXmpProperty::new("http://example.com/ns/", "acme", "batch", XmpValue::Integer(42)));
1634
+ * assert_eq!(
1635
+ * serde_json::to_string(&op)?,
1636
+ * r#"{"op":"setXmpProperty","args":{"namespace":"http://example.com/ns/","prefix":"acme","name":"batch","value":{"type":"integer","value":42}}}"#
1637
+ * );
1638
+ * # Ok::<(), serde_json::Error>(())
1639
+ * ```
1640
+ *
1641
+ * Added in 0.3.0.
1642
+ */
1643
+ export interface SetXmpProperty {
1644
+ /**
1645
+ * The property's namespace URI.
1646
+ */
1647
+ namespace: string;
1648
+ /**
1649
+ * The prefix to write the namespace with when the packet binds none.
1650
+ */
1651
+ prefix: string;
1652
+ /**
1653
+ * The property's local name.
1654
+ */
1655
+ name: string;
1656
+ /**
1657
+ * The new value.
1658
+ */
1659
+ value: XmpValue;
1660
+ /**
1661
+ * As [`SetMetadata::replace_malformed`](crate::SetMetadata::replace_malformed).
1662
+ */
1663
+ replaceMalformed?: boolean;
1664
+ }
1665
+
1666
+ /**
1667
+ * Something in the file that names a destination.
1668
+ *
1669
+ * Added in 0.3.0.
1670
+ */
1671
+ export type DestinationUse = { kind: "outlineItem"; item: string } | { kind: "annotation"; page: string; annot: string } | { kind: "catalog" };
1672
+
399
1673
  /**
400
1674
  * The Info dictionary's text entries; absent ones are left out (document text).
401
1675
  */
@@ -474,14 +1748,50 @@ export interface MetadataInit {
474
1748
  modDate?: string;
475
1749
  }
476
1750
 
1751
+ /**
1752
+ * The document's XMP packet as text (`laminapdf::XmpText`).
1753
+ */
1754
+ export interface XmpTextJs {
1755
+ /**
1756
+ * Whether there is a packet, and whether it was read.
1757
+ */
1758
+ status: PacketStatus;
1759
+ /**
1760
+ * The packet's XML, with the edits made so far.
1761
+ */
1762
+ xml?: string;
1763
+ /**
1764
+ * The PDF/A part and conformance the packet claims, e.g. `"2B"`.
1765
+ */
1766
+ pdfa?: string;
1767
+ }
1768
+
477
1769
  /**
478
1770
  * The kind of change an op makes. The save planner checks it against `DocMDP` / `FieldMDP`
479
1771
  * permissions and the document's `SaveConstraint` ([ADR 0007]).
480
1772
  *
481
1773
  * [ADR 0007]: https://github.com/laudebugs/laminapdf/blob/main/docs/decisions/0007-save-planner.md
1774
+ *
1775
+ * Added in 0.2.0.
482
1776
  */
483
1777
  export type ModificationClass = "metadata" | "formFill" | "annotation" | "signature" | "pageStructure" | "content" | "document";
484
1778
 
1779
+ /**
1780
+ * The page and zoom a document opens at.
1781
+ *
1782
+ * Added in 0.3.0.
1783
+ */
1784
+ export interface OpenAt {
1785
+ /**
1786
+ * The page.
1787
+ */
1788
+ page: string;
1789
+ /**
1790
+ * The zoom.
1791
+ */
1792
+ zoom?: "default" | "fitPage" | "fitWidth" | number;
1793
+ }
1794
+
485
1795
  /**
486
1796
  * The pages an edit touched.
487
1797
  */
@@ -490,11 +1800,80 @@ export interface EditedJs {
490
1800
  * IDs of the pages whose content or attributes changed.
491
1801
  */
492
1802
  touchedPages: string[];
1803
+ /**
1804
+ * A metadata edit rewrote the XMP packet whole (UTF-16, or marked read-only).
1805
+ */
1806
+ packetRewritten: boolean;
1807
+ /**
1808
+ * A metadata edit removed values the file's earlier revisions still hold, which only a
1809
+ * full rewrite drops.
1810
+ */
1811
+ stillInEarlierRevisions: boolean;
1812
+ /**
1813
+ * IDs of annotations an annotation op deleted besides the one it names (Popups, replies,
1814
+ * the rest of a group); left out when there are none.
1815
+ */
1816
+ alsoDeleted?: string[];
493
1817
  /**
494
1818
  * Whether the edit changed the document: one that only set values they already had leaves
495
1819
  * no step to undo.
496
1820
  */
497
1821
  changed: boolean;
1822
+ /**
1823
+ * The IDs of the bookmarks the edit added, in order.
1824
+ */
1825
+ createdOutlineItems: string[];
1826
+ /**
1827
+ * An outline edit found the outline's links broken and rewrote them whole; bookmarks
1828
+ * stored inside others have new IDs.
1829
+ */
1830
+ outlineRepaired: boolean;
1831
+ /**
1832
+ * What still names a named destination the edit deleted.
1833
+ */
1834
+ destinationUses: DestinationUse[];
1835
+ /**
1836
+ * The XMP properties the edit declared in a PDF/A file's extension schemas.
1837
+ */
1838
+ declared: ExtensionPropertyJs[];
1839
+ /**
1840
+ * The page IDs in order after the edit, when it changed the page list.
1841
+ */
1842
+ pageOrder?: string[];
1843
+ /**
1844
+ * What a page deletion did to the references to the deleted pages.
1845
+ */
1846
+ references: ReferenceEditJs[];
1847
+ /**
1848
+ * What a page duplication did to the form fields whose widgets the copied pages show;
1849
+ * left out when there are none.
1850
+ */
1851
+ fields?: FieldEditJs[];
1852
+ /**
1853
+ * The copies a page duplication made whose tags couldn't all be cloned; left out when
1854
+ * there are none.
1855
+ */
1856
+ tagsIncomplete?: string[];
1857
+ /**
1858
+ * The page holding each link a page copied in from another document lost, one entry per
1859
+ * link; left out when there are none.
1860
+ */
1861
+ linksRemoved?: string[];
1862
+ /**
1863
+ * The pages copied in from another document without the tags their source gave them; left
1864
+ * out when there are none.
1865
+ */
1866
+ tagsDropped?: string[];
1867
+ /**
1868
+ * The named destinations a merge or a page import brought in under another name, because
1869
+ * the document already used theirs; left out when there are none.
1870
+ */
1871
+ renamedDestinations?: RenamedDestinationJs[];
1872
+ /**
1873
+ * The bookmarks a merge left out because the pages they went to didn't come along; left
1874
+ * out when there are none.
1875
+ */
1876
+ bookmarksDropped?: number;
498
1877
  }
499
1878
 
500
1879
  /**
@@ -507,6 +1886,52 @@ export type PageModeInit = "none" | "outlines" | "thumbnails" | "fullScreen" | "
507
1886
  */
508
1887
  export type SaveModeJs = "auto" | "incremental" | "fullRewrite";
509
1888
 
1889
+ /**
1890
+ * The wire form, under the name TypeScript gives it.
1891
+ */
1892
+ export type XmpValue = { type: "text"; value: string } | { type: "uri"; value: string } | { type: "integer"; value: number } | { type: "real"; value: number } | { type: "boolean"; value: boolean } | { type: "date"; value: string } | { type: "langAlt"; value: XmpLangText[] } | { type: "seq"; value: XmpValue[] } | { type: "bag"; value: XmpValue[] };
1893
+
1894
+ /**
1895
+ * What [`DeleteOutlineItem`](super::DeleteOutlineItem) does with the item's children.
1896
+ *
1897
+ * Added in 0.3.0.
1898
+ */
1899
+ export type ChildItems = "delete" | "promote";
1900
+
1901
+ /**
1902
+ * What [`DeletePages`] does with a destination that names a deleted page.
1903
+ *
1904
+ * Added in 0.3.0.
1905
+ */
1906
+ export type ReferencePolicy = "retarget" | "remove";
1907
+
1908
+ /**
1909
+ * What a page duplication or a page copied in did to one form field.
1910
+ */
1911
+ export interface FieldEditJs {
1912
+ /**
1913
+ * The field on the source page (in the source document, for a page copied in).
1914
+ */
1915
+ field: string;
1916
+ /**
1917
+ * The copy whose widget it concerns.
1918
+ */
1919
+ page: string;
1920
+ /**
1921
+ * `renamed` (the copy's widget belongs to a new field), `linked` (it joined the field of
1922
+ * the same name) or `notCopied` (a signature's widget).
1923
+ */
1924
+ outcome: "renamed" | "linked" | "notCopied";
1925
+ /**
1926
+ * The new field's ID, when renamed; the field it joined, when linked.
1927
+ */
1928
+ to?: string;
1929
+ /**
1930
+ * The new field's fully qualified name, when renamed.
1931
+ */
1932
+ name?: string;
1933
+ }
1934
+
510
1935
  /**
511
1936
  * What a save wrote (`laminapdf::SaveReport`).
512
1937
  */
@@ -535,6 +1960,24 @@ export interface SaveReportJs {
535
1960
  * Whether encryption was removed.
536
1961
  */
537
1962
  encryptionRemoved: boolean;
1963
+ /**
1964
+ * Whether the save stamped the save-time metadata (`metadataDate`, `instanceId`).
1965
+ */
1966
+ metadataUpdated: boolean;
1967
+ /**
1968
+ * Whether the options asked for the stamp and the save left it out, because it would have
1969
+ * broken a signature's restriction the saved changes keep.
1970
+ */
1971
+ metadataNotUpdated: boolean;
1972
+ /**
1973
+ * Whether an edit saved here rewrote the XMP packet whole.
1974
+ */
1975
+ packetRewritten: boolean;
1976
+ /**
1977
+ * Whether the save emptied the undo history (a full rewrite, or an incremental save that
1978
+ * freed objects); for a copy, whether adopting it would.
1979
+ */
1980
+ historyCleared: boolean;
538
1981
  }
539
1982
 
540
1983
  /**
@@ -556,11 +1999,73 @@ export interface RevertedJs {
556
1999
  touchedPages: string[];
557
2000
  }
558
2001
 
2002
+ /**
2003
+ * What becomes of the sources' outlines (bookmarks) in a merge.
2004
+ *
2005
+ * Added in 0.3.0.
2006
+ */
2007
+ export type OutlineMerge = "perFile" | "keep" | "none";
2008
+
2009
+ /**
2010
+ * What comes along with pages copied from another document.
2011
+ *
2012
+ * Added in 0.3.0.
2013
+ */
2014
+ export interface Carry {
2015
+ /**
2016
+ * Whether the pages' annotations other than form fields come along (links, comments,
2017
+ * markup); `true` when left out.
2018
+ */
2019
+ annotations?: boolean;
2020
+ /**
2021
+ * What happens to a form field whose name the target already uses; `rename` when left
2022
+ * out.
2023
+ */
2024
+ fields?: FieldClash;
2025
+ }
2026
+
2027
+ /**
2028
+ * What happens to a copied form field whose fully qualified name the target already uses
2029
+ * (E2 plan §11).
2030
+ *
2031
+ * Added in 0.3.0.
2032
+ */
2033
+ export type FieldClash = "rename" | "link";
2034
+
2035
+ /**
2036
+ * What the document's `/Metadata` holds.
2037
+ *
2038
+ * Added in 0.2.0.
2039
+ */
2040
+ export type PacketStatus = "absent" | "parsed" | "malformed";
2041
+
2042
+ /**
2043
+ * Where a bookmark goes.
2044
+ *
2045
+ * Added in 0.3.0.
2046
+ */
2047
+ export type OutlineTarget = { destination: ExplicitDestination } | { named: string } | { uri: string };
2048
+
559
2049
  /**
560
2050
  * Where a link or outline item goes.
561
2051
  */
562
2052
  export type LinkTargetInit = { type: "uri"; uri: string } | { type: "page"; index: number; fit?: FitInit } | { type: "named"; name: string };
563
2053
 
2054
+ /**
2055
+ * Whether [`RenameNamedDestination`](super::RenameNamedDestination) renames the uses of the
2056
+ * name in this file too.
2057
+ *
2058
+ * Added in 0.3.0.
2059
+ */
2060
+ export type RenameLinks = "keep" | "follow";
2061
+
2062
+ /**
2063
+ * Which side [`DocumentMetadata`]'s values come from.
2064
+ *
2065
+ * Added in 0.2.0.
2066
+ */
2067
+ export type MetadataSource = "info" | "xmp";
2068
+
564
2069
  /**
565
2070
  * `Limits` with every field optional (ADR 0003 §3; names as in Rust, camelCase).
566
2071
  */
@@ -715,6 +2220,24 @@ export class WasmDocument {
715
2220
  private constructor();
716
2221
  free(): void;
717
2222
  [Symbol.dispose](): void;
2223
+ /**
2224
+ * Adds another PDF as a source document: `{ id, pages }`, its ID (64 hex digits, the
2225
+ * SHA-256 of `bytes`) and its page IDs in order. An encrypted one opens with `password`,
2226
+ * which no op ever holds.
2227
+ *
2228
+ * # Errors
2229
+ * `wrong-password`, `password-required`, and the errors of a file that doesn't open.
2230
+ */
2231
+ addSource(bytes: Uint8Array, password?: string | null): { id: string; pages: string[] };
2232
+ /**
2233
+ * Makes `bytes`, the whole output of `saveCopy`, the document's saved file (it rebases onto
2234
+ * them), once they are written where the document lives; `historyCleared` is the copy's
2235
+ * report's.
2236
+ *
2237
+ * # Errors
2238
+ * `nested-transaction`, `saved-not-rebased`; `not-rebased` when `bytes` can't be read.
2239
+ */
2240
+ adoptCopy(bytes: Uint8Array, historyCleared: boolean): void;
718
2241
  /**
719
2242
  * Applies an op log (`{ "v": 1, "ops": [ … ] }`) as one transaction.
720
2243
  *
@@ -730,6 +2253,14 @@ export class WasmDocument {
730
2253
  * error (nothing is applied).
731
2254
  */
732
2255
  apply(ops: Op[]): EditedJs;
2256
+ /**
2257
+ * A new document of `pages` of this one, in the order given.
2258
+ *
2259
+ * # Errors
2260
+ * `invalid-argument` for a value that isn't a list of page IDs, `unknown-page`; reading
2261
+ * errors.
2262
+ */
2263
+ extractPages(pages: string[]): WasmDocument;
733
2264
  /**
734
2265
  * Whether there are changes no save has written yet.
735
2266
  */
@@ -745,6 +2276,24 @@ export class WasmDocument {
745
2276
  * Whether the file is encrypted (it opened with its password, or the empty one).
746
2277
  */
747
2278
  isEncrypted(): boolean;
2279
+ /**
2280
+ * A new document of `inputs` (each `{ bytes, password?, title? }`) one after another,
2281
+ * with their bookmarks, named destinations, form fields, layers and page labels; `options`
2282
+ * as `mergeDocuments`' (`outline`, `fields`).
2283
+ *
2284
+ * # Errors
2285
+ * `invalid-argument` / `unknown-option` for bad values, `wrong-password`,
2286
+ * `password-required`, and the errors of a file that doesn't open.
2287
+ */
2288
+ static merge(inputs: { bytes: Uint8Array; password?: string; title?: string }[], options: MergeOptions | undefined): WasmDocument;
2289
+ /**
2290
+ * The document's metadata, read from Info and XMP together, with the fields where they
2291
+ * disagree.
2292
+ *
2293
+ * # Errors
2294
+ * Reading errors.
2295
+ */
2296
+ metadata(): DocumentMetadata;
748
2297
  /**
749
2298
  * Opens a document from its bytes.
750
2299
  *
@@ -797,6 +2346,13 @@ export class WasmDocument {
797
2346
  * `page-index-out-of-range`; reading errors.
798
2347
  */
799
2348
  page(index: number): WasmPage;
2349
+ /**
2350
+ * How a save with `options` would go, without saving.
2351
+ *
2352
+ * # Errors
2353
+ * `invalid-argument` / `unknown-option` for bad options; reading errors.
2354
+ */
2355
+ plan(options: SaveOptionsInit | undefined): PlannedSaveJs;
800
2356
  /**
801
2357
  * Rebases onto `bytes`, the output of a save whose rebase failed (`not-rebased`).
802
2358
  *
@@ -812,6 +2368,14 @@ export class WasmDocument {
812
2368
  * As [`WasmDocument::undo`].
813
2369
  */
814
2370
  redo(): RevertedJs | undefined;
2371
+ /**
2372
+ * Writes what `save` would and leaves the document as it was: not rebased, its changes
2373
+ * still unsaved, the save-time stamp in the copy only (Save a copy).
2374
+ *
2375
+ * # Errors
2376
+ * As `save`; the document is unchanged either way.
2377
+ */
2378
+ saveCopy(options: SaveOptionsInit | undefined): { bytes: Uint8Array<ArrayBuffer>; report: SaveReportJs };
815
2379
  /**
816
2380
  * A save as a job, stepped with a budget of work units (ADR 0028 §3, §4).
817
2381
  *
@@ -826,6 +2390,17 @@ export class WasmDocument {
826
2390
  * The save's errors (`would-invalidate-signatures`, `requires-rewrite`, …).
827
2391
  */
828
2392
  save(options: SaveOptionsInit | undefined): { bytes: Uint8Array<ArrayBuffer>; report: SaveReportJs };
2393
+ /**
2394
+ * This document cut into new ones: `{ count: n }` parts of `n` pages, `{ ranges: [[id, …],
2395
+ * …] }` one part per list of page IDs, or `{ bookmarks: level }` a part from each bookmark
2396
+ * at that depth (1: the top level). Each part carries what its pages reach and the
2397
+ * bookmarks that go to them.
2398
+ *
2399
+ * # Errors
2400
+ * `invalid-argument` / `unknown-option` for a value that isn't one of those, `unknown-page`,
2401
+ * `no-pages-left` for an empty list; reading errors.
2402
+ */
2403
+ split(by: { readonly count: number } | { readonly ranges: readonly (readonly string[])[] } | { readonly bookmarks: number }): WasmDocument[];
829
2404
  /**
830
2405
  * Undoes the newest transaction: how it went and the pages it touched; `undefined` when
831
2406
  * there is nothing to undo.
@@ -838,6 +2413,20 @@ export class WasmDocument {
838
2413
  * A cursor over the warnings so far (build warnings first), each as the §1 object.
839
2414
  */
840
2415
  warnings(): WasmWarningCursor;
2416
+ /**
2417
+ * The signature restrictions `op` would break if it were applied and saved.
2418
+ *
2419
+ * # Errors
2420
+ * `invalid-argument` for a value that isn't an op; reading errors.
2421
+ */
2422
+ wouldViolate(op: Op): ViolationJs[];
2423
+ /**
2424
+ * The document's XMP packet as text, with the edits made so far.
2425
+ *
2426
+ * # Errors
2427
+ * Reading errors.
2428
+ */
2429
+ xmpPacket(): XmpTextJs;
841
2430
  }
842
2431
 
843
2432
  /**
@@ -1205,11 +2794,16 @@ export interface InitOutput {
1205
2794
  readonly wasmbuilder_page: (a: number, b: number, c: number) => number;
1206
2795
  readonly wasmbuilder_pageLabels: (a: number, b: any) => [number, number];
1207
2796
  readonly wasmbuilder_viewerPreferences: (a: number, b: any) => [number, number];
2797
+ readonly wasmdocument_addSource: (a: number, b: number, c: number, d: number, e: number) => [number, number, number];
2798
+ readonly wasmdocument_adoptCopy: (a: number, b: number, c: number, d: number) => [number, number];
1208
2799
  readonly wasmdocument_apply: (a: number, b: any) => [number, number, number];
1209
2800
  readonly wasmdocument_applyLog: (a: number, b: number, c: number) => [number, number, number];
2801
+ readonly wasmdocument_extractPages: (a: number, b: any) => [number, number, number];
1210
2802
  readonly wasmdocument_hasUnsavedChanges: (a: number) => number;
1211
2803
  readonly wasmdocument_info: (a: number) => [number, number, number];
1212
2804
  readonly wasmdocument_isEncrypted: (a: number) => number;
2805
+ readonly wasmdocument_merge: (a: any, b: any) => [number, number, number];
2806
+ readonly wasmdocument_metadata: (a: number) => [number, number, number];
1213
2807
  readonly wasmdocument_openBytes: (a: number, b: number, c: any) => [number, number, number];
1214
2808
  readonly wasmdocument_openSource: (a: number, b: any) => [number, number, number];
1215
2809
  readonly wasmdocument_page: (a: number, b: number) => [number, number, number];
@@ -1217,12 +2811,17 @@ export interface InitOutput {
1217
2811
  readonly wasmdocument_pageCount: (a: number) => [number, number, number];
1218
2812
  readonly wasmdocument_pageInfo: (a: number, b: number) => [number, number, number];
1219
2813
  readonly wasmdocument_pageInfos: (a: number) => [number, number, number];
2814
+ readonly wasmdocument_plan: (a: number, b: any) => [number, number, number];
1220
2815
  readonly wasmdocument_rebase: (a: number, b: number, c: number) => [number, number];
1221
2816
  readonly wasmdocument_redo: (a: number) => [number, number, number];
1222
2817
  readonly wasmdocument_save: (a: number, b: any) => [number, number, number];
2818
+ readonly wasmdocument_saveCopy: (a: number, b: any) => [number, number, number];
1223
2819
  readonly wasmdocument_saveJob: (a: number, b: any) => [number, number, number];
2820
+ readonly wasmdocument_split: (a: number, b: any) => [number, number, number];
1224
2821
  readonly wasmdocument_undo: (a: number) => [number, number, number];
1225
2822
  readonly wasmdocument_warnings: (a: number) => number;
2823
+ readonly wasmdocument_wouldViolate: (a: number, b: any) => [number, number, number];
2824
+ readonly wasmdocument_xmpPacket: (a: number) => [number, number, number];
1226
2825
  readonly wasmimage_height: (a: number) => number;
1227
2826
  readonly wasmimage_jpeg: (a: number, b: number) => [number, number, number];
1228
2827
  readonly wasmimage_png: (a: number, b: number) => [number, number, number];