# frozen_string_literal: true # wrap.rb: Handles text wrapping for for formatted text # # Contributed by Daniel Nelson # # This is free software. Please see the LICENSE and COPYING files for details. require_relative 'line_wrap' require_relative 'arranger' module Prawn module Text module Formatted #:nodoc: # @private module Wrap #:nodoc: def initialize(_array, options) @line_wrap = Prawn::Text::Formatted::LineWrap.new @arranger = Prawn::Text::Formatted::Arranger.new( @document, kerning: options[:kerning] ) @disable_wrap_by_char = options[:disable_wrap_by_char] end # See the developer documentation for PDF::Core::Text#wrap # # Formatted#wrap should set the following variables: # @line_height:: # the height of the tallest fragment in the last printed line # @descender:: # the descender height of the tallest fragment in the last # printed line # @ascender:: # the ascender heigth of the tallest fragment in the last # printed line # @baseline_y:: # the baseline of the current line # @nothing_printed:: # set to true until something is printed, then false # @everything_printed:: # set to false until everything printed, then true # # Returns any formatted text that was not printed # def wrap(array) #:nodoc: initialize_wrap(array) stop = false until stop # wrap before testing if enough height for this line because the # height of the highest fragment on this line will be used to # determine the line height @line_wrap.wrap_line( document: @document, kerning: @kerning, width: available_width, arranger: @arranger, disable_wrap_by_char: @disable_wrap_by_char ) if enough_height_for_this_line? move_baseline_down print_line else stop = true end stop ||= @single_line || @arranger.finished? end @text = @printed_lines.join("\n") @everything_printed = @arranger.finished? @arranger.unconsumed end private def print_line @nothing_printed = false printed_fragments = [] fragments_this_line = [] word_spacing = word_spacing_for_this_line @arranger.fragments.each do |fragment| fragment.word_spacing = word_spacing if fragment.text == "\n" printed_fragments << "\n" if @printed_lines.last == '' break end printed_fragments << fragment.text fragments_this_line << fragment end @arranger.fragments.replace [] accumulated_width = 0 fragments_this_line.reverse! if @direction == :rtl fragments_this_line.each do |fragment_this_line| fragment_this_line.default_direction = @direction format_and_draw_fragment( fragment_this_line, accumulated_width, @line_wrap.width, word_spacing ) accumulated_width += fragment_this_line.width end @printed_lines << printed_fragments.map do |s| s.dup.force_encoding(::Encoding::UTF_8) end.join end def word_spacing_for_this_line if @align == :justify && @line_wrap.space_count.positive? && !@line_wrap.paragraph_finished? (available_width - @line_wrap.width) / @line_wrap.space_count else 0 end end def enough_height_for_this_line? @line_height = @arranger.max_line_height @descender = @arranger.max_descender @ascender = @arranger.max_ascender diff = if @baseline_y.zero? @ascender + @descender else @descender + @line_height + @leading end require_relatived_total_height = @baseline_y.abs + diff if require_relatived_total_height > @height + 0.0001 # no room for the full height of this line @arranger.repack_unretrieved false else true end end def initialize_wrap(array) @text = nil @arranger.format_array = array # these values will depend on the maximum value within a given line @line_height = 0 @descender = 0 @ascender = 0 @baseline_y = 0 @printed_lines = [] @nothing_printed = true @everything_printed = false end def format_and_draw_fragment(fragment, accumulated_width, line_width, word_spacing) @arranger.apply_color_and_font_settings(fragment) do draw_fragment( fragment, accumulated_width, line_width, word_spacing ) end end end end end end