Source file : pdf_out.adb
with PDF_Out.Fonts,
PDF_Out.Images;
with GID;
with Ada.Characters.Conversions,
Ada.Characters.Handling,
Ada.Strings.Fixed,
Ada.Unchecked_Deallocation;
with Interfaces;
package body PDF_Out is
use Ada.Streams.Stream_IO, Ada.Streams;
use Interfaces;
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 Write_delayed (pdf : in out PDF_Out_Stream'Class; s : String) is
pragma Inline (Write_delayed);
begin
No_Nowhere (pdf);
Append (pdf.stream_obj_buf, s);
end Write_delayed;
procedure WLd (pdf : in out PDF_Out_Stream'Class; s : String) is
pragma Inline (WLd);
begin
Write_delayed (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 Show_Text_String (pdf : in out PDF_Out_Stream; str : String) is
-- 9.4.3 Text-Showing Operators; table 109.
begin
if str'Length > 0 then
Insert_Text_PDF_Code (pdf, '(' & str & ") Tj");
end if;
end Show_Text_String;
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
for i in str'Range loop
-- We scan the string for special characters:
case str (i) is
when ASCII.NUL .. ASCII.HT |
ASCII.VT .. ASCII.US =>
-- Skip special character.
Show_Text_String (pdf, str (str'First .. i - 1));
Put (pdf, str (i + 1 .. str'Last));
return;
when ASCII.LF =>
-- Line Feed character: display string on two or more lines.
Show_Text_String (pdf, str (str'First .. i - 1));
New_Line (pdf);
Put (pdf, str (i + 1 .. str'Last));
return;
when '(' | ')' | '\' =>
-- Insert a Reverse Solidus (backslash, '\') for an escape
-- sequence. See full list in: 7.3.4.2 Literal Strings; table 3.
Show_Text_String (pdf, str (str'First .. i - 1) & '\' & str (i));
Put (pdf, str (i + 1 .. str'Last));
return;
when others =>
null;
end case;
end loop;
Show_Text_String (pdf, str);
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 Put_WS
(pdf : in out PDF_Out_Stream; w_str : Wide_String) is
use Ada.Characters.Conversions;
begin
Put (pdf, To_String (w_str));
end Put_WS;
procedure Put_Line_WS
(pdf : in out PDF_Out_Stream; w_str : Wide_String)
is
use Ada.Characters.Conversions;
begin
Put_Line (pdf, To_String (w_str));
end Put_Line_WS;
procedure Put_Line_WS
(pdf : in out PDF_Out_Stream;
w_str : Ada.Strings.Wide_Unbounded.Unbounded_Wide_String)
is
use Ada.Characters.Conversions, Ada.Strings.Wide_Unbounded;
begin
Put_Line (pdf, To_String (To_Wide_String (w_str)));
end Put_Line_WS;
procedure Put_WWS
(pdf : in out PDF_Out_Stream; ww_str : Wide_Wide_String) is
use Ada.Characters.Conversions;
begin
Put (pdf, To_String (ww_str));
end Put_WWS;
procedure Put_Line_WWS
(pdf : in out PDF_Out_Stream; ww_str : Wide_Wide_String)
is
use Ada.Characters.Conversions;
begin
Put_Line (pdf, To_String (ww_str));
end Put_Line_WWS;
procedure Put_Line_WWS
(pdf : in out PDF_Out_Stream;
ww_str : Ada.Strings.Wide_Wide_Unbounded.Unbounded_Wide_Wide_String)
is
use Ada.Characters.Conversions, Ada.Strings.Wide_Wide_Unbounded;
begin
Put_Line (pdf, To_String (To_Wide_Wide_String (ww_str)));
end Put_Line_WWS;
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 := Ada.Characters.Handling.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;
Ada PDF Writer: Ada package for writing PDF files (.pdf).
Ada programming.
Some news about Ada PDF Writer and other Ada projects
on Gautier's blog.