class Prawn::Text::Formatted::Box

Generally, one would use the Prawn::Text::Formatted#formatted_text_box convenience method. However, using ::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

Attributes

ascender[R]

The height of the ascender of the last line printed

at[R]

The upper left corner of the text box

descender[R]

The height of the descender of the last line printed

leading[R]

The leading used during printing

line_height[R]

The line height of the last line printed

text[R]

The text that was successfully printed (or, if dry_run was used, the text that would have been successfully printed)

Public Class Methods

extensions() click to toggle source

Example (see Prawn::Text::Core::Formatted::Wrap for what is required of the wrap method if you want to override the default wrapping algorithm):

module MyWrap

  def wrap(array)
    initialize_wrap([{ :text => 'all your base are belong to us' }])
    @line_wrap.wrap_line(:document => @document,
                         :kerning => @kerning,
                         :width => 10000,
                         :arranger => @arranger)
    fragment = @arranger.retrieve_fragment
    format_and_draw_fragment(fragment, 0, @line_wrap.width, 0)
    []
  end

end

Prawn::Text::Formatted::Box.extensions << MyWrap

box = Prawn::Text::Formatted::Box.new('hello world')
box.render('why can't I print anything other than' +
           '"all your base are belong to us"?')
# File lib/prawn/text/formatted/box.rb, line 331
def self.extensions
  @extensions ||= []
end
inherited(base) click to toggle source

@private

# File lib/prawn/text/formatted/box.rb, line 336
def self.inherited(base)
  extensions.each { |e| base.extensions << e }
end
new(formatted_text, options = {}) click to toggle source

See Prawn::Text#text_box for valid options

Calls superclass method
# File lib/prawn/text/formatted/box.rb, line 138
def initialize(formatted_text, options = {})
  @inked = false
  Prawn.verify_options(valid_options, options)
  options = options.dup

  self.class.extensions.reverse_each { |e| extend e }

  @overflow = options[:overflow] || :truncate
  @disable_wrap_by_char = options[:disable_wrap_by_char]

  self.original_text = formatted_text
  @text = nil

  @document = options[:document]
  @direction = options[:direction] || @document.text_direction
  @fallback_fonts = options[:fallback_fonts] ||
    @document.fallback_fonts
  @at = (
    options[:at] || [@document.bounds.left, @document.bounds.top]
  ).dup
  @width = options[:width] ||
    @document.bounds.right - @at[0]
  @height = options[:height] || default_height
  @align = options[:align] ||
    (@direction == :rtl ? :right : :left)
  @vertical_align = options[:valign] || :top
  @leading = options[:leading] || @document.default_leading
  @character_spacing = options[:character_spacing] ||
    @document.character_spacing
  @mode = options[:mode] || @document.text_rendering_mode
  @rotate = options[:rotate] || 0
  @rotate_around = options[:rotate_around] || :upper_left
  @single_line = options[:single_line]
  @draw_text_callback = options[:draw_text_callback]

  # if the text rendering mode is :unknown, force it back to :fill
  if @mode == :unknown
    @mode = :fill
  end

  if @overflow == :expand
    # if set to expand, then we simply set the bottom
    # as the bottom of the document bounds, since that
    # is the maximum we should expand to
    @height = default_height
    @overflow = :truncate
  end
  @min_font_size = options[:min_font_size] || 5
  if options[:kerning].nil?
    options[:kerning] = @document.default_kerning?
  end
  @options = {
    kerning: options[:kerning],
    size: options[:size],
    style: options[:style]
  }

  super(formatted_text, options)
end

Public Instance Methods

available_width() click to toggle source

The width available at this point in the box

# File lib/prawn/text/formatted/box.rb, line 241
def available_width
  @width
end
everything_printed?() click to toggle source

True if everything printed (or, if dry_run was used, everything would have been successfully printed)

# File lib/prawn/text/formatted/box.rb, line 117
def everything_printed?
  @everything_printed
end
height() click to toggle source

The height actually used during the previous render

# File lib/prawn/text/formatted/box.rb, line 247
def height
  return 0 if @baseline_y.nil? || @descender.nil?
  (@baseline_y - @descender).abs
end
line_gap() click to toggle source
# File lib/prawn/text/formatted/box.rb, line 132
def line_gap
  line_height - (ascender + descender)
end
nothing_printed?() click to toggle source

True if nothing printed (or, if dry_run was used, nothing would have been successfully printed)

# File lib/prawn/text/formatted/box.rb, line 111
def nothing_printed?
  @nothing_printed
end
render(flags = {}) click to toggle source

