class Prawn::Text::Formatted::Box
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
Attributes
The height of the ascender of the last line printed
The upper left corner of the text box
The height of the descender of the last line printed
The leading used during printing
The line height of the last line printed
The text that was successfully printed (or, if dry_run
was used, the text that would have been successfully printed)
Public Class Methods
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
@private
# File lib/prawn/text/formatted/box.rb, line 336 def self.inherited(base) extensions.each { |e| base.extensions << e } end
See Prawn::Text#text_box
for valid options
# 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
The width available at this point in the box
# File lib/prawn/text/formatted/box.rb, line 241 def available_width @width end
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
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
# File lib/prawn/text/formatted/box.rb, line 132 def line_gap line_height - (ascender + descender) end
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 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
# 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
# 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
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
# 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
# File lib/prawn/text/formatted/box.rb, line 588 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: PDF::Core::LiteralString.new(fragment.link) } ) end
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# File lib/prawn/text/formatted/box.rb, line 367 def original_text @original_array.collect(&:dup) end
# File lib/prawn/text/formatted/box.rb, line 371 def original_text=(formatted_text) @original_array = formatted_text end
# 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
# 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
# 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
# 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
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