# 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 "zlib" require "prawn/document/text/box" module Prawn class Document module Text # Draws text on the page. If a point is specified via the +:at+ # option the text will begin exactly at that point, and the string is # assumed to be pre-formatted to properly fit the page. # # pdf.text "Hello World", :at => [100,100] # pdf.text "Goodbye World", :at => [50,50], :size => 16 # # When +:at+ is not specified, 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 # # Wrapping is done by splitting words by spaces by default. If your text # does not contain spaces, you can wrap based on characters instead: # # pdf.text "This will be wrapped by character", :wrap => :character # # 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: # # FIXME: If we go with this of using ascender for TTF and font height # For AFM, we need to document the sucker. # # When using the +:at+ parameter, Prawn will position your text by its # baseline, and flow along a single line. # # When using automatic text flow, Prawn will position your text exactly # font.height *below* the baseline, and space each line of text by # font.height + options[:spacing] (default 0) # # Finally, the drawing position will be moved to the baseline of final # line of text, plus any additional spacing. # # If you wish to position your flowing text by it's baseline rather # than +font.height+ below, simply call move_up font.height # before your call to text() # # == Rotation # # Text can be rotated before it is placed on the canvas by specifying the # :rotate option. 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. # def text(text,options={}) # we'll be messing with the strings encoding, don't change the users # original string text = text.to_s.dup # we might also mess with the font original_font = font.name options = text_options.merge(options) process_text_options(options) font.normalize_encoding(text) unless @skip_encoding if options[:at] x,y = translate(options[:at]) font.size(options[:size]) { add_text_content(text,x,y,options) } else if options[:rotate] raise ArgumentError, "Rotated text may only be used with :at" end wrapped_text(text,options) end font(original_font) end # A hash of configuration options, to be used globally by text(). # # pdf.text_options.update(:size => 16, :align => :right) # pdf.text "Hello World" #=> Size 16 w. right alignment # def text_options @text_options ||= {} end private def process_text_options(options) Prawn.verify_options [:style, :kerning, :size, :at, :wrap, :spacing, :align, :rotate ], options if options[:style] raise "Bad font family" unless font.family font(font.family,:style => options[:style]) end unless options.key?(:kerning) options[:kerning] = font.metrics.has_kerning_data? end options[:size] ||= font.size end def move_text_position(dy) bottom = @bounding_box.stretchy? ? @margin_box.absolute_bottom : @bounding_box.absolute_bottom start_new_page if (y - dy) < bottom self.y -= dy end def wrapped_text(text,options) options[:align] ||= :left font.size(options[:size]) do text = font.metrics.naive_wrap(text, bounds.right, font.size, :kerning => options[:kerning], :mode => options[:wrap]) lines = text.lines lines.each do |e| if font.metrics.type0? move_text_position(font.ascender) else move_text_position(font.height) end line_width = font.width_of(e) case(options[:align]) when :left x = @bounding_box.absolute_left when :center x = @bounding_box.absolute_left + (@bounding_box.width - line_width) / 2.0 when :right x = @bounding_box.absolute_right - line_width end add_text_content(e,x,y,options) if font.metrics.type0? move_text_position(font.height - font.ascender) end move_text_position(options[:spacing]) if options[:spacing] end end end def add_text_content(text, x, y, options) text = font.metrics.convert_text(text,options) add_content "\nBT" add_content "/#{font.identifier} #{font.size} Tf" if options[:rotate] rad = options[:rotate].to_i * Math::PI / 180 arr = [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), x, y ] add_content "%.3f %.3f %.3f %.3f %.3f %.3f Tm" % arr else add_content "#{x} #{y} Td" end rad = 1.570796 add_content Prawn::PdfObject(text, true) << " #{options[:kerning] ? 'TJ' : 'Tj'}" add_content "ET\n" end end end end