write_restricted.rs

Die Rust-Datei des Beispiels „Ein Dokument, das sich frei lesen lässt und bittet, nicht kopiert zu werden“. Eine Seite, die sich ohne Passwort öffnet und wie jede andere druckt, aber die Anzeigesoftware bittet, das Kopieren und Einfügen ihres Textes zu verbieten. Der Text ist in der Datei verschlüsselt: Ein Programm, das sie Byte für Byte liest, findet darin keinen Satz.

Rust 1000 Zeilen

Wofür dieses Beispiel da ist

Denken Sie an eine auf dem Ladentisch liegende Preisliste. Jeder darf sie nehmen, lesen und mitnehmen: Dafür liegt sie da. Was man nicht sehen möchte, ist, dass die eigenen Absätze vierzehn Tage später Wort für Wort in der Broschüre eines anderen auftauchen. Die Datei unten ist diese Preisliste. Sie öffnet sich für jeden und trägt eine schriftliche Bitte, ihren Text in Ruhe zu lassen.

Was dieses Beispiel zeigt

   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
//! Writes a document that opens on its own and asks not to be copied.
//!
//! Every string and every stream in the file is ciphertext, locked with AES
//! under a two-hundred-and-fifty-six-bit key. The user password is empty, so
//! reading software opens the file without asking anybody for anything, which
//! is what most protected files in the world do.
//!
//! What the file grants is printing, at the resolution the page was drawn at,
//! and reading aloud. Copying, changing and taking pages out are withheld — as
//! requests reading software honours, not as locks: reading software that
//! ignores them copies the page all the same, and the page says so rather than
//! letting the file be taken for a safe.
//!
//! The document reading software is shown nothing of until a password is typed
//! is `write_locked`, and this page names it in words rather than by that name.
//!
//! The page is written in the language `HQF_PDF_LANG` names. The password is
//! not: it is a string typed into reading software, and a translated password
//! opens nothing.
//!
//! A second page says what the same first page weighs locked other ways:
//! granting everything, granting changes, copying, notes, forms and assembly,
//! under the older cipher earlier reading software knows, with its metadata
//! left readable, carrying one file, with only that file locked, and locked for
//! the holder of one certificate rather than under a password.
//!
//! Usage: `cargo run --example write_restricted -- tmp/restricted.pdf
//! [font.ttf]`
//!        `HQF_PDF_LANG=fr cargo run --example write_restricted`

use std::env;
use std::fs;
use std::path::{Path, PathBuf};

use hqf_pdf::content::Content;
use hqf_pdf::cos::Name;
use hqf_pdf::metadata::attachment::{Attachment, Relationship};
use hqf_pdf::{Document, Encryption, Font, FontHandle, Page, Permissions, Recipient};

#[path = "shared/out.rs"]
mod out;

#[path = "shared/licence.rs"]
mod licence;

#[path = "shared/language.rs"]
mod language;

#[path = "shared/failure.rs"]
mod failure;

use language::Language;

/// The font the example draws with when none is given on the command line: the
/// one committed for the tests, so that the example runs on any machine.
fn default_font() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("fonts")
        .join("DejaVuSans.ttf")
}

/// The certificate the first page is locked for in the last of the other ways:
/// one committed for the tests, holding an RSA key, so that the example runs on
/// any machine.
fn certificate() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("certificates")
        .join("recipient.der")
}

/// How far in from the left edge of the sheet every line is set, in points.
const LEFT: f64 = 72.0;

/// The size the heading is set at, in points.
const HEADING_SIZE: f64 = 18.0;

/// Where the baseline of the first line of the heading sits, in points up from
/// the foot of the sheet.
const HEADING_BASELINE: f64 = 760.0;

/// How far one line of the heading sits below the one before it, in points.
const HEADING_LEADING: f64 = 22.0;

/// The size the body is set at, in points.
const BODY_SIZE: f64 = 11.0;

/// Where the baseline of the first line of the body sits, in points up from the
/// foot of the sheet.
const FIRST_BASELINE: f64 = 700.0;

/// How far one line of the body sits below the one before it, in points.
const LEADING: f64 = 18.0;

/// The size the heading over each closing section is set at, in points.
const SECTION_SIZE: f64 = 13.0;

/// Where the baseline of the heading over the other document sits, in points up
/// from the foot of the sheet.
const COMPANION_HEADING_BASELINE: f64 = 520.0;

/// Where the baseline of the first line about the other document sits, in
/// points up from the foot of the sheet.
const COMPANION_BASELINE: f64 = 492.0;

/// Where the baseline of the heading over the closing note sits, in points up
/// from the foot of the sheet.
const NOTE_HEADING_BASELINE: f64 = 420.0;

/// Where the baseline of the first line of the note sits, in points up from the
/// foot of the sheet.
const NOTE_BASELINE: f64 = 392.0;

