# encoding: utf-8 # text.rb : Implements PDF text primitives # # Copyright May 2008, Gregory Brown. All Rights Reserved. # # This is free software. Please see the LICENSE and COPYING files for details. require "prawn/core/text" require "prawn/text/box" require "zlib" module Prawn module Text include Prawn::Core::Text VALID_OPTIONS = Prawn::Core::Text::VALID_OPTIONS + [:at, :rotate] # Gets height of text in PDF points. # Same options as text(), except as noted. # Not compatible with :indent_paragraphs option # # Raises Prawn::Errors::UnknownOption if # :indent_paragraphs option included and debug flag is set # def height_of(string, options={}) process_final_gap_option(options) box = Text::Box.new(string, options.merge(:height => 100000000, :document => self)) box.render(:dry_run => true) height = box.height - box.descender height += box.line_height + box.leading - box.ascender if @final_gap height end # If you want text to flow onto a new page or between columns, this is the # method to use. If, instead, if you want to place bounded text outside of # the flow of a document (for captions, labels, charts, etc.), use Text::Box # or its convenience method text_box. # # Draws text on the page. Prawn attempts to wrap the text to fit within your # current bounding box (or margin_box if no bounding box is being used). # Text will flow onto the next page when it reaches the bottom of the # bounding box. Text wrap in Prawn does not re-flow linebreaks, so if you # want fully automated text wrapping, be sure to remove newlines before # attempting to draw your string. # # pdf.text "Will be wrapped when it hits the edge of your bounding box" # pdf.text "This will be centered", :align => :center # pdf.text "This will be right aligned", :align => :right # # If your font contains kerning pairs data that Prawn can parse, the # text will be kerned by default. You can disable this feature by passing # :kerning => false. # # === Text Positioning Details: # # The text is positioned at font.ascender below the baseline, # making it easy to use this method within bounding boxes and spans. # # == Encoding # # Note that strings passed to this function should be encoded as UTF-8. # If you get unexpected characters appearing in your rendered document, # check this. # # If the current font is a built-in one, although the string must be # encoded as UTF-8, only characters that are available in WinAnsi # are allowed. # # If an empty box is rendered to your PDF instead of the character you # wanted it usually means the current font doesn't include that character. # # == Options (default values marked in []) # # :kerning:: boolean. Whether or not to use kerning (if it # is available with the current font) [true] # :size:: number. The font size to use. [current font # size] # :style:: The style to use. The requested style must be part of # the current font familly. [current style] # :indent_paragraphs:: number. The amount to indent the # first line of each paragraph. Omit this # option if you do not want indenting # :align:: :left, :center, or :right. # Alignment within the bounding box [:left] # :valign:: :top, :center, or :bottom. # Vertical alignment within the bounding box [:top] # :leading:: number. Additional space between lines [0] # :final_gap:: boolean. If true, then the space between # each line is included below the last line; # otherwise, document.y is placed just below the # descender of the last line printed [true] # :wrap_block:: proc. A proc used for custom line # wrapping. The proc must accept a single # line of text and an options hash # and return the string from that single line that # can fit on the line under the conditions defined by # options. If omitted, the default wrapping # proc is used. The options hash passed into the # wrap_block proc includes the following options: # :width:: the width available for the # current line of text # :document:: the pdf object # :kerning:: boolean # :size:: the font size # # Raises ArgumentError if :at option included # def text(string, options={}) # we modify the options. don't change the user's hash options = options.dup inspect_options_for_text(options) if @indent_paragraphs string.split("\n").each do |paragraph| options[:skip_encoding] = false remaining_text = draw_indented_line(paragraph, options) options[:skip_encoding] = true if remaining_text == paragraph # we were too close to the bottom of the page to print even one line @bounding_box.move_past_bottom remaining_text = draw_indented_line(paragraph, options) end remaining_text = fill_text_box(remaining_text, options) draw_remaining_text_on_new_pages(remaining_text, options) end else remaining_text = fill_text_box(string, options) options[:skip_encoding] = true draw_remaining_text_on_new_pages(remaining_text, options) end end # Draws text on the page, beginning at the point specified by the :at option # the string is assumed to be pre-formatted to properly fit the page. # # pdf.draw_text "Hello World", :at => [100,100] # pdf.draw_text "Goodbye World", :at => [50,50], :size => 16 # # If your font contains kerning pairs data that Prawn can parse, the # text will be kerned by default. You can disable this feature by passing # :kerning => false. # # === Text Positioning Details: # # Prawn will position your text by the left-most edge of its baseline, and # flow along a single line. (This means that :align will not work) # # == Rotation # # Text can be rotated before it is placed on the canvas by specifying the # :rotate option with a given angle. Rotation occurs counter-clockwise. # # == Encoding # # Note that strings passed to this function should be encoded as UTF-8. # If you get unexpected characters appearing in your rendered document, # check this. # # If the current font is a built-in one, although the string must be # encoded as UTF-8, only characters that are available in WinAnsi # are allowed. # # If an empty box is rendered to your PDF instead of the character you # wanted it usually means the current font doesn't include that character. # # == Options (default values marked in []) # # :at:: [x, y](required). The position at which to start the text # :kerning:: boolean. Whether or not to use kerning (if it # is available with the current font) [true] # :size:: number. The font size to use. [current font # size] # :style:: The style to use. The requested style must be part of # the current font familly. [current style] # # :rotate:: number. The angle to which to rotate text # # Raises ArgumentError if :at option omitted # Raises ArgumentError if :align option included # def draw_text(text, options) # we modify the options. don't change the user's hash options = options.dup inspect_options_for_draw_text(options) # dup because normalize_encoding changes the string text = text.to_s.dup options = @text_options.merge(options) save_font do process_text_options(options) font.normalize_encoding!(text) unless @skip_encoding font_size(options[:size]) { draw_text!(text, options) } end end private def draw_remaining_text_on_new_pages(remaining_text, options) while remaining_text.length > 0 @bounding_box.move_past_bottom previous_remaining_text = remaining_text remaining_text = fill_text_box(remaining_text, options) break if remaining_text == previous_remaining_text end end def draw_indented_line(string, options) indent(@indent_paragraphs) do fill_text_box(string, options.dup.merge(:single_line => true)) end end def fill_text_box(text, options) bottom = @bounding_box.stretchy? ? @margin_box.absolute_bottom : @bounding_box.absolute_bottom options[:height] = y - bottom options[:width] = bounds.width options[:at] = [@bounding_box.left_side - @bounding_box.absolute_left, y - @bounding_box.absolute_bottom] box = Text::Box.new(text, options) remaining_text = box.render self.y -= box.height - box.descender if @final_gap self.y -= box.line_height + box.leading - box.ascender end remaining_text end def inspect_options_for_draw_text(options) if options[:at].nil? raise ArgumentError, "The :at option is required for draw_text" elsif options[:align] raise ArgumentError, "The :align option does not work with draw_text" end Prawn.verify_options(VALID_OPTIONS, options) end def inspect_options_for_text(options) if options[:at] raise ArgumentError, ":at is no longer a valid option with text." + "use draw_text or text_box instead" end process_final_gap_option(options) process_indent_paragraphs_option(options) options[:document] = self end def process_final_gap_option(options) @final_gap = options[:final_gap].nil? || options[:final_gap] options.delete(:final_gap) end def process_indent_paragraphs_option(options) @indent_paragraphs = options[:indent_paragraphs] options.delete(:indent_paragraphs) end def move_text_position(dy) bottom = @bounding_box.stretchy? ? @margin_box.absolute_bottom : @bounding_box.absolute_bottom @bounding_box.move_past_bottom if (y - dy) < bottom self.y -= dy end end end