apdf_5.0.3_dbe27598/pdf_out.adb

   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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
with PDF_Out.Fonts,
     PDF_Out.Images;

with GID;

with Ada.Characters.Handling;           use Ada.Characters.Handling;
with Ada.Strings.Fixed;
with Ada.Unchecked_Deallocation;

with Interfaces;                        use Interfaces;

package body PDF_Out is

  use Ada.Streams.Stream_IO, Ada.Streams;

  package CIO is new Ada.Text_IO.Integer_IO(Ada.Streams.Stream_IO.Count);

  --  Very low level part which deals with transferring data endian-proof,
  --  and floats in the IEEE format. This is needed for having PDF Writer
  --  totally portable on all systems and processor architectures.

  type Byte_buffer is array (Integer range <>) of Unsigned_8;

  --  Put numbers with correct endianess as bytes:
  generic
    type Number is mod <>;
    size: Positive;
  function Intel_x86_buffer( n: Number ) return Byte_buffer;
  pragma Inline(Intel_x86_buffer);

  function Intel_x86_buffer( n: Number ) return Byte_buffer is
    b: Byte_buffer(1..size);
    m: Number:= n;
  begin
    for i in b'Range loop
      b(i):= Unsigned_8(m and 255);
      m:= m / 256;
    end loop;
    return b;
  end Intel_x86_buffer;

  function Intel_32 is new Intel_x86_buffer( Unsigned_32, 4 );
  pragma Unreferenced (Intel_32);

  function Intel_16( n: Unsigned_16 ) return Byte_buffer is
    pragma Inline(Intel_16);
  begin
    return (Unsigned_8(n and 255), Unsigned_8(Shift_Right(n, 8)));
  end Intel_16;
  pragma Unreferenced (Intel_16);

  --  Workaround for the severe xxx'Read xxx'Write performance
  --  problems in the GNAT and ObjectAda compilers (as in 2009)
  --  This is possible if and only if Byte = Stream_Element and
  --  arrays types are both packed and aligned the same way.
  --
  subtype Size_test_a is Byte_buffer(1..19);
  subtype Size_test_b is Ada.Streams.Stream_Element_Array(1..19);
  workaround_possible: constant Boolean:=
    Size_test_a'Size = Size_test_b'Size and
    Size_test_a'Alignment = Size_test_b'Alignment;

  procedure Block_Read(
    file         : in     Ada.Streams.Stream_IO.File_Type;
    buffer       :    out Byte_buffer;
    actually_read:    out Natural
  )
  is
    SE_Buffer   : Stream_Element_Array (1 .. buffer'Length);
    for SE_Buffer'Address use buffer'Address;
    pragma Import (Ada, SE_Buffer);
    Last_Read   : Stream_Element_Offset;
  begin
    if workaround_possible then
      Read(Stream(file).all, SE_Buffer, Last_Read);
      actually_read:= Natural(Last_Read);
    else
      if End_Of_File(file) then
        actually_read:= 0;
      else
        actually_read:=
          Integer'Min( buffer'Length, Integer(Size(file) - Index(file) + 1) );
        Byte_buffer'Read(
          Stream(file),
          buffer(buffer'First .. buffer'First + actually_read - 1)
        );
      end if;
    end if;
  end Block_Read;

  procedure Block_Write(
    stream : in out Ada.Streams.Root_Stream_Type'Class;
    buffer : in     Byte_buffer
  )
  is
    pragma Inline(Block_Write);
    SE_Buffer   : Stream_Element_Array (1 .. buffer'Length);
    for SE_Buffer'Address use buffer'Address;
    pragma Import (Ada, SE_Buffer);
  begin
    if workaround_possible then
      Ada.Streams.Write(stream, SE_Buffer);
    else
      Byte_buffer'Write(stream'Access, buffer);
      --  ^ This was 30x to 70x slower on GNAT 2009
      --    Test in the Zip-Ada project.
    end if;
  end Block_Write;

  --  Copy a whole file into a stream, using a temporary buffer
  procedure Copy_file(
    file_name  : String;
    into       : in out Ada.Streams.Root_Stream_Type'Class;
    buffer_size: Positive:= 1024*1024
  )
  is
    f: File_Type;
    buf: Byte_buffer(1..buffer_size);
    actually_read: Natural;
  begin
    Open(f, In_File, file_name);
    loop
      Block_Read(f, buf, actually_read);
      exit when actually_read = 0; -- this is expected
      Block_Write(into, buf(1..actually_read));
    end loop;
    Close(f);
  end Copy_file;

  procedure W(pdf : in out PDF_Out_Stream'Class; s: String) is
  pragma Inline(W);
  begin
    String'Write(pdf.pdf_stream, s);
  end W;

  NL: constant Character:= ASCII.LF;

  procedure WL(pdf : in out PDF_Out_Stream'Class; s: String) is
  begin
    W(pdf, s & NL);
  end WL;

  procedure No_Nowhere(pdf : in out PDF_Out_Stream'Class) is
  begin
    if pdf.zone = nowhere then
      New_Page(pdf);
    end if;
  end No_Nowhere;

  --  Delayed output, for internal PDF's "stream" object

  procedure Wd(pdf : in out PDF_Out_Stream'Class; s: String) is
  pragma Inline(Wd);
  begin
    No_Nowhere (pdf);
    Append(pdf.stream_obj_buf, s);
  end Wd;

  procedure WLd(pdf : in out PDF_Out_Stream'Class; s: String) is
  pragma Inline(WLd);
  begin
    Wd(pdf, s & NL);
  end WLd;

  --  External stream index

  function Buffer_index(pdf : PDF_Out_Stream'Class) return Ada.Streams.Stream_IO.Count is
  begin
    return Index(pdf) - pdf.start_index;
  end Buffer_index;

  function Img(p: Integer) return String is
    s: constant String:= Integer'Image(p);
  begin
    if p < 0 then
      return s;
    else
      return s(s'First+1..s'Last); -- Skip the *%"%! front space
    end if;
  end Img;

  function Img(p: PDF_Index_Type) return String is
    s: constant String:= PDF_Index_Type'Image(p);
  begin
    if p < 0 then
      return s;
    else
      return s(s'First+1..s'Last); -- Skip the *%"%! front space
    end if;
  end Img;

  package RIO is new Ada.Text_IO.Float_IO(Real);

  --  Compact real number image, taken from TeXCAD (TeX_Number in tc.adb)
  --
  function Img(x: Real; prec: Positive:= Real'Digits) return String is
    s: String(1 .. 20 + prec);
    na,nb,np: Natural;
  begin
    RIO.Put(s,x,prec,0);
    na:= s'First;
    nb:= s'Last;
    np:= 0;
    for i in s'Range loop
      case s(i) is
        when '.' => np:= i; exit;  --   Find a decimal point
        when ' ' => na:= i+1;      -- * Trim spaces on left
        when others => null;
      end case;
    end loop;
    if np > 0 then
      while nb > np and then s(nb)='0' loop
        nb:= nb-1;                 -- * Remove extra '0's
      end loop;
      if nb = np then
        nb:= nb-1;                 -- * Remove '.' if it is at the end
      elsif s(na..np-1)= "-0" then
        na:= na+1;
        s(na):= '-';               -- * Reduce "-0.x" to "-.x"
      elsif s(na..np-1)= "0" then
        na:= na+1;                 -- * Reduce "0.x" to ".x"
      end if;
    end if;
    return s(na..nb);
  end Img;

  function "+"(P1,P2: Point) return Point is
  begin
    return ( P1.x+P2.x, P1.y+P2.y );
  end "+";

  function "*"(f: Real; P: Point) return Point is
  begin
    return ( f * P.x, f * P.y );
  end "*";

  function "+"(P: Point; r: Rectangle) return Rectangle is
  begin
    return ( P.x + r.x_min, P.y + r.y_min, r.width, r.height );
  end "+";

  function "*"(f: Real; r: Rectangle) return Rectangle is
  begin
    return ( r.x_min, r.y_min, f * r.width, f * r.height );
  end "*";

  function X_Max(r: Rectangle) return Real is
  begin
    return r.x_min + r.width;
  end X_Max;

  function Y_Max(r: Rectangle) return Real is
  begin
    return r.y_min + r.height;
  end Y_Max;

  type Abs_Rel_Mode is (absolute, relative);

  function Img(p: Point) return String is
  begin
    return Img(p.x) & ' ' & Img(p.y);
  end Img;

  function Img(box: Rectangle; mode: Abs_Rel_Mode) return String is
  begin
    case mode is
      when absolute =>
        return Img(box.x_min) & ' ' & Img(box.y_min) & ' ' &
               Img(X_Max(box)) & ' ' & Img(Y_Max(box)) & ' ';
      when relative =>
        return Img(box.x_min) & ' ' & Img(box.y_min) & ' ' &
               Img(box.width) & ' ' & Img(box.height) & ' ';
    end case;
  end Img;

  procedure Dispose is new Ada.Unchecked_Deallocation(Offset_table, p_Offset_table);

  procedure New_fixed_index_object(pdf : in out PDF_Out_Stream'Class; idx: PDF_Index_Type) is
    new_table: p_Offset_table;
  begin
    if pdf.object_offset = null then
      pdf.object_offset:= new Offset_table(1..idx);
    elsif pdf.object_offset'Last < idx then
      new_table:= new Offset_table(1..idx*2);
      new_table(1..pdf.object_offset'Last):= pdf.object_offset.all;
      Dispose(pdf.object_offset);
      pdf.object_offset:= new_table;
    end if;
    pdf.object_offset(idx):= Buffer_index(pdf);
    WL(pdf, Img(idx) & " 0 obj");
  end New_fixed_index_object;

  procedure New_object(pdf : in out PDF_Out_Stream'Class) is
  begin
    pdf.objects:= pdf.objects + 1;
    New_fixed_index_object(pdf, pdf.objects);
  end New_object;

  producer: constant String :=
    "Ada PDF Writer " & version & ", ref: " & reference & ", " & web &
    " , using GID (Generic Image Decoder) version " & GID.version;

  procedure Write_PDF_header(pdf : in out PDF_Out_Stream'Class) is
  begin
    pdf.is_created:= True;
    pdf.start_index:= Index(pdf);
    case pdf.format is
      when PDF_1_3 =>
        WL(pdf, "%PDF-1.3");
        Byte_buffer'Write(pdf.pdf_stream, (16#25#,16#C2#,16#A5#,16#C2#,16#B1#,16#C3#,16#AB#,10));
    end case;
    WL(pdf, "%  --  Produced by " & producer);
  end Write_PDF_header;

  procedure New_substream(pdf : in out PDF_Out_Stream'Class) is
  begin
    pdf.stream_obj_buf:= Null_Unbounded_String;
  end New_substream;

  procedure Finish_substream(pdf : in out PDF_Out_Stream'Class) is
  begin
    WL(pdf, "  << /Length" & Integer'Image(Length(pdf.stream_obj_buf)) & " >>");
    --  Length could be alternatively stored in next object,
    --  so we wouldn't need to buffer the stream - see 7.3.10, Example 3.
    --  But we prefer the buffered version, which could be compressed in a future version
    --  of this package.
    WL(pdf, "stream");
    WL(pdf, To_String(pdf.stream_obj_buf));
    WL(pdf, "endstream");
  end Finish_substream;

  --  Internal - test page for experimenting PDF constructs (and how Adobe Reader reacts to them)
  --
  procedure Test_Page(pdf: in out PDF_Out_Stream'Class) is
  begin
    WLd(pdf, "10 10 200 200 re S"); -- rectangle, stroke
    WLd(pdf, "  BT");            --  Begin Text object (9.4). Text matrix and text line matrix:= I
    WLd(pdf, "    /Ada_PDF_Std_Font_Helvetica 24 Tf");   --  F1 font, 24 pt size (9.3 Text State Parameters and Operators)
    WLd(pdf, "    0.5 0 0 rg");  --  red, nonstroking colour (Table 74)
    WLd(pdf, "    0.25 G") ;     --  25% gray stroking colour (Table 74)
    WLd(pdf, "    2 Tr");        --  Tr: Set rendering mode as "Fill, then stroke text" (Table 106)
    WLd(pdf, "    20 539 Td");
    WLd(pdf, "    (Hello World !) Tj"); -- Tj: Show a text string (9.4.3 Text-Showing Operators)
    WLd(pdf, "    16 TL");       --  TL: set text leading (distance between lines, 9.3.5)
    WLd(pdf, "    T*");          --  T*: Move to the start of the next line (9.4.2)
    WLd(pdf, "    20 20 200 200 re S"); -- rectangle, stroke (within text region)
    WLd(pdf, "    /Ada_PDF_Std_Font_Helvetica-Oblique 12 Tf");
    WLd(pdf, "    0 Tr");        --  Tr: Set rendering mode as default: "Fill text" (Table 106)
    WLd(pdf, "    0 g");         --  black (default)
    WLd(pdf, "    (Subtitle here.) Tj T*");
    WLd(pdf, "  ET");           --  End Text
    WLd(pdf, "30 30 200 200 re S"); -- rectangle, stroke
    WLd(pdf, "  BT");
    WLd(pdf, "    5 5 Td (Second text chunk here.) Tj T*");
    WLd(pdf, "  ET");
    WLd(pdf, "40 40 240 240 re S"); -- rectangle, stroke
    WLd(pdf, "15 15 Td (Text chunk not within BT/ET.) Tj");
  end Test_Page;

  test_page_mode: constant Boolean:= False;

  procedure Insert_PDF_Font_Selection_Code(pdf: in out PDF_Out_Stream) is
  begin
    Insert_Text_PDF_Code(pdf,
      PDF_Out.Fonts.Current_Font_Dictionary_Name(pdf) &
      ' ' & Img(pdf.font_size) & " Tf " &  --  Tf: 9.3 Text State Parameters and Operators
      Img(pdf.font_size * pdf.line_spacing) & " TL"  --  TL: set text leading (9.3.5)
    );
  end Insert_PDF_Font_Selection_Code;

  procedure Font(pdf: in out PDF_Out_Stream; f: Standard_Font_Type) is
  begin
    pdf.current_font:= f;
    Insert_PDF_Font_Selection_Code(pdf);
  end Font;

  procedure Font_Size(pdf: in out PDF_Out_Stream; size: Real) is
  begin
    pdf.font_size:= size;
    Insert_PDF_Font_Selection_Code(pdf);
  end Font_Size;

  procedure Line_Spacing(pdf: in out PDF_Out_Stream; factor: Real) is
  begin
    pdf.line_spacing:= factor;
    Insert_PDF_Font_Selection_Code(pdf);
  end Line_Spacing;

  procedure Line_Spacing_Pt(pdf: in out PDF_Out_Stream; pt: Real) is
  begin
    pdf.line_spacing:= pt / pdf.font_size;
    --  !! This assumes that the font size is in Point (pt) units.
    Insert_PDF_Font_Selection_Code(pdf);
  end Line_Spacing_Pt;

  procedure Begin_text(pdf: in out PDF_Out_Stream'Class) is
  begin
    WLd(pdf,  "  BT");  --  Begin Text object (9.4.1, Table 107)
  end Begin_text;

  procedure End_text(pdf: in out PDF_Out_Stream'Class) is
  begin
    WLd(pdf,  "  ET");
  end End_text;

  procedure Dispose is new Ada.Unchecked_Deallocation(Page_table, p_Page_table);

  procedure Flip_to (pdf: in out PDF_Out_Stream'Class; new_state: Text_or_graphics) is
  begin
    No_Nowhere (pdf);
    --  WLd(pdf,  " % Text_or_graphics before: " & pdf.text_switch'Image);
    if pdf.text_switch /= new_state then
      pdf.text_switch := new_state;
      case new_state is
        when text     => Begin_text(pdf);
        when graphics => End_text(pdf);
      end case;
    end if;
    --  WLd(pdf,  " % Text_or_graphics after: " & pdf.text_switch'Image);
  end Flip_to;

  procedure New_Page(pdf: in out PDF_Out_Stream) is
    new_table: p_Page_table;
  begin
    if pdf.zone /= nowhere then
      Finish_Page(pdf);
    end if;
    pdf.last_page:= pdf.last_page + 1;
    pdf.current_line:= 1;
    pdf.current_col:= 1;
    PDF_Out.Images.Clear_local_resource_flags(pdf);
    --
    --  Page descriptor object:
    --
    New_object(pdf);
    if pdf.page_idx = null then
      pdf.page_idx:= new Page_table(1..pdf.last_page);
    elsif pdf.page_idx'Last < pdf.last_page then
      new_table:= new Page_table(1..pdf.last_page*2);
      new_table(1..pdf.page_idx'Last):= pdf.page_idx.all;
      Dispose(pdf.page_idx);
      pdf.page_idx:= new_table;
    end if;
    pdf.page_idx(pdf.last_page):= pdf.objects;
    --  Table 30 (7.7.3.3 Page Objects) for options
    WL(pdf, "  <</Type /Page");
    WL(pdf, "    /Parent " & Img(pages_idx) & " 0 R");
    --  Contents stream object is object number n+1 (our choice):
    WL(pdf, "    /Contents " & Img(pdf.objects + 1) & " 0 R");
    --  Resources: a dictionary containing any resources required by the page.
    --  Resources object is object number n+2 (our choice):
    WL(pdf, "    /Resources " & Img(pdf.objects + 2) & " 0 R");
    WL(pdf, "    /MediaBox [" & Img(pdf.page_box, absolute) & ']');
    WL(pdf, "  >>");
    WL(pdf, "endobj");
    --  Page contents object:
    --
    New_object(pdf);
    New_substream(pdf);
    if test_page_mode then
      Test_Page(pdf);
    else
      pdf.zone:= in_page;
      Insert_PDF_Font_Selection_Code(pdf);
      pdf.zone:= in_header;
      --  PDF_Out_Stream'Class: make the call to Page_Header dispatching
      Page_Header(PDF_Out_Stream'Class(pdf));
    end if;
    pdf.zone:= in_page;
    Text_XY(pdf, pdf.page_margins.left, Y_Max(pdf.page_box) - pdf.page_margins.top);
  end New_Page;

  procedure Finish_Page(pdf: in out PDF_Out_Stream) is

    appended_object_idx: PDF_Index_Type;

    procedure Image_Item(dn: in out Dir_node) is
      img_obj: PDF_Index_Type;
    begin
      if dn.local_resource then
        if dn.pdf_object_index = 0 then
          img_obj:= appended_object_idx;
          appended_object_idx:= appended_object_idx + 1;
        else
          img_obj:= dn.pdf_object_index;  --  image has been loaded for a previous page
        end if;
        WL(pdf, Image_name(dn.image_index) & ' ' & Img(img_obj) & " 0 R");
      end if;
    end Image_Item;

    procedure Image_List is new PDF_Out.Images.Traverse_private(Image_Item);

  begin
    if pdf.zone = nowhere then
      return;  --  We are already "between pages"
    end if;
    if test_page_mode then
      null;  --  Nothing to do anymore with test page
    else
      pdf.zone:= in_footer;
      --  PDF_Out_Stream'Class: make the call to Page_Header dispatching
      Page_Footer(PDF_Out_Stream'Class(pdf));
      Flip_to (pdf, graphics);
    end if;
    pdf.zone:= nowhere;
    Finish_substream(pdf);
    WL(pdf, "endobj");  --  end of page contents.
    --  Resources Dictionary (7.8.3) for the page just finished:
    New_object(pdf);
    WL(pdf, "<<");
    --  Font resources:
    PDF_Out.Fonts.Font_Dictionary(pdf);
    appended_object_idx:= pdf.objects + 1;  --  Images contents to be appended after this object
    --  Image resources:
    WL(pdf, "  /XObject <<");
    Image_List(pdf);
    WL(pdf, "  >>");
    WL(pdf, ">>");
    WL(pdf, "endobj");  --  end of Resources
    PDF_Out.Images.Insert_unloaded_local_images(pdf);
  end Finish_Page;

  procedure Put(pdf  : in out PDF_Out_Stream;
                num  : in Real;
                fore : in Ada.Text_IO.Field := Real_IO.Default_Fore;
                aft  : in Ada.Text_IO.Field := Real_IO.Default_Aft;
                exp  : in Ada.Text_IO.Field := Real_IO.Default_Exp
            )
  is
  begin
    if exp = 0 then
      declare
        s: String(1 .. fore + 1 + aft);  --  "  123.45"
      begin
        Real_IO.Put(s, num, aft, exp);
        Put(pdf, s);
      end;
    else
      declare
        s: String(1 .. fore + 1 + aft + 1 + exp);  --  "  1.234E-01"
      begin
        Real_IO.Put(s, num, aft, exp);
        Put(pdf, s);
      end;
    end if;
  end Put;

  procedure Put(pdf   : in out PDF_Out_Stream;
                num   : in Integer;
                width : in Ada.Text_IO.Field := 0; -- ignored
                base  : in Ada.Text_IO.Number_Base := 10
            )
  is
  begin
    if base = 10 then
      Put(pdf, Img(num));
    else
      declare
        use Ada.Strings.Fixed;
        s: String(1..50 + 0*width);
        --  "0*width" is just to skip a warning about width being unused
        package IIO is new Ada.Text_IO.Integer_IO(Integer);
      begin
        IIO.Put(s, num, Base => base);
        Put(pdf, Trim(s, Ada.Strings.Left));
      end;
    end if;
  end Put;

  procedure Put(pdf: in out PDF_Out_Stream; str : String) is
  begin
    if test_page_mode then
      null;  --  Nothing to do (test page instead)
    else
      Insert_Text_PDF_Code(pdf, '(' & str & ") Tj");
    end if;
  end Put;

  procedure Put(pdf: in out PDF_Out_Stream; str : Unbounded_String) is
  begin
    Put(pdf, To_String(str));
  end Put;

  procedure Put_Line(pdf  : in out PDF_Out_Stream;
                     num  : in Real;
                     fore : in Ada.Text_IO.Field := Real_IO.Default_Fore;
                     aft  : in Ada.Text_IO.Field := Real_IO.Default_Aft;
                     exp  : in Ada.Text_IO.Field := Real_IO.Default_Exp
            ) is
  begin
    Put(pdf, num, fore, aft, exp);
    New_Line(pdf);
  end Put_Line;

  procedure Put_Line(pdf   : in out PDF_Out_Stream;
                     num   : in Integer;
                     width : in Ada.Text_IO.Field := 0; -- ignored
                     base  : in Ada.Text_IO.Number_Base := 10
            )
  is
  begin
    Put(pdf, num, width, base);
    New_Line(pdf);
  end Put_Line;

  procedure Put_Line(pdf: in out PDF_Out_Stream; str : String) is
  begin
    Put(pdf, str);
    New_Line(pdf);
  end Put_Line;

  procedure Put_Line(pdf: in out PDF_Out_Stream; str : Unbounded_String) is
  begin
    Put_Line(pdf, To_String(str));
  end Put_Line;

  procedure New_Line(pdf: in out PDF_Out_Stream; Spacing : Positive := 1) is
  begin
    pdf.current_line:= pdf.current_line + 1;
    pdf.current_col:= 1;
    if test_page_mode then
      null;  --  Nothing to do (test page instead)
    else
      for i in 1..Spacing loop
        Insert_Text_PDF_Code(pdf, "T*");
      end loop;
    end if;
  end New_Line;

  procedure Text_XY(pdf: in out PDF_Out_Stream; x,y: Real) is
  begin
    Flip_to (pdf, text);
    --  The following explicit End_text, Begin_text are just
    --  for resetting the text matrices (hence, position and orientation).
    End_text(pdf);
    Begin_text(pdf);
    Insert_PDF_Code(pdf, Img(x) & ' ' & Img(y) & " Td");  --  Td: 9.4.2 Text-Positioning Operators
    pdf.current_line:= 1;
    pdf.current_col:= 1;
  end Text_XY;

  procedure Put_XY(pdf: in out PDF_Out_Stream; x,y: Real; str : String) is
  begin
    Text_XY(pdf, x,y);
    Put(pdf, str);
  end Put_XY;

  function Col(pdf: in PDF_Out_Stream) return Positive is
  begin
    return pdf.current_col;
  end Col;

  function Line(pdf: in PDF_Out_Stream) return Positive is
  begin
    return pdf.current_line;
  end Line;

  function Page(pdf: in PDF_Out_Stream) return Natural is
  begin
    return Natural(pdf.last_page);  --  Issue if Integer is 16-bit and last_page > 2**15-1
  end Page;

  procedure Color(pdf: in out PDF_Out_Stream; c: Color_Type) is
  begin
    Insert_PDF_Code(pdf, Img(c.red) & ' ' & Img(c.green) & ' ' & Img(c.blue) & " rg");
    --  rg = nonstroking colour (Table 74)
  end Color;

  procedure Stroking_Color(pdf: in out PDF_Out_Stream; c: Color_Type) is
  begin
    Insert_PDF_Code(pdf, Img(c.red) & ' ' & Img(c.green) & ' ' & Img(c.blue) & " RG");
    --  RG = nonstroking colour (Table 74)
  end Stroking_Color;

  procedure Text_Rendering_Mode(pdf: in out PDF_Out_Stream; r: Rendering_Mode) is
  begin
    Insert_Text_PDF_Code(pdf, Img(Integer(Rendering_Mode'Pos(r))) & " Tr");
    --  Tr = Set rendering mode (Table 106)
  end Text_Rendering_Mode;

  function Image_name(i: Positive) return String is
  begin
    return "/Ada_PDF_Img" & Img(i);
  end Image_name;

  procedure Image(pdf: in out PDF_Out_Stream; file_name: String; target: Rectangle) is
    image_index: Positive;  --  Index in the list of images
  begin
    No_Nowhere (pdf);
    PDF_Out.Images.Image_ref(pdf, file_name, image_index);
    Insert_Graphics_PDF_Code(pdf, "q " &
      Img(target.width) & " 0 0 " & Img(target.height) &
      ' ' & Img(target.x_min) & ' ' & Img(target.y_min) & " cm " &  --  cm: Table 57
      Image_name(image_index) & " Do Q"
    );
  end Image;

  function Get_pixel_dimensions(image_file_name: String) return Rectangle is
  begin
    return PDF_Out.Images.Get_pixel_dimensions(image_file_name);
  end Get_pixel_dimensions;

  -----------------------
  --  Vector graphics  --
  -----------------------

  procedure Line_Width(pdf: in out PDF_Out_Stream; width: Real) is
  begin
    Insert_Graphics_PDF_Code(pdf, Img(width) & " w");
  end Line_Width;

  procedure Single_Line(pdf: in out PDF_Out_Stream; from, to: Point) is
  begin
    Insert_Graphics_PDF_Code(pdf,
      Img(from.x) & ' ' & Img(from.y) & " m " &
      Img(to.x) & ' ' & Img(to.y) & " l s"
    );
  end Single_Line;

  --    Table 59 - Path Construction Operators (8.5.2)
  --    Table 60 - Path-Painting Operators (8.5.3.1)

  inside_path_rule_char: constant array(Inside_path_rule) of Character:= (
    nonzero_winding_number => ' ',
    even_odd               => '*'
  );

  path_drawing_operator: constant array(Path_Rendering_Mode) of Character:= (
    fill             => 'F',
    stroke           => 'S',
    fill_then_stroke => 'B'
  );

  procedure Draw(pdf: in out PDF_Out_Stream; what: Rectangle; rendering: Path_Rendering_Mode) is
  begin
    Insert_Graphics_PDF_Code(pdf, Img(what, relative) & " re " & path_drawing_operator(rendering));
  end Draw;

  procedure Move(pdf: in out PDF_Out_Stream; to: Point) is
  begin
    Insert_Graphics_PDF_Code(pdf, Img(to) & " m");  --  m operator (Table 59)
  end Move;

  procedure Line(pdf: in out PDF_Out_Stream; to: Point) is
  begin
    Insert_Graphics_PDF_Code(pdf, Img(to) & " l");
  end Line;

  procedure Cubic_Bezier(pdf: in out PDF_Out_Stream; control_1, control_2: Point; to: Point) is
  begin
    Insert_Graphics_PDF_Code(
      pdf,
      Img(control_1) & ' ' &
      Img(control_2) & ' ' &
      Img(to) & " c"
    );
  end Cubic_Bezier;

  procedure Finish_Path(
    pdf        : in out PDF_Out_Stream;
    close_path :        Boolean;
    rendering  :        Path_Rendering_Mode;  --  fill, stroke, or both
    rule       :        Inside_path_rule
  )
  is
    cmd: String:= path_drawing_operator(rendering) & inside_path_rule_char(rule);
  begin
    if close_path then
      cmd:= To_Lower(cmd);
    end if;
    --  Insert the s, S, f, f*, b, b*, B, B* of Table 60 - Path-Painting Operators (8.5.3.1)
    if cmd = "s*" or cmd = "S*" or cmd = "F " or cmd = "F*" then
      Insert_Graphics_PDF_Code(pdf, "n");  --  End the path object without filling or stroking it.
    else
      Insert_Graphics_PDF_Code(pdf, cmd);
    end if;
  end Finish_Path;

  -----------------------------
  --  Direct code insertion  --
  -----------------------------

  procedure Insert_PDF_Code(pdf: in out PDF_Out_Stream; code: String) is
  begin
    WLd(pdf, "    " & code);  --  Indentation is just cosmetic...
  end Insert_PDF_Code;

  procedure Insert_Text_PDF_Code(pdf: in out PDF_Out_Stream; code: String) is
  begin
    Flip_to (pdf, text);
    Insert_PDF_Code(pdf,code);
  end Insert_Text_PDF_Code;

  procedure Insert_Graphics_PDF_Code(pdf: in out PDF_Out_Stream; code: String) is
  begin
    Flip_to (pdf, graphics);
    Insert_PDF_Code(pdf,code);
  end Insert_Graphics_PDF_Code;

  --  Table 317 - Entries in the document information dictionary (14.3.3)

  procedure Title(pdf: in out PDF_Out_Stream; s: String) is
  begin
    pdf.doc_title:= To_Unbounded_String(s);
  end Title;

  procedure Author(pdf: in out PDF_Out_Stream; s: String) is
  begin
    pdf.doc_author:= To_Unbounded_String(s);
  end Author;

  procedure Subject(pdf: in out PDF_Out_Stream; s: String) is
  begin
    pdf.doc_subject:= To_Unbounded_String(s);
  end Subject;

  procedure Keywords(pdf: in out PDF_Out_Stream; s: String) is
  begin
    pdf.doc_keywords:= To_Unbounded_String(s);
  end Keywords;

  procedure Creator_Application(pdf: in out PDF_Out_Stream; s: String) is
  begin
    pdf.doc_creator:= To_Unbounded_String(s);
  end Creator_Application;

  procedure Page_Header(pdf : in out PDF_Out_Stream) is
  begin
    null;  --  Default header is empty.
  end Page_Header;

  procedure Page_Footer(pdf : in out PDF_Out_Stream) is
  begin
    null;  --  Default footer is empty.
  end Page_Footer;

  procedure Left_Margin(pdf : out PDF_Out_Stream; pts: Real) is
  begin
    pdf.page_margins.left:= pts;
  end Left_Margin;

  function Left_Margin(pdf : PDF_Out_Stream) return Real is
  begin
    return pdf.page_margins.left;
  end Left_Margin;

  procedure Right_Margin(pdf : out PDF_Out_Stream; pts: Real) is
  begin
    pdf.page_margins.right:= pts;
  end Right_Margin;

  function Right_Margin(pdf : PDF_Out_Stream) return Real is
  begin
    return pdf.page_margins.right;
  end Right_Margin;

  procedure Top_Margin(pdf : out PDF_Out_Stream; pts: Real) is
  begin
    pdf.page_margins.top:= pts;
  end Top_Margin;

  function Top_Margin(pdf : PDF_Out_Stream) return Real is
  begin
    return pdf.page_margins.top;
  end Top_Margin;

  procedure Bottom_Margin(pdf : out PDF_Out_Stream; pts: Real) is
  begin
    pdf.page_margins.bottom:= pts;
  end Bottom_Margin;

  function Bottom_Margin(pdf : PDF_Out_Stream) return Real is
  begin
    return pdf.page_margins.bottom;
  end Bottom_Margin;

  procedure Margins(pdf : out PDF_Out_Stream; new_margins: Margins_Type) is
  begin
    pdf.page_margins:= new_margins;
  end Margins;

  function Margins(pdf : PDF_Out_Stream) return Margins_Type is
  begin
    return pdf.page_margins;
  end Margins;

  procedure Page_Setup(pdf : in out PDF_Out_Stream; layout: Rectangle) is
    mb_x_min, mb_y_min, mb_x_max, mb_y_max: Real;
  begin
    pdf.page_box:= layout;
    mb_x_min:= Real'Min(pdf.maximum_box.x_min, layout.x_min);
    mb_y_min:= Real'Min(pdf.maximum_box.y_min, layout.y_min);
    mb_x_max:= Real'Max(X_Max(pdf.maximum_box), X_Max(layout));
    mb_y_max:= Real'Max(Y_Max(pdf.maximum_box), Y_Max(layout));
    pdf.maximum_box:=
      ( x_min  => mb_x_min,
        y_min  => mb_y_min,
        width  => mb_x_max - mb_x_min,
        height => mb_y_max - mb_y_min
      );
  end Page_Setup;

  function Layout(pdf : PDF_Out_Stream) return Rectangle is
  begin
    return pdf.page_box;
  end Layout;

  procedure Reset(
    pdf        : in out PDF_Out_Stream'Class;
    PDF_format :        PDF_type:= Default_PDF_type
  )
  is
    dummy_pdf_with_defaults: PDF_Out_Pre_Root_Type;
  begin
    --  Check if we are trying to re-use a half-finished object (ouch!):
    if pdf.is_created and not pdf.is_closed then
      raise PDF_stream_not_closed;
    end if;
    --  We will reset everything with defaults, except this:
    dummy_pdf_with_defaults.format:= PDF_format;
    --  Now we reset pdf:
    PDF_Out_Pre_Root_Type(pdf):= dummy_pdf_with_defaults;
    --  Set a default title (replaced when procedure Title is called).
    --  In Adobe Reader, this content can be copied to the clipboard.
    pdf.doc_title := "Document created with: " & To_Unbounded_String(producer);
  end Reset;

  procedure Finish(pdf : in out PDF_Out_Stream) is

    info_idx, cat_idx: PDF_Index_Type;

    procedure Info is
    begin
      New_object(pdf);
      info_idx:= pdf.objects;
      WL(pdf, "  << /Producer (" & producer & ')');
      WL(pdf, "     /Title (" & To_String(pdf.doc_title) & ')');
      WL(pdf, "     /Author (" & To_String(pdf.doc_author) & ')');
      WL(pdf, "     /Subject (" & To_String(pdf.doc_subject) & ')');
      WL(pdf, "     /Keywords (" & To_String(pdf.doc_keywords) & ')');
      WL(pdf, "     /Creator (" & To_String(pdf.doc_creator) & ')');
      WL(pdf, "  >>");
      WL(pdf, "endobj");
    end Info;

    procedure Pages_dictionary is
    begin
      New_fixed_index_object(pdf, pages_idx);
      WL(pdf, "  << /Type /Pages");
      W(pdf,  "     /Kids [");
      for p in 1..pdf.last_page loop
        W(pdf, Img(pdf.page_idx(p)) & " 0 R ");
      end loop;
      WL(pdf, "]");
      if pdf.last_page > 0 then
        WL(pdf, "     /Count " & Img(pdf.last_page));
      end if;
      WL(pdf, "     /MediaBox [" & Img(pdf.maximum_box, absolute) & ']'
      );
      --  7.7.3.3 Page Objects - MediaBox
      --  Boundaries of the physical medium on which the page shall be displayed or printed
      --  7.7.3.4 Inheritance of Page Attributes
      --  Global page size, lower-left to upper-right, measured in points
      --  Bounding box of all pages
      WL(pdf, "  >>");
      WL(pdf, "endobj");
    end Pages_dictionary;

    procedure Catalog_dictionary is
    begin
      New_object(pdf);
      cat_idx:= pdf.objects;
      WL(pdf, "  << /Type /Catalog");
      WL(pdf, "     /Pages " & Img(pages_idx) & " 0 R");
      if pdf.last_page > 0 then
        --  Open the document on page 1, fit the
        --  entire page within the window (Table 151):
        WL(pdf, "     /OpenAction [" & Img(pdf.page_idx(1)) & " 0 R /Fit]");
      end if;
      WL(pdf, "  >>");
      WL(pdf, "endobj");
    end Catalog_dictionary;

    procedure Trailer is
    begin
      WL(pdf, "trailer");
      WL(pdf, "  << /Root " & Img(cat_idx) & " 0 R");
      WL(pdf, "     /Size " & Img(pdf.objects+1));
      WL(pdf, "     /Info " & Img(info_idx) & " 0 R");
      WL(pdf, "  >>");
    end Trailer;

    xref_offset: Ada.Streams.Stream_IO.Count;

    procedure XRef is
      s10: String(1..10);
    begin
      xref_offset:= Buffer_index(pdf);
      WL(pdf, "xref");
      WL(pdf, "0 " & Img(pdf.objects+1));
      WL(pdf, "0000000000 65535 f ");
      for i in 1..pdf.objects loop
        CIO.Put(s10, pdf.object_offset(i));
        for n in s10'Range loop
          if s10(n)=' ' then
            s10(n):= '0';
          end if;
        end loop;
        WL(pdf, s10 & " 00000 n ");  --   <-- the trailing space is needed!
      end loop;
    end XRef;

  begin
    if pdf.last_page = 0 then
      --  No page ? Then make quickly a blank page.
      New_Page(pdf);
    end if;
    Finish_Page(pdf);
    Info;
    Pages_dictionary;
    Catalog_dictionary;
    XRef;
    Trailer;
    WL(pdf, "startxref"); -- offset of xref
    WL(pdf, Img(Integer(xref_offset)));
    WL(pdf, "%%EOF");
    Dispose(pdf.page_idx);
    Dispose(pdf.object_offset);
    PDF_Out.Images.Clear_image_directory(pdf);
    pdf.is_closed:= True;
  end Finish;

  ----------------------
  -- Output to a file --
  ----------------------

  procedure Create(
    pdf        : in out PDF_Out_File;
    file_name  :        String;
    PDF_format :        PDF_type:= Default_PDF_type
  )
  is
  begin
    Reset(pdf, PDF_format);
    pdf.pdf_file:= new Ada.Streams.Stream_IO.File_Type;
    Create(pdf.pdf_file.all, Out_File, file_name);
    pdf.file_name:= To_Unbounded_String(file_name);
    pdf.pdf_stream:= PDF_Raw_Stream_Class(Stream(pdf.pdf_file.all));
    Write_PDF_header(pdf);
  end Create;

  procedure Close(pdf : in out PDF_Out_File) is
    procedure Dispose is new
      Ada.Unchecked_Deallocation(Ada.Streams.Stream_IO.File_Type, PDF_file_acc);
  begin
    Finish(PDF_Out_Stream(pdf));
    if pdf.file_name /= "nul" then  --  Test needed for OA 7.2.2 (Close raises Use_Error)
      Close(pdf.pdf_file.all);
    end if;
    Dispose(pdf.pdf_file);
  end Close;

  --  Set the index on the file
  overriding procedure Set_Index (pdf: in out PDF_Out_File;
                                  to :        Ada.Streams.Stream_IO.Positive_Count)
  is
  begin
    Ada.Streams.Stream_IO.Set_Index(pdf.pdf_file.all, to);
  end Set_Index;

  --  Return the index of the file
  overriding function Index (pdf: PDF_Out_File) return Ada.Streams.Stream_IO.Count
  is
  begin
    return Ada.Streams.Stream_IO.Index(pdf.pdf_file.all);
  end Index;

  function Is_Open(pdf : in PDF_Out_File) return Boolean is
  begin
    if pdf.pdf_file = null then
      return False;
    end if;
    return Ada.Streams.Stream_IO.Is_Open(pdf.pdf_file.all);
  end Is_Open;

  ------------------------
  -- Output to a string --
  ------------------------
  --  Code reused from Zip_Streams

  overriding procedure Read
    (Stream : in out Unbounded_Stream;
     Item   : out Stream_Element_Array;     Last   : out Stream_Element_Offset) is
  begin
    --  Item is read from the stream. If (and only if) the stream is
    --  exhausted, Last will be < Item'Last. In that case, T'Read will
    --  raise an End_Error exception.
    --
    --  Cf: RM 13.13.1(8), RM 13.13.1(11), RM 13.13.2(37) and
    --  explanations by Tucker Taft
    --
    Last:= Item'First - 1;
    --  if Item is empty, the following loop is skipped; if Stream.Loc
    --  is already indexing out of Stream.Unb, that value is also appropriate
    for i in Item'Range loop
      Item(i) := Character'Pos (Element(Stream.Unb, Stream.Loc));
      Stream.Loc := Stream.Loc + 1;
      Last := i;
    end loop;
  exception
    when Ada.Strings.Index_Error =>
      null; -- what could be read has been read; T'Read will raise End_Error
  end Read;

  overriding procedure Write
    (Stream : in out Unbounded_Stream;
     Item   : Stream_Element_Array) is
  begin
    for I in Item'Range loop
      if Length(Stream.Unb) < Stream.Loc then
        Append(Stream.Unb, Character'Val(Item(I)));
      else
        Replace_Element(Stream.Unb, Stream.Loc, Character'Val(Item(I)));
      end if;
      Stream.Loc := Stream.Loc + 1;
    end loop;
  end Write;

  procedure Set_Index (S : access Unbounded_Stream; To : Positive) is
  begin
    if Length(S.Unb) < To then
      for I in Length(S.Unb) .. To loop
        Append(S.Unb, ASCII.NUL);
      end loop;
    end if;
    S.Loc := To;
  end Set_Index;

  function Index (S : access Unbounded_Stream) return Integer is
  begin
    return S.Loc;
  end Index;

  --- ***

  procedure Create(
    pdf        : in out PDF_Out_String;
    PDF_format :        PDF_type:= Default_PDF_type
  )
  is
  begin
    Reset(pdf, PDF_format);
    pdf.pdf_memory:= new Unbounded_Stream;
    pdf.pdf_memory.Unb:= Null_Unbounded_String;
    pdf.pdf_memory.Loc:= 1;
    pdf.pdf_stream:= PDF_Raw_Stream_Class(pdf.pdf_memory);
    Write_PDF_header(pdf);
  end Create;

  procedure Close(pdf : in out PDF_Out_String) is
  begin
    Finish(PDF_Out_Stream(pdf));
  end Close;

  function Contents(pdf: PDF_Out_String) return String is
  begin
    if not pdf.is_closed then
      raise PDF_stream_not_closed;
    end if;
    return To_String(pdf.pdf_memory.Unb);
  end Contents;

  --  Set the index on the PDF string stream
  overriding procedure Set_Index (pdf: in out PDF_Out_String;
                                  to :        Ada.Streams.Stream_IO.Positive_Count)
  is
  begin
    Set_Index(pdf.pdf_memory, Integer(to));
  end Set_Index;

  --  Return the index of the PDF string stream
  overriding function Index (pdf: PDF_Out_String) return Ada.Streams.Stream_IO.Count
  is
  begin
    return Ada.Streams.Stream_IO.Count(Index(pdf.pdf_memory));
  end Index;

end PDF_Out;