/// The author's password, which the page states so that what the file withholds
/// can be lifted. It stands outside the words: a password is typed into reading
/// software, and a translated one opens nothing.
const OWNER_PASSWORD: &str = "the owner";

/// The thirty-two bytes the file key is built from.
///
/// They are fixed here, so that the example writes the same file on every run
/// and one build can be compared with the last. **A program takes them from its
/// operating system** — `getrandom` in Rust, `os.urandom(32)` in Python. A file
/// locked under a seed anybody can read is a file anybody opens.
const SEED: [u8; 32] = [
    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
    0x0F, 0x1E, 0x2D, 0x3C, 0x4B, 0x5A, 0x69, 0x78, 0x87, 0x96, 0xA5, 0xB4, 0xC3, 0xD2, 0xE1, 0xF0,
];

/// The words the page is written in, one set per language.
///
/// The password is not among them: it is typed into reading software, not
/// translated.
#[derive(Debug)]
struct Words {
    /// What the document is called, set over two lines at the head of the page
    /// and joined by a space in what the file says of itself.
    heading: [&'static str; 2],
    /// The body of the page, one line to a line.
    body: [&'static str; 8],
    /// What stands before the author's password.
    password_label: &'static str,
    /// What stands over the lines about the other document.
    companion_heading: &'static str,
    /// What the other document is, and how it differs from this one.
    companion: [&'static str; 3],
    /// What stands over the closing note.
    note_heading: &'static str,
    /// The closing note, which says where the key comes from.
    note: [&'static str; 4],
    /// The second page's heading.
    other_heading: &'static str,
    /// What the first page weighs locked each other way; `{bytes}` stands for
    /// the weight.
    other: [&'static str; 6],
    /// What the file carried by the copy that carries one is.
    carried: &'static str,
}

impl Words {
    /// The words the page is written in, in `language`.
    fn of(language: Language) -> &'static Self {
        language::pick(&WORDS, language)
    }

    /// What the file says of itself: the two lines of the heading, in a row.
    fn title(&self) -> String {
        self.heading.join(" ")
    }
}

/// The page in English.
const ENGLISH: Words = Words {
    heading: ["A document that reads freely", "and asks not to be copied"],
    body: [
        "Everything written in this file is scrambled: every word,",
        "every picture, under a key of two hundred and fifty-six bits.",
        "It opens all the same. Nobody is asked for anything, which is",
        "what most protected files in the world do.",
        "What it asks is asked of the reading software: let this be",
        "printed and read out loud, but do not let it be copied or",
        "changed. It is a request and not a lock, and software that",
        "pays no heed to it copies the page all the same.",
    ],
    password_label: "The author's password lifts the request:",
    companion_heading: "The other document of this pair",
    companion: [
        "A second document was written beside this one. That one shows",
        "nothing at all until its password is typed in, while the page",
        "you are reading opened on its own.",
    ],
    note_heading: "Where the key comes from",
    note: [
        "The thirty-two bytes the key is built from are fixed in this",
        "example, so that it writes the same file on every run and one",
        "build can be compared with the last. A program takes them from",
        "its operating system: a seed anybody can read locks nothing.",
    ],
    other_heading: "The first page, locked other ways",
    other: [
        "Granting everything, it weighs {bytes} bytes.",
        "Granting changes, copying, notes, forms and assembly: {bytes} bytes.",
        "Under the older cipher earlier software knows: {bytes} bytes.",
        "With what it says of itself left readable: {bytes} bytes.",
        "Carrying one file, with only that file locked: {bytes} bytes.",
        "Locked for one person's certificate instead: {bytes} bytes.",
    ],
    carried: "The file carried inside",
};

/// The page in French.
const FRENCH: Words = Words {
    heading: [
        "Un document qui se lit librement",
        "et demande à ne pas être copié",
    ],
    body: [
        "Tout ce que ce fichier contient est chiffré : chaque mot,",
        "chaque image, avec une clé de deux cent cinquante-six bits.",
        "Il s’ouvre quand même. On ne demande rien à personne, comme",
        "le font la plupart des fichiers protégés.",
        "Sa demande s’adresse au logiciel de lecture : autoriser",
        "l’impression et la lecture à voix haute, mais ni la copie",
        "ni la modification. C’est une demande, pas un verrou, et un logiciel",
        "qui n’en tient pas compte copie la page quand même.",
    ],
    password_label: "Le mot de passe de l’auteur lève la demande :",
    companion_heading: "L’autre document de la paire",
    companion: [
        "Un second document a été créé avec celui-ci. Il n’affiche",
        "rien du tout tant qu’on n’a pas saisi son mot de passe,",
        "alors que la page que vous lisez s’est ouverte toute seule.",
    ],
    note_heading: "D’où vient la clé",
    note: [
        "Les trente-deux octets dont la clé est tirée sont figés dans cet",
        "exemple, pour qu’il produise le même fichier à chaque exécution et",
        "qu’on puisse le comparer au précédent. Un programme les demande à son",
        "système d’exploitation : une graine lisible par tous ne protège rien.",
    ],
    other_heading: "La première page, verrouillée autrement",
    other: [
        "En accordant tout, elle pèse {bytes} octets.",
        "En accordant modification, copie, notes, formulaires et assemblage : {bytes} octets.",
        "Avec l’ancien chiffrement que connaissent les logiciels d’avant : {bytes} octets.",
        "En laissant lisible ce qu’elle dit d’elle-même : {bytes} octets.",
        "Avec un fichier joint, seul ce fichier verrouillé : {bytes} octets.",
        "Pour le certificat d’une seule personne : {bytes} octets.",
    ],
    carried: "Le fichier joint",
};

/// The page in Dutch.
const DUTCH: Words = Words {
    heading: [
        "Een document dat vrij te lezen is",
        "en niet gekopieerd wil worden",
    ],
    body: [
        "Alles wat in dit bestand staat, is versleuteld: elk woord,",
        "elke afbeelding, met een sleutel van tweehonderdzesenvijftig bits.",
        "Het opent toch. Niemand wordt iets gevraagd, en zo",
        "werken de meeste beveiligde bestanden ter wereld.",
        "Wat het vraagt, vraagt het aan de leessoftware: laat dit",
        "afdrukken en voorlezen, maar niet kopiëren of",
        "wijzigen. Het is een verzoek en geen slot, en software die",
        "er geen acht op slaat, kopieert de pagina toch.",
    ],
    password_label: "Het wachtwoord van de auteur heft het verzoek op:",
    companion_heading: "Het andere document van dit paar",
    companion: [
        "Naast dit document is een tweede geschreven. Dat toont",
        "helemaal niets tot het wachtwoord is ingetypt, terwijl de pagina",
        "die u leest, vanzelf openging.",
    ],
    note_heading: "Waar de sleutel vandaan komt",
    note: [
        "De tweeëndertig bytes waaruit de sleutel wordt opgebouwd, liggen in dit",
        "voorbeeld vast, zodat het bij elke run hetzelfde bestand schrijft en een",
        "build met de vorige vergeleken kan worden. Een programma haalt ze uit",
        "zijn besturingssysteem: een startwaarde die iedereen kan lezen, sluit niets af.",
    ],
    other_heading: "De eerste pagina, op andere manieren vergrendeld",
    other: [
        "Met alles toegestaan weegt ze {bytes} bytes.",
        "Met wijzigen, kopiëren, notities, formulieren en samenstellen toegestaan: {bytes} bytes.",
        "Met de oudere versleuteling die eerdere software kent: {bytes} bytes.",
        "Met leesbaar gelaten wat ze over zichzelf zegt: {bytes} bytes.",
        "Met één bestand erin, en alleen dat bestand vergrendeld: {bytes} bytes.",
        "Vergrendeld voor het certificaat van één persoon: {bytes} bytes.",
    ],
    carried: "Het ingesloten bestand",
};

/// The page in Portuguese.
const PORTUGUESE: Words = Words {
    heading: [
        "Um documento que se lê livremente",
        "e pede para não ser copiado",
    ],
    body: [
        "Tudo o que está escrito neste ficheiro está cifrado: cada palavra,",
        "cada imagem, com uma chave de duzentos e cinquenta e seis bits.",
        "Abre na mesma. Ninguém tem de escrever nada, que é",
        "o que faz a maioria dos ficheiros protegidos do mundo.",
        "O que pede, pede-o ao programa de leitura: que o deixe",
        "imprimir e ler em voz alta, mas não copiar nem",
        "alterar. É um pedido e não um cadeado, e um programa que",
        "não lhe liga copia a página na mesma.",
    ],
    password_label: "A palavra-passe do autor levanta o pedido:",
    companion_heading: "O outro documento deste par",
    companion: [
        "Ao lado deste foi escrito um segundo documento. Esse não mostra",
        "nada até se escrever a sua palavra-passe, enquanto a página",
        "que está a ler abriu sozinha.",
    ],
    note_heading: "De onde vem a chave",
    note: [
        "Os trinta e dois bytes a partir dos quais a chave é construída são fixos neste",
        "exemplo, para que escreva o mesmo ficheiro a cada execução e uma",
        "compilação possa ser comparada com a anterior. Um programa tira-os do",
        "seu sistema operativo: uma semente que qualquer um pode ler não fecha nada.",
    ],
    other_heading: "A primeira página, fechada de outras formas",
    other: [
        "Permitindo tudo, pesa {bytes} bytes.",
        "Permitindo alterações, cópia, notas, formulários e montagem: {bytes} bytes.",
        "Com a cifra antiga que programas anteriores conhecem: {bytes} bytes.",
        "Com o que diz de si mesmo deixado legível: {bytes} bytes.",
        "Com um ficheiro dentro, e só esse ficheiro fechado: {bytes} bytes.",
        "Fechada para o certificado de uma só pessoa: {bytes} bytes.",
    ],
    carried: "O ficheiro levado dentro",
};

/// The page in Italian.
const ITALIAN: Words = Words {
    heading: [
        "Un documento che si legge liberamente",
        "e chiede di non essere copiato",
    ],
    body: [
        "Tutto ciò che è scritto in questo file è cifrato: ogni parola,",
        "ogni immagine, con una chiave di duecentocinquantasei bit.",
        "Si apre lo stesso. A nessuno si chiede nulla, come fa",
        "la maggior parte dei file protetti al mondo.",
        "Ciò che chiede lo chiede al programma di lettura: lasciarlo",
        "stampare e leggere ad alta voce, ma non copiarlo né",
        "modificarlo. È una richiesta e non un lucchetto, e un programma che",
        "non ne tiene conto copia la pagina lo stesso.",
    ],
    password_label: "La password dell’autore toglie la richiesta:",
    companion_heading: "L’altro documento di questa coppia",
    companion: [
        "Accanto a questo è stato scritto un secondo documento. Quello non mostra",
        "nulla finché non si digita la sua password, mentre la pagina",
        "che state leggendo si è aperta da sola.",
    ],
    note_heading: "Da dove viene la chiave",
    note: [
        "I trentadue byte da cui si costruisce la chiave sono fissi in questo",
        "esempio, così scrive lo stesso file a ogni esecuzione e una",
        "compilazione si può confrontare con la precedente. Un programma li prende dal",
        "suo sistema operativo: un seme che chiunque può leggere non chiude nulla.",
    ],
    other_heading: "La prima pagina, chiusa in altri modi",
    other: [
        "Concedendo tutto, pesa {bytes} byte.",
        "Concedendo modifiche, copia, note, moduli e assemblaggio: {bytes} byte.",
        "Con il vecchio cifrario che conoscono i programmi precedenti: {bytes} byte.",
        "Con ciò che dice di sé lasciato leggibile: {bytes} byte.",
        "Con un file dentro, e solo quel file chiuso: {bytes} byte.",
        "Chiusa per il certificato di una sola persona: {bytes} byte.",
    ],
    carried: "Il file portato dentro",
};

/// The page in Spanish.
const SPANISH: Words = Words {
    heading: [
        "Un documento que se lee libremente",
        "y pide que no se copie",
    ],
    body: [
        "Todo lo escrito en este archivo está cifrado: cada palabra,",
        "cada imagen, con una clave de doscientos cincuenta y seis bits.",
        "Se abre igualmente. A nadie se le pide nada, que es",
        "lo que hacen la mayoría de los archivos protegidos del mundo.",
        "Lo que pide se lo pide al programa de lectura: que deje",
        "imprimirlo y leerlo en voz alta, pero no copiarlo ni",
        "cambiarlo. Es una petición y no un cerrojo, y un programa que",
        "no le hace caso copia la página igualmente.",
    ],
    password_label: "La contraseña del autor anula la petición:",
    companion_heading: "El otro documento de esta pareja",
    companion: [
        "Junto a este se escribió un segundo documento. Ese no muestra",
        "nada hasta que se teclea su contraseña, mientras que la página",
        "que está leyendo se abrió sola.",
    ],
    note_heading: "De dónde sale la clave",
    note: [
        "Los treinta y dos bytes con que se construye la clave son fijos en este",
        "ejemplo, para que escriba el mismo archivo en cada ejecución y una",
        "compilación pueda compararse con la anterior. Un programa real los toma de",
        "su sistema operativo: una semilla que cualquiera puede leer no cierra nada.",
    ],
    other_heading: "La primera página, cerrada de otras formas",
    other: [
        "Permitiéndolo todo, pesa {bytes} bytes.",
        "Permitiendo cambios, copia, notas, formularios y montaje: {bytes} bytes.",
        "Con el cifrado antiguo que conocen programas anteriores: {bytes} bytes.",
        "Con lo que dice de sí mismo sin cifrar: {bytes} bytes.",
        "Con un archivo dentro, y solo ese archivo cerrado: {bytes} bytes.",
        "Cerrada para el certificado de una sola persona: {bytes} bytes.",
    ],
    carried: "El archivo que lleva dentro",
};

/// The page in German.
const GERMAN: Words = Words {
    heading: [
        "Ein Dokument, das sich frei lesen lässt",
        "und bittet, nicht kopiert zu werden",
    ],
    body: [
        "Alles in dieser Datei ist verschlüsselt: jedes Wort,",
        "jedes Bild, unter einem Schlüssel von 256 Bit.",
        "Sie öffnet sich trotzdem. Niemand wird nach etwas gefragt,",
        "wie bei den meisten geschützten Dateien der Welt.",
        "Die Bitte richtet sich an die Lesesoftware: dies drucken",
        "und vorlesen lassen, aber nicht kopieren oder ändern",
        "lassen. Es ist eine Bitte und kein Schloss, und Software,",
        "die sie missachtet, kopiert die Seite trotzdem.",
    ],
    password_label: "Das Passwort des Autors hebt die Bitte auf:",
    companion_heading: "Das andere Dokument dieses Paares",
    companion: [
        "Neben diesem wurde ein zweites Dokument geschrieben. Es zeigt",
        "gar nichts, bis sein Passwort eingegeben ist, während die Seite,",
        "die Sie lesen, sich von selbst geöffnet hat.",
    ],
    note_heading: "Woher der Schlüssel kommt",
    note: [
        "Die zweiunddreißig Bytes, aus denen der Schlüssel gebaut wird,",
        "sind in diesem Beispiel fest, damit es bei jedem Lauf dieselbe",
        "Datei schreibt. Ein Programm holt sie von seinem Betriebssystem:",
        "Ein Startwert, den jeder lesen kann, schließt nichts ab.",
    ],
    other_heading: "Die erste Seite, anders verschlossen",
    other: [
        "Wenn alles erlaubt ist, wiegt sie {bytes} Bytes.",
        "Mit erlaubtem Ändern, Kopieren, Notizen, Formularen und Zusammenstellen: {bytes} Bytes.",
        "Mit der älteren Verschlüsselung, die frühere Software kennt: {bytes} Bytes.",
        "Mit lesbar gelassenen Angaben über sich selbst: {bytes} Bytes.",
        "Mit einer mitgeführten Datei, und nur diese verschlossen: {bytes} Bytes.",
        "Für das Zertifikat einer einzigen Person verschlossen: {bytes} Bytes.",
    ],
    carried: "Die darin mitgeführte Datei",
};

/// Every language the example is written in. A language is added by writing its
/// own set of words and naming it here.
static WORDS: [(Language, &Words); 7] = [
    (Language::English, &ENGLISH),
    (Language::French, &FRENCH),
    (Language::German, &GERMAN),
    (Language::Spanish, &SPANISH),
    (Language::Italian, &ITALIAN),
    (Language::Portuguese, &PORTUGUESE),
    (Language::Dutch, &DUTCH),
];

/// Every line of the body, the last of which states the author's password.
fn lines(words: &Words) -> Vec<String> {
    let mut all: Vec<String> = words.body.iter().map(|line| (*line).to_owned()).collect();
    all.push(format!("{} {OWNER_PASSWORD}", words.password_label));
    all
}

/// What the document is locked with: the author's password, and what reading
/// software is asked to allow — printing at the resolution the page was drawn
/// at, and reading aloud.
fn encryption() -> Encryption {
    Encryption::new(SEED)
        .owner_password(OWNER_PASSWORD)
        .permissions(Permissions::new().printing().printing_at_full_resolution())
}

/// What the first page weighs under `encryption`, `carrying` a file where it is
/// true.
fn weighed(
    words: &Words,
    font: &Font,
    encryption: Encryption,
    carrying: bool,
) -> Result<usize, Box<dyn std::error::Error>> {
    let mut doc = document(words, font.clone())?;
    if carrying {
        doc.attach(Attachment::new(
            "note.txt",
            words.carried,
            "text/plain",
            Relationship::Supplement,
            "2026-09-17T09:00:00+02:00",
            words.carried.as_bytes().to_vec(),
        ));
    }
    doc.set_encryption(encryption);
    Ok(doc.to_bytes()?.len())
}

/// The second page's lines: what the first page weighs locked each other way.
fn other_ways(words: &Words, font: &Font) -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let weights = [
        weighed(
            words,
            font,
            Encryption::new(SEED)
                .owner_password(OWNER_PASSWORD)
                .permissions(Permissions::everything()),
            false,
        )?,
        weighed(
            words,
            font,
            Encryption::new(SEED)
                .owner_password(OWNER_PASSWORD)
                .permissions(
                    Permissions::new()
                        .modifying()
                        .copying()
                        .annotating()
                        .filling_forms()
                        .assembling(),
                ),
            false,
        )?,
        weighed(words, font, encryption().for_older_readers(), false)?,
        weighed(words, font, encryption().metadata_in_the_clear(), false)?,
        weighed(
            words,
            font,
            Encryption::new(SEED)
                .user_password(OWNER_PASSWORD)
                .only_the_attachments(),
            true,
        )?,
        weighed(
            words,
            font,
            Encryption::new(SEED).recipient(
                Recipient::from_der(&fs::read(certificate())?)?,
                Permissions::new().printing().printing_at_full_resolution(),
            ),
            false,
        )?,
    ];
    Ok(words
        .other
        .iter()
        .zip(weights)
        .map(|(line, weight)| line.replace("{bytes}", &weight.to_string()))
        .collect())
}

/// Every line the page draws, in the order they are drawn: what it says, the
/// size it is set at, and where its baseline sits in points up from the foot of
/// the sheet.
fn drawing(words: &Words) -> Vec<(String, f64, f64)> {
    let mut drawn = Vec::new();

    let mut baseline = HEADING_BASELINE;
    for line in words.heading {
        drawn.push((line.to_owned(), HEADING_SIZE, baseline));
        baseline -= HEADING_LEADING;
    }

    baseline = FIRST_BASELINE;
    for line in lines(words) {
        drawn.push((line, BODY_SIZE, baseline));
        baseline -= LEADING;
    }

    drawn.push((
        words.companion_heading.to_owned(),
        SECTION_SIZE,
        COMPANION_HEADING_BASELINE,
    ));
    baseline = COMPANION_BASELINE;
    for line in words.companion {
        drawn.push((line.to_owned(), BODY_SIZE, baseline));
        baseline -= LEADING;
    }

    drawn.push((
        words.note_heading.to_owned(),
        SECTION_SIZE,
        NOTE_HEADING_BASELINE,
    ));
    baseline = NOTE_BASELINE;
    for line in words.note {
        drawn.push((line.to_owned(), BODY_SIZE, baseline));
        baseline -= LEADING;
    }
    drawn
}

/// The page: every line set by its own origin, each in an object of its own.
fn drawn(font: &FontHandle, lines: &[(String, f64, f64)]) -> Result<Content, hqf_pdf::Error> {
    let mut content = Content::new();
    for (line, size, baseline) in lines {
        content.begin_text();
        content.set_font(font, *size)?;
        content.text_origin(LEFT, *baseline)?;
        content.show_glyphs(&font.glyphs(line));
        content.end_text();
    }
    Ok(content)
}

/// The document and its first page, before it is locked, with the handle the
/// page is set through.
fn composed(words: &Words, font: Font) -> Result<(Document, FontHandle), hqf_pdf::Error> {
    let mut doc = Document::new();
    doc.set_license(licence::licensed());
    doc.set_info(Name::new("Title"), &words.title());
    let handle = doc.add_font(font);

    let mut page = Page::a4();
    page.content = drawn(&handle, &drawing(words))?.into_bytes();
    doc.add_page(page)?;
    Ok((doc, handle))
}

/// The document and its first page, before it is locked.
fn document(words: &Words, font: Font) -> Result<Document, hqf_pdf::Error> {
    Ok(composed(words, font)?.0)
}

/// The document and its first page, before it is locked, and a second page
/// setting `second` under its heading.
fn document_with(words: &Words, font: Font, second: &[String]) -> Result<Document, hqf_pdf::Error> {
    let (mut doc, handle) = composed(words, font)?;
    let mut lines: Vec<(String, f64, f64)> = vec![(
        words.other_heading.to_owned(),
        SECTION_SIZE,
        HEADING_BASELINE,
    )];
    let mut baseline = FIRST_BASELINE;
    for line in second {
        lines.push((line.clone(), BODY_SIZE, baseline));
        baseline -= LEADING;
    }
    let mut page = Page::a4();
    page.content = drawn(&handle, &lines)?.into_bytes();
    doc.add_page(page)?;
    Ok(doc)
}

/// Writes `bytes` to `path`, making the directory it goes in if it is not
/// there, and says what was written.
fn written(path: &str, bytes: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
    if let Some(parent) = Path::new(path).parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(path, bytes)?;
    println!("wrote {path}: {} bytes", bytes.len());
    Ok(())
}

fn main() -> std::process::ExitCode {
    failure::reported(run())
}

fn run() -> Result<(), Box<dyn std::error::Error>> {
    let language = Language::from_environment()?;
    let words = Words::of(language);

    let mut args = env::args().skip(1);
    // A named file is written as named; the default one carries the language,
    // so the two languages do not overwrite each other in `tmp/`.
    let out = args
        .next()
        .unwrap_or_else(|| language.file_name(&out::default_path("restricted")));
    let font_path = args.next().map_or_else(default_font, PathBuf::from);

    let font = Font::parse(fs::read(&font_path)?)?;
    let said = other_ways(words, &font)?;
    let mut doc = document_with(words, font, &said)?;
    doc.set_encryption(encryption());
    written(&out, &doc.to_bytes()?)
}

#[cfg(test)]
mod tests {
    use std::path::{Path, PathBuf};
    use std::process::Command;

    use hqf_pdf::read::Reader;
    use hqf_pdf::{Document, Encryption, Font};

    use super::{
        LEFT, OWNER_PASSWORD, WORDS, Words, default_font, document, drawing, encryption, language,
    };

    /// The width of the sheet, in points: A4, which the page is.
    const SHEET_WIDTH: f64 = 595.276;

    /// The room a line set from the left margin has before it reaches the
    /// margin on the other side of the sheet.
    const PAPER: f64 = SHEET_WIDTH - 2.0 * LEFT;

    /// The lines two languages are allowed to write the same way. There are
    /// none: the password is held outside the words.
    const SPARED: [&str; 0] = [];

    /// The committed font, parsed.
    fn a_font() -> Font {
        Font::parse(std::fs::read(default_font()).expect("the committed font is there"))
            .expect("the committed font parses")
    }

    /// The bytes the example writes for `words`, protected or in the clear.
    fn written(words: &Words, protected: bool) -> Vec<u8> {
        let mut doc = document(words, a_font()).expect("the document is built");
        if protected {
            doc.set_encryption(encryption());
        }
        doc.to_bytes().expect("the document writes")
    }

    #[test]
    fn every_language_writes_the_page_in_its_own_words() {
        let untranslated = language::untranslated_lines(&WORDS, &SPARED);

        assert!(
            untranslated.is_empty(),
            "the page says these in more than one language: {untranslated:?}"
        );
    }

    /// Nothing on the page is broken to a width: a line longer than the paper
    /// runs off the edge of it.
    #[test]
    fn every_language_writes_lines_that_fit_the_room_they_have() {
        let mut doc = Document::new();
        let font = doc.add_font(a_font());

        for (named, words) in WORDS {
            for (line, size, _) in drawing(words) {
                let measured = font.measure(&line, size);
                assert!(
                    measured <= PAPER,
                    "the {} page draws {line:?} over {measured:.1} points, and \
                     it has {PAPER:.1}",
                    named.code()
                );
            }
        }
    }

    /// The page draws no line over the one below it: every baseline sits lower
    /// than the one before, by at least the size the line above is set at.
    #[test]
    fn the_page_draws_no_line_over_the_one_below_it() {
        for (named, words) in WORDS {
            for pair in drawing(words).windows(2) {
                let (above, size, higher) = &pair[0];
                let (below, _, lower) = &pair[1];
                assert!(
                    higher - lower >= *size,
                    "the {} page sets {above:?} at {higher} and {below:?} at \
                     {lower}, and the first is {size} points tall",
                    named.code()
                );
            }
        }
    }

    /// The bytes a file states its title as, however it spells them: plain
    /// where the words are, sixteen bits to a letter where they are not.
    fn stated_title(bytes: &[u8]) -> Vec<u8> {
        let key = b"/Title (";
        let at = bytes
            .windows(key.len())
            .position(|window| window == key)
            .expect("the file states a title")
            + key.len();

        let mut stated = Vec::new();
        let mut escaped = false;
        for byte in &bytes[at..] {
            if escaped {
                escaped = false;
            } else if *byte == b'\\' {
                escaped = true;
            } else if *byte == b')' {
                break;
            }
            stated.push(*byte);
        }
        stated
    }

    /// The file names the handler it was locked with and carries nothing of
    /// what it states its title to be. The same document written in the clear
    /// carries it plainly, which is what says the reading looks in the right
    /// place.
    #[test]
    fn the_file_carries_nothing_it_draws_in_the_clear() {
        for (named, words) in WORDS {
            let code = named.code();
            let plain = written(words, false);
            let stated = stated_title(&plain);
            assert!(
                stated.len() > 8,
                "the {code} document states its title where a file states one"
            );
            assert!(
                !String::from_utf8_lossy(&plain).contains("/Encrypt"),
                "and names no handler"
            );

            let locked = written(words, true);
            let text = String::from_utf8_lossy(&locked);
            assert!(text.contains("/Encrypt"), "the {code} file is protected");
            assert!(text.contains("/AESV3"), "and names the cipher it used");
            assert!(
                !locked
                    .windows(stated.len())
                    .any(|window| window == stated.as_slice()),
                "the {code} file carries its title in the clear"
            );
        }
    }

    /// What the file says of itself comes back out of it, which noise would not
    /// spell. The user password is empty, so an empty one opens it; the
    /// author's opens it too.
    #[test]
    fn what_the_document_says_of_itself_comes_back_out_of_the_file() {
        for (named, words) in WORDS {
            let bytes = written(words, true);

            for password in ["", OWNER_PASSWORD] {
                let reader = Reader::with_password(bytes.clone(), password)
                    .unwrap_or_else(|error| panic!("{password:?} opens it: {error}"));
                assert_eq!(
                    reader
                        .information()
                        .expect("the file states what it is")
                        .title
                        .unwrap_or_default(),
                    words.title(),
                    "the {} file states its title",
                    named.code()
                );
            }
        }
    }

    /// The page says printing and reading aloud are allowed and the rest is
    /// withheld; the file states the same thing, as the flags of table 22 with
    /// every reserved bit set — 0xFFFFF0C0, which is -3904 — plus bit 3 for
    /// printing, which is 4, bit 10 for reading aloud, which is 512, and bit 12
    /// for printing at the resolution the page was drawn at, which is 2048. A
    /// file that grants bit 3 without bit 12 is printed as a coarse picture.
    #[test]
    fn what_the_page_says_is_allowed_is_what_the_file_states() {
        let bytes = written(Words::of(language::Language::English), true);

        assert_eq!(-3904 + 4 + 512 + 2048, -1340, "the flags add up");
        assert!(
            String::from_utf8_lossy(&bytes).contains("/P -1340"),
            "the file states what it grants"
        );
    }

    /// The same document written twice is the same file, which is what lets one
    /// build be compared with the last; another key writes another file.
    #[test]
    fn the_same_seed_writes_the_same_file_and_another_writes_another() {
        let words = Words::of(language::Language::English);

        assert_eq!(
            written(words, true),
            written(words, true),
            "a protected document is still the same file"
        );

        let mut other = document(words, a_font()).expect("the document is built");
        other.set_encryption(Encryption::new([1u8; 32]).owner_password(OWNER_PASSWORD));
        assert_ne!(
            written(words, true),
            other.to_bytes().expect("the document writes"),
            "another key writes another file"
        );
    }

    /// Where reading software that is not ours is handed what it is to read.
    fn scratch(name: &str) -> PathBuf {
        let mut at = std::env::temp_dir();
        at.push(format!(
            "hqf-pdf-write-restricted-{}-{name}",
            std::process::id()
        ));
        at
    }

    /// Whether a program is on the machine.
    fn installed(program: &str) -> bool {
        Command::new(program)
            .arg("-v")
            .output()
            .is_ok_and(|out| out.status.success() || !out.stderr.is_empty())
    }

    /// What `program` printed about `file`, and whether it managed to read it.
    fn asked(program: &str, file: &Path, arguments: &[&str]) -> (bool, String) {
        let out = Command::new(program)
            .args(arguments)
            .arg(file)
            .output()
            .unwrap_or_else(|error| panic!("{program} runs: {error}"));
        (
            out.status.success(),
            String::from_utf8_lossy(&out.stdout).into_owned(),
        )
    }

    /// What poppler gets off the pages of `file`, and whether it managed to
    /// read it at all.
    fn text_of(file: &Path) -> (bool, String) {
        let out = Command::new("pdftotext")
            .arg(file)
            .arg("-")
            .output()
            .expect("pdftotext runs");
        (
            out.status.success(),
            String::from_utf8_lossy(&out.stdout).into_owned(),
        )
    }

    /// Poppler, which shares no code with this library, opens the file with
    /// nothing typed, says it is protected, reads its title back and gets the
    /// page off it — which is the page's own claim measured from outside: what
    /// the file withholds is a request, and a reader is free to pay it no heed.
    ///
    /// Our own reader agreeing with our own writer would prove only that the
    /// two were written from one reading of one clause.
    #[test]
    fn reading_software_that_is_not_ours_opens_this_file_with_nothing_typed() {
        if !installed("pdfinfo") {
            println!("skipped: poppler is not on this machine");
            return;
        }
        let words = Words::of(language::Language::English);

        let at = scratch("opens.pdf");
        std::fs::write(&at, written(words, true)).expect("the file is written");

        let (opened, said) = asked("pdfinfo", &at, &[]);
        assert!(
            opened,
            "poppler opens the file with nothing typed, and said {said}"
        );
        assert!(
            said.contains(&format!("Title:          {}", words.title())),
            "and read its title back, and said {said}"
        );
        assert!(
            said.contains("Encrypted:      yes"),
            "and says the file is protected, and said {said}"
        );
        for stated in ["print:yes", "copy:no", "change:no"] {
            assert!(
                said.contains(stated),
                "and was told {stated}, and said {said}"
            );
        }

        if installed("pdftotext") {
            let drawn = words.body[0];
            let (read, got) = text_of(&at);
            assert!(
                read && got.contains(drawn),
                "poppler got the page off the file with nothing typed, and got \
                 {got:?}"
            );
        } else {
            println!("skipped: pdftotext is not on this machine");
        }

        std::fs::remove_file(&at).ok();
    }
}