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, " <>"); 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;