# encoding: utf-8
# text/formatted/rectangle.rb : Implements text boxes with formatted text
#
# Copyright February 2010, Daniel Nelson. All Rights Reserved.
#
# This is free software. Please see the LICENSE and COPYING files for details.
#
module Prawn
module Text
module Formatted
# Draws the requested formatted text into a box. When the text overflows
# the rectangle shrink to fit or truncate the text. Text boxes are
# independent of the document y position.
#
# == Formatted Text Array
#
# Formatted text is comprised of an array of hashes, where each hash
# defines text and format information. As of the time of writing, the
# following hash options are supported:
#
# :text::
# the text to format according to the other hash options
# :styles::
# an array of styles to apply to this text. Available styles include
# :bold, :italic, :underline, :strikethrough, :subscript, and
# :superscript
# :size::
# an integer denoting the font size to apply to this text
# :font::
# the name of a font. The name must be an AFM font with the desired
# faces or must be a font that is already registered using
# Prawn::Document#font_families
# :color::
# anything compatible with Prawn::Graphics::Color#fill_color and
# Prawn::Graphics::Color#stroke_color
# :link::
# a URL to which to create a link. A clickable link will be created
# to that URL. Note that you must explicitly underline and color using
# the appropriate tags if you which to draw attention to the link
# :anchor::
# a destination that has already been or will be registered using
# Prawn::Core::Destinations#add_dest. A clickable link will be
# created to that destination. Note that you must explicitly underline
# and color using the appropriate tags if you which to draw attention
# to the link
# :callback::
# a hash with the following options
# :object:: required. the object to target
# :method:: required. the method to call on the target object
# :arguments:: optional. the arguments to pass to the
# callback method
#
# == Example
#
# formatted_text_box([{ :text => "hello" },
# { :text => "world",
# :size => 24,
# :styles => [:bold, :italic] }])
#
# == Options
#
# Accepts the same options as Text::Box with the below exceptions
#
# :overflow::
# does not accept :ellipses
#
# == Returns
#
# Returns a formatted text array representing any text that did not print
# under the current settings.
#
# == Exceptions
#
# Raises "Bad font family" if no font family is defined for the current font
#
# Raises Prawn::Errrors::CannotFit if not wide enough to print
# any text
#
# Raises NotImplementedError if :ellipses overflow
# option included
#
def formatted_text_box(array, options)
Text::Formatted::Box.new(array, options.merge(:document => self)).render
end
# Generally, one would use the Prawn::Text::Formatted#formatted_text_box
# convenience method. However, using Text::Formatted::Box.new in
# conjunction with #render(:dry_run => true) enables one to do look-ahead
# calculations prior to placing text on the page, or to determine how much
# vertical space was consumed by the printed text
#
class Box < Prawn::Text::Box
include Prawn::Core::Text::Formatted::Wrap
def initialize(array, options={})
super(array, options)
if @overflow == :ellipses
raise NotImplementedError, "ellipses overflow unavailable with" +
"formatted box"
end
end
# The height actually used during the previous render
#
def height
return 0 if @baseline_y.nil? || @descender.nil?
@baseline_y.abs + @line_height - @ascender
end
# fragment is a Prawn::Text::Formatted::Fragment object
#
def draw_fragment(fragment, accumulated_width=0, line_width=0, word_spacing=0) #:nodoc:
case(@align)
when :left, :justify
x = @at[0]
when :center
x = @at[0] + @width * 0.5 - line_width * 0.5
when :right
x = @at[0] + @width - line_width
end
x += accumulated_width
y = @at[1] + @baseline_y
y += fragment.y_offset
fragment.left = x
fragment.baseline = y
if @inked
if @align == :justify
@document.word_spacing(word_spacing) {
@document.draw_text!(fragment.text, :at => [x, y],
:kerning => @kerning)
}
else
@document.draw_text!(fragment.text, :at => [x, y],
:kerning => @kerning)
end
draw_fragment_overlays(fragment)
end
end
private
def original_text
@original_array.collect { |hash| hash.dup }
end
def original_text=(array)
@original_array = array
end
def normalize_encoding
array = original_text
array.each do |hash|
hash[:text] = @document.font.normalize_encoding(hash[:text])
end
array
end
def move_baseline_down
if @baseline_y == 0
@baseline_y = -@ascender
else
@baseline_y -= (@line_height + @leading)
end
end
def draw_fragment_overlays(fragment)
draw_fragment_overlay_styles(fragment)
draw_fragment_overlay_link(fragment)
draw_fragment_overlay_anchor(fragment)
end
def draw_fragment_overlay_link(fragment)
return unless fragment.link
box = fragment.absolute_bounding_box
@document.link_annotation(box,
:Border => [0, 0, 0],
:A => { :Type => :Action,
:S => :URI,
:URI => Prawn::Core::LiteralString.new(fragment.link) })
end
def draw_fragment_overlay_anchor(fragment)
return unless fragment.anchor
box = fragment.absolute_bounding_box
@document.link_annotation(box,
:Border => [0, 0, 0],
:Dest => fragment.anchor)
end
def draw_fragment_overlay_styles(fragment)
underline = fragment.styles.include?(:underline)
if underline
@document.stroke_line(fragment.underline_points)
end
strikethrough = fragment.styles.include?(:strikethrough)
if strikethrough
@document.stroke_line(fragment.strikethrough_points)
end
end
end
end
end
end