# -*- encoding: utf-8; frozen_string_literal: true -*- # #-- # This file is part of HexaPDF. # # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby # Copyright (C) 2014-2022 Thomas Leitner # # HexaPDF is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License version 3 as # published by the Free Software Foundation with the addition of the # following permission added to Section 15 as permitted in Section 7(a): # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON # INFRINGEMENT OF THIRD PARTY RIGHTS. # # HexaPDF is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public # License for more details. # # You should have received a copy of the GNU Affero General Public License # along with HexaPDF. If not, see . # # The interactive user interfaces in modified source and object code # versions of HexaPDF must display Appropriate Legal Notices, as required # under Section 5 of the GNU Affero General Public License version 3. # # In accordance with Section 7(b) of the GNU Affero General Public # License, a covered work must retain the producer line in every PDF that # is created or manipulated using HexaPDF. # # If the GNU Affero General Public License doesn't fit your need, # commercial licenses are available at . #++ require 'hexapdf/layout/box' require 'hexapdf/layout/text_layouter' module HexaPDF module Layout # A TextBox is used for drawing text, either inside a rectangular box or by flowing it around # objects of a Frame. # # This class uses TextLayouter behind the scenes to do the hard work. class TextBox < Box # Creates a new TextBox object with the given inline items (e.g. TextFragment and InlineBox # objects). def initialize(items:, **kwargs) super(**kwargs) @tl = TextLayouter.new(style) @items = items @result = nil end # Returns +true+ as the 'position' style property value :flow is supported. def supports_position_flow? true end # Fits the text box into the Frame. # # Depending on the 'position' style property, the text is either fit into the rectangular area # given by +available_width+ and +available_height+, or fit to the outline of the frame # starting from the top (when 'position' is set to :flow). # # The spacing after the last line can be controlled via the style property +last_line_gap+. # # Also see TextLayouter#style for other style properties taken into account. def fit(available_width, available_height, frame) return false if (@initial_width > 0 && @initial_width > available_width) || (@initial_height > 0 && @initial_height > available_height) @width = @height = 0 @result = if style.position == :flow @tl.fit(@items, frame.width_specification, frame.shape.bbox.height) else @width = reserved_width @height = reserved_height width = (@initial_width > 0 ? @initial_width : available_width) - @width height = (@initial_height > 0 ? @initial_height : available_height) - @height @tl.fit(@items, width, height) end @width += if @initial_width > 0 || style.align == :center || style.align == :right width else @result.lines.max_by(&:width)&.width || 0 end @height += if @initial_height > 0 || style.valign == :center || style.valign == :bottom height else @result.height end if style.last_line_gap && @result.lines.last @height += style.line_spacing.gap(@result.lines.last, @result.lines.last) end @result.status == :success end # Splits the text box into two boxes if necessary and possible. def split(available_width, available_height, frame) fit(available_width, available_height, frame) unless @result if style.position != :flow && (@width > available_width || @height > available_height) [nil, self] elsif @result.remaining_items.empty? [self] elsif @result.lines.empty? [nil, self] else [self, create_box_for_remaining_items] end end # :nodoc: def empty? super && (!@result || @result.lines.empty?) end private # Draws the text into the box. def draw_content(canvas, x, y) return unless @result && !@result.lines.empty? @result.draw(canvas, x, y + content_height) end # Creates a new TextBox instance for the items remaining after fitting the box. def create_box_for_remaining_items box = create_split_box box.instance_variable_set(:@result, nil) box.instance_variable_set(:@items, @result.remaining_items) box end end end end