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)

Draws the list items onto the canvas at position [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?

Returns +true+ if no box was fitted into the list box.
def empty?
  super && (!@results || @results.all? {|result| result.box_fitter.fit_results.empty? })
end

def fit(available_width, available_height, frame)

Fits the list box into the available space.
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,

Creates a new ListBox object for the given child boxes in +children+.
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)

its contents.
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)

Removes the +content_indentation+ from the left side of the given shape (a Geom2D::PolygonSet).
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)

Splits the content of the list box. This method is called from Box#split.
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?

Returns +true+ as the 'position' style property value :flow is supported.
def supports_position_flow?
  true
end