Render text to the document based on the settings defined in initialize.

In order to facilitate look-ahead calculations, render accepts a :dry_run => true option. If provided, then everything is executed as if rendering, with the exception that nothing is drawn on the page. Useful for look-ahead computations of height, unprinted text, etc.

Returns any text that did not print under the current settings.

# File lib/prawn/text/formatted/box.rb, line 209
def render(flags = {})
  unprinted_text = []

  @document.save_font do
    @document.character_spacing(@character_spacing) do
      @document.text_rendering_mode(@mode) do
        process_options

        text = normalized_text(flags)

        @document.font_size(@font_size) do
          shrink_to_fit(text) if @overflow == :shrink_to_fit
          process_vertical_alignment(text)
          @inked = true unless flags[:dry_run]
          unprinted_text = if @rotate != 0 && @inked
                             render_rotated(text)
                           else
                             wrap(text)
                           end
          @inked = false
        end
      end
    end
  end

  unprinted_text.map do |e|
    e.merge(text: @document.font.to_utf8(e[:text]))
  end
end
valid_options() click to toggle source
# File lib/prawn/text/formatted/box.rb, line 340
def valid_options
  PDF::Core::Text::VALID_OPTIONS + [
    :at,
    :height, :width,
    :align, :valign,
    :rotate, :rotate_around,
    :overflow, :min_font_size,
    :disable_wrap_by_char,
    :leading, :character_spacing,
    :mode, :single_line,
    :document,
    :direction,
    :fallback_fonts,
    :draw_text_callback
  ]
end

Private Instance Methods

analyze_glyphs_for_fallback_font_support(hash) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 406
def analyze_glyphs_for_fallback_font_support(hash)
  font_glyph_pairs = []

  original_font = @document.font.family
  fragment_font = hash[:font] || original_font

  fallback_fonts = @fallback_fonts.dup
  # always default back to the current font if the glyph is missing from
  # all fonts
  fallback_fonts << fragment_font

  @document.save_font do
    hash[:text].each_char do |char|
      font_glyph_pairs << [
        find_font_for_this_glyph(
          char,
          fragment_font,
          fallback_fonts.dup
        ),
        char
      ]
    end
  end

  # Don't add a :font to fragments if it wasn't there originally
  if hash[:font].nil?
    font_glyph_pairs.each do |pair|
      pair[0] = nil if pair[0] == original_font
    end
  end

  form_fragments_from_like_font_glyph_pairs(font_glyph_pairs, hash)
end
default_height() click to toggle source

Returns the default height to be used if none is provided or if the overflow option is set to :expand. If we are in a stretchy bounding box, assume we can stretch to the bottom of the innermost non-stretchy box.

# File lib/prawn/text/formatted/box.rb, line 482
def default_height
  # Find the "frame", the innermost non-stretchy bbox.
  frame = @document.bounds
  frame = frame.parent while frame.stretchy? && frame.parent

  @at[1] + @document.bounds.absolute_bottom - frame.absolute_bottom
end
draw_fragment_overlay_anchor(fragment) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 602
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
draw_fragment_overlay_local(fragment) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 612
def draw_fragment_overlay_local(fragment)
  return unless fragment.local
  box = fragment.absolute_bounding_box
  @document.link_annotation(
    box,
    Border: [0, 0, 0],
    A: {
      Type: :Action,
      S: :Launch,
      F: PDF::Core::LiteralString.new(fragment.local),
      NewWindow: true
    }
  )
end
draw_fragment_overlay_styles(fragment) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 627
def draw_fragment_overlay_styles(fragment)
  if fragment.styles.include?(:underline)
    @document.stroke_line(fragment.underline_points)
  end

  if fragment.styles.include?(:strikethrough)
    @document.stroke_line(fragment.strikethrough_points)
  end
end
draw_fragment_overlays(fragment) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 578
def draw_fragment_overlays(fragment)
  draw_fragment_overlay_styles(fragment)
  draw_fragment_overlay_link(fragment)
  draw_fragment_overlay_anchor(fragment)
  draw_fragment_overlay_local(fragment)
  fragment.callback_objects.each do |obj|
    obj.render_in_front(fragment) if obj.respond_to?(:render_in_front)
  end
end
draw_fragment_underlays(fragment) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 572
def draw_fragment_underlays(fragment)
  fragment.callback_objects.each do |obj|
    obj.render_behind(fragment) if obj.respond_to?(:render_behind)
  end
