class HexaPDF::Layout::ListBox
arguments are ignored.
frame into account. This also means that the available_width
and available_height
If this is set to :flow, the frames created for the list items will take the shape of the
Style#position::
The following style properties are used (additionally to those used by the parent class):
each item.
box. This means they are around all items and their contents and are not used separately for
If the list box has padding and/or borders specified, they are handled like with any other
for ordered lists (#start_number) and the amount of spacing between items (#item_spacing).
item (#item_type) can be specified. Additionally, it is possible to define the start number
The indentation of the contents from the left (#content_indentation) as well as the type of
A ListBox arranges its children as unordered or ordered list items.
def draw_content(canvas, x, y)
def draw_content(canvas, x, y) translate = (style.position != :flow && (x != @draw_pos_x || y != @draw_pos_y)) if translate canvas.save_graphics_state canvas.translate(x - @draw_pos_x, y - @draw_pos_y) end @results.each do |item_result| box_fitter = item_result.box_fitter if (marker = item_result.marker) marker.draw(canvas, item_result.marker_pos_x, box_fitter.frames[0].bottom + box_fitter.frames[0].height - marker.height) end box_fitter.fit_results.each {|result| result.draw(canvas) } end canvas.restore_graphics_state if translate end
def empty?
def empty? super && (!@results || @results.all? {|result| result.box_fitter.fit_results.empty? }) end
def fit(available_width, available_height, frame)
def fit(available_width, available_height, frame) @width = if @initial_width > 0 @initial_width else (style.position == :flow ? frame.width : available_width) end height = if @initial_height > 0 @initial_height - reserved_height else (style.position == :flow ? frame.y - frame.bottom : available_height) - reserved_height end width = @width - reserved_width left = (style.position == :flow ? frame.left : frame.x) + reserved_width_left top = frame.y - reserved_height_top # The left side of the frame of an item is always indented, regardless of style.position item_frame_left = left + @content_indentation item_frame_width = width - @content_indentation # We can remove the content indentation for a rectangle by just modifying left and width unless style.position == :flow left = item_frame_left width = item_frame_width end @results = [] @children.each_with_index do |child, index| item_result = ItemResult.new shape = Geom2D::Polygon([left, top - height], [left + width, top - height], [left + width, top], [left, top]) if style.position == :flow shape = Geom2D::Algorithms::PolygonOperation.run(frame.shape, shape, :intersection) remove_indent_from_frame_shape(shape) unless shape.polygons.empty? end item_frame = Frame.new(item_frame_left, top - height, item_frame_width, height, shape: shape, context: frame.context) if index != 0 || !split_box? || @split_box == :show_first_marker box = item_marker_box(frame.document, index) break unless box.fit(content_indentation, height, nil) item_result.marker = box item_result.marker_pos_x = item_frame.x - content_indentation item_result.height = box.height end box_fitter = BoxFitter.new([item_frame]) Array(child).each {|box| box_fitter.fit(box) } item_result.box_fitter = box_fitter item_result.height = [item_result.height.to_i, box_fitter.content_heights[0]].max @results << item_result top -= item_result.height + item_spacing height -= item_result.height + item_spacing break if !box_fitter.fit_successful? || height <= 0 end @height = @results.sum {|item_result| item_result.height } + (@results.count - 1) * item_spacing + reserved_height @draw_pos_x = frame.x + reserved_width_left @draw_pos_y = frame.y - @height + reserved_height_bottom @fit_successful = @results.all? {|r| r.box_fitter.fit_successful? } && @results.size == @children.size end
def initialize(children: [], item_type: :disc, content_indentation: nil, start_number: 1,
def initialize(children: [], item_type: :disc, content_indentation: nil, start_number: 1, item_spacing: 0, **kwargs) super(**kwargs) @children = children @item_type = item_type @content_indentation = content_indentation || 2 * style.font_size @start_number = start_number @item_spacing = item_spacing @results = nil @results_item_marker_x = nil end
def item_marker_box(document, index)
Creates a box for the item marker at the given item index, using #item_style to decide on
def item_marker_box(document, index) return @item_type.call(document, self, index) if @item_type.kind_of?(Proc) return @item_marker_box if defined?(@item_marker_box) fragment = case @item_type when :disc TextFragment.create("•", font: document.fonts.add("Times"), font_size: style.font_size, fill_color: style.fill_color) when :circle TextFragment.create("❍", font: document.fonts.add("ZapfDingbats"), font_size: style.font_size / 2.0, fill_color: style.fill_color, text_rise: -style.font_size / 1.8) when :square TextFragment.create("■", font: document.fonts.add("ZapfDingbats"), font_size: style.font_size / 2.0, fill_color: style.fill_color, text_rise: -style.font_size / 1.8) when :decimal text = (@start_number + index).to_s << "." decimal_style = { font: (style.font? ? style.font : document.fonts.add("Times")), font_size: style.font_size || 10, fill_color: style.fill_color } TextFragment.create(text, decimal_style) else raise HexaPDF::Error, "Unknown list item type #{@item_type.inspect}" end box = TextBox.new(items: [fragment], style: {align: :right, padding: [0, 5, 0, 0]}) @item_marker_box = box unless @item_type == :decimal box end
def remove_indent_from_frame_shape(shape)
def remove_indent_from_frame_shape(shape) polygon_index = 0 data = [] # Determine the lower-left-most and upper-left-most vertices and their indices, together # with the polygon index that holds them and the direction wrt to the indices from # upper-left-most to lower-left-most. shape.polygons.each_with_index do |polygon, pindex| lower_vertex = upper_vertex = polygon[0] lower_index = upper_index = 0 1.upto(polygon.nr_of_vertices - 1) do |i| v = polygon[i] if v.y < lower_vertex.y || (v.y == lower_vertex.y && v.x <= lower_vertex.x) lower_vertex = v lower_index = i elsif v.y > upper_vertex.y || (v.y == upper_vertex.y && v.x <= upper_vertex.x) upper_vertex = v upper_index = i end end direction = upper_vertex.x == polygon[(upper_index + 1) % polygon.nr_of_vertices].x ? 1 : -1 if data.empty? || data[0].x > lower_vertex.x polygon_index = pindex data = [lower_vertex, lower_index, upper_vertex, upper_index, direction] end end # Now we have all the data to remove the indentation on the left side of the polygon. This # is done by shifting all vertices between and including the lower-left-most and # upper-left-most vertices to the right. vertices = shape.polygons[polygon_index].to_a point = data[2] index = data[3] while point != data[0] vertices[index] = Geom2D::Point(point.x + content_indentation, point.y) index = (index + data[4]) % vertices.size point = vertices[index] end vertices[data[1]] = Geom2D::Point(data[0].x + content_indentation, data[0].y) shape.polygons[polygon_index] = Geom2D::Polygon(*vertices) end
def split_content(_available_width, _available_height, _frame)
def split_content(_available_width, _available_height, _frame) remaining_boxes = @results[-1].box_fitter.remaining_boxes first_is_split_box = remaining_boxes.first&.split_box? children = (remaining_boxes.empty? ? [] : [remaining_boxes]) + @children[@results.size..-1] box = create_split_box(split_box_value: first_is_split_box ? :hide_first_marker : :show_first_marker) box.instance_variable_set(:@children, children) box.instance_variable_set(:@start_number, @start_number + @results.size + (first_is_split_box ? -1 : 0)) box.instance_variable_set(:@results, []) [self, box] end
def supports_position_flow?
def supports_position_flow? true end