Class: HexaPDF::Layout::ListBox

Inherits:
Box
  • Object
show all
Defined in:
lib/hexapdf/layout/list_box.rb

Overview

A ListBox arranges its children as unordered or ordered list items.

The indentation of the contents from the left (#content_indentation) as well as the marker type of the items (#marker_type) can be specified. Additionally, it is possible to define the start number for ordered lists (#start_number) and the amount of spacing between items (#item_spacing).

If the list box has padding and/or borders specified, they are handled like with any other box. This means they are around all items and their contents and are not used separately for each item.

The following style properties are used (additionally to those used by the parent class):

Style#position

If this is set to :flow, the frames created for the list items will take the shape of the frame into account. This also means that the available_width and available_height arguments are ignored.

Defined Under Namespace

Classes: ItemResult

Instance Attribute Summary collapse

Attributes inherited from Box

#height, #properties, #style, #width

Instance Method Summary collapse

Methods inherited from Box

#content_height, #content_width, create, #draw, #split, #split_box?

Constructor Details

#initialize(children: [], marker_type: :disc, content_indentation: nil, start_number: 1, item_spacing: 0, **kwargs) ⇒ ListBox

Creates a new ListBox object for the given child boxes in children.



166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/hexapdf/layout/list_box.rb', line 166

def initialize(children: [], marker_type: :disc, content_indentation: nil, start_number: 1,
               item_spacing: 0, **kwargs)
  super(**kwargs)
  @children = children
  @marker_type = marker_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

Instance Attribute Details

#childrenObject (readonly)

The child boxes of this ListBox. They need to be finalized before #fit is called.



68
69
70
# File 'lib/hexapdf/layout/list_box.rb', line 68

def children
  @children
end

#content_indentationObject (readonly)

The indentation of the list content in PDF points. The item marker will be inside this indentation.

The default value is two times the font size.

Example:

#>pdf-composer100
composer.box(:list) {|list| list.lorem_ipsum_box(sentences: 1) }
composer.box(:list, content_indentation: 50) do |list|
  list.lorem_ipsum_box(sentences: 1)
end


151
152
153
# File 'lib/hexapdf/layout/list_box.rb', line 151

def content_indentation
  @content_indentation
end

#item_spacingObject (readonly)

The spacing between two consecutive list items.

The default value is zero.

Example:

#>pdf-composer
composer.box(:list, item_spacing: 10) do |list|
  3.times { list.lorem_ipsum_box(sentences: 1) }
end


163
164
165
# File 'lib/hexapdf/layout/list_box.rb', line 163

def item_spacing
  @item_spacing
end

#marker_typeObject (readonly)

The type of list item marker to be rendered before the list item contents.

The following values are supported (and :disc is the default):

:disc

Draws a filled disc for the items of the unordered list.

#>pdf-composer100
composer.box(:list, marker_type: :disc) do |list|
  list.lorem_ipsum_box(sentences: 1)
end
:circle

Draws an unfilled circle for the items of the unordered list.

#>pdf-composer100
composer.box(:list, marker_type: :circle) do |list|
  list.lorem_ipsum_box(sentences: 1)
end
:square

Draws a filled square for the items of the unordered list.

#>pdf-composer100
composer.box(:list, marker_type: :square) do |list|
  list.lorem_ipsum_box(sentences: 1)
end
:decimal

Draws the numbers in decimal form, starting from #start_number) for the items of the ordered list.

#>pdf-composer100
composer.box(:list, marker_type: :decimal) do |list|
  5.times { list.lorem_ipsum_box(sentences: 1) }
end
custom marker

Additionally, it is possible to specify an object as value that responds to #call(document, box, index) where document is the HexaPDF::Document, box is the list box, and index is the current item index, starting at 0. The return value needs to be a Box object which is then fit into the content indentation area and drawn.

#>pdf-composer100
image = lambda do |document, box, index|
  document.layout.image_box(machu_picchu, height: box.style.font_size)
end
composer.box(:list, marker_type: image) do |list|
  2.times { list.lorem_ipsum_box(sentences: 1) }
end


125
126
127
# File 'lib/hexapdf/layout/list_box.rb', line 125

def marker_type
  @marker_type
end

#start_numberObject (readonly)

The start number when using a #marker_type that represents an ordered list.

The default value for this is 1.

Example:

#>pdf-composer100
composer.box(:list, marker_type: :decimal, start_number: 3) do |list|
  2.times { list.lorem_ipsum_box(sentences: 1) }
end


137
138
139
# File 'lib/hexapdf/layout/list_box.rb', line 137

def start_number
  @start_number
end

Instance Method Details

#empty?Boolean

Returns true if no box was fitted into the list box.

Returns:

  • (Boolean)


185
186
187
# File 'lib/hexapdf/layout/list_box.rb', line 185

def empty?
  super && (!@results || @results.all? {|result| result.box_fitter.fit_results.empty? })
end

#fit(available_width, available_height, frame) ⇒ Object

Fits the list box into the current region of the frame.



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/hexapdf/layout/list_box.rb', line 190

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

#supports_position_flow?Boolean

Returns true as the ‘position’ style property value :flow is supported.

Returns:

  • (Boolean)


180
181
182
# File 'lib/hexapdf/layout/list_box.rb', line 180

def supports_position_flow?
  true
end