end
find_font_for_this_glyph(char, current_font, fallback_fonts) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 440
def find_font_for_this_glyph(char, current_font, fallback_fonts)
  @document.font(current_font)
  if fallback_fonts.empty? || @document.font.glyph_present?(char)
    current_font
  else
    find_font_for_this_glyph(char, fallback_fonts.shift, fallback_fonts)
  end
end
form_fragments_from_like_font_glyph_pairs(font_glyph_pairs, hash) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 449
def form_fragments_from_like_font_glyph_pairs(font_glyph_pairs, hash)
  fragments = []
  fragment = nil
  current_font = nil

  font_glyph_pairs.each do |font, char|
    if font != current_font || fragments.count.zero?
      current_font = font
      fragment = hash.dup
      fragment[:text] = char
      fragment[:font] = font unless font.nil?
      fragments << fragment
    else
      fragment[:text] += char
    end
  end

  fragments
end
move_baseline_down() click to toggle source
# File lib/prawn/text/formatted/box.rb, line 469
def move_baseline_down
  if @baseline_y.zero?
    @baseline_y = -@ascender
  else
    @baseline_y -= (@line_height + @leading)
  end
end
normalize_encoding() click to toggle source
# File lib/prawn/text/formatted/box.rb, line 375
def normalize_encoding
  formatted_text = original_text

  unless @fallback_fonts.empty?
    formatted_text = process_fallback_fonts(formatted_text)
  end

  formatted_text.each do |hash|
    if hash[:font]
      @document.font(hash[:font]) do
        hash[:text] = @document.font.normalize_encoding(hash[:text])
      end
    else
      hash[:text] = @document.font.normalize_encoding(hash[:text])
    end
  end

  formatted_text
end
normalized_text(flags) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 359
def normalized_text(flags)
  text = normalize_encoding

  text.each { |t| t.delete(:color) } if flags[:dry_run]

  text
end
original_text() click to toggle source
# File lib/prawn/text/formatted/box.rb, line 367
def original_text
  @original_array.collect(&:dup)
end
original_text=(formatted_text) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 371
def original_text=(formatted_text)
  @original_array = formatted_text
end
process_fallback_fonts(formatted_text) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 395
def process_fallback_fonts(formatted_text)
  modified_formatted_text = []

  formatted_text.each do |hash|
    fragments = analyze_glyphs_for_fallback_font_support(hash)
    modified_formatted_text.concat(fragments)
  end

  modified_formatted_text
end
process_options() click to toggle source
# File lib/prawn/text/formatted/box.rb, line 537
def process_options
  # must be performed within a save_font block because
  # document.process_text_options sets the font
  @document.process_text_options(@options)
  @font_size = @options[:size]
  @kerning = @options[:kerning]
end
process_vertical_alignment(text) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 490
def process_vertical_alignment(text)
  # The vertical alignment must only be done once per text box, but
  # we need to wait until render() is called so that the fonts are set
  # up properly for wrapping. So guard with a boolean to ensure this is
  # only run once.
  if defined?(@vertical_alignment_processed) &&
      @vertical_alignment_processed
    return
  end
  @vertical_alignment_processed = true

  return if @vertical_align == :top

  wrap(text)

  case @vertical_align
  when :center
    @at[1] -= (@height - height + @descender) * 0.5
  when :bottom
    @at[1] -= (@height - height)
  end

  @height = height
end
render_rotated(text) click to toggle source
# File lib/prawn/text/formatted/box.rb, line 545
def render_rotated(text)
  unprinted_text = ''

  case @rotate_around
  when :center
    x = @at[0] + @width * 0.5
    y = @at[1] - @height * 0.5
  when :upper_right
    x = @at[0] + @width
    y = @at[1]
  when :lower_right
    x = @at[0] + @width
    y = @at[1] - @height
  when :lower_left
    x = @at[0]
    y = @at[1] - @height
  else
    x = @at[0]
    y = @at[1]
  end

  @document.rotate(@rotate, origin: [x, y]) do
    unprinted_text = wrap(text)
  end
  unprinted_text
end
shrink_to_fit(text) click to toggle source

Decrease the font size until the text fits or the min font size is reached

# File lib/prawn/text/formatted/box.rb, line 517
def shrink_to_fit(text)
  loop do
    if @disable_wrap_by_char && @font_size > @min_font_size
      begin
        wrap(text)
      rescue Errors::CannotFit
        # Ignore errors while we can still attempt smaller
        # font sizes.
      end
    else
      wrap(text)
    end

    break if @everything_printed || @font_size <= @min_font_size

    @font_size = [@font_size - 0.5, @min_font_size].max
    @document.font_size = @font_size
  end
end