Class: Arrolio::Flowables::TableFlowable

Inherits:
Arrolio::Flowable show all
Defined in:
lib/arrolio/flowables/table_flowable.rb

Overview

Real table layout driven by Table::Grid: colspan and rowspan aware cell placement, per-flavor row minimum height, vertical alignment inside cells, borders per cell, header row repeat, continuation caption on split, and table footnotes rendered below the last row.

Constant Summary collapse

CAPTION_DASH =
Regexp.new("[\u00A0\u2009\u0020]\u2014").freeze
FOOTNOTE_SPACING =
10.0

Instance Attribute Summary collapse

Attributes inherited from Arrolio::Flowable

#style

Instance Method Summary collapse

Methods inherited from Arrolio::Flowable

#keep_together?, #keep_with_next?, #page_break_after?, #page_break_before?, #space_after, #space_before, #split

Constructor Details

#initialize(table, style: Style::Definition.new, continued: false, caption_text: nil, caption_runs: nil, caption_style: nil, min_row_height: 0.0, cell_padding: 2.0, footnote_font_size: nil) ⇒ TableFlowable

Returns a new instance of TableFlowable.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/arrolio/flowables/table_flowable.rb', line 17

def initialize(table, style: Style::Definition.new,
               continued: false, caption_text: nil, caption_runs: nil,
               caption_style: nil, min_row_height: 0.0,
               cell_padding: 2.0, footnote_font_size: nil)
  super(style: style)
  @table = table
  @continued = continued
  @caption_runs = caption_runs
  @caption_text = caption_text || caption_runs&.map(&:text)&.join
  @caption_style = caption_style
  @min_row_height = min_row_height.to_f
  @cell_padding = cell_padding.to_f
  @footnote_font_size = footnote_font_size ||
                        (@style.font_size * 0.9).round(1)
  @grid = Table::Grid.build(table)
end

Instance Attribute Details

#caption_runsObject (readonly)

Returns the value of attribute caption_runs.



11
12
13
# File 'lib/arrolio/flowables/table_flowable.rb', line 11

def caption_runs
  @caption_runs
end

#caption_textObject (readonly)

Returns the value of attribute caption_text.



11
12
13
# File 'lib/arrolio/flowables/table_flowable.rb', line 11

def caption_text
  @caption_text
end

#cell_paddingObject (readonly)

Returns the value of attribute cell_padding.



11
12
13
# File 'lib/arrolio/flowables/table_flowable.rb', line 11

def cell_padding
  @cell_padding
end

#continuedObject (readonly)

Returns the value of attribute continued.



11
12
13
# File 'lib/arrolio/flowables/table_flowable.rb', line 11

def continued
  @continued
end

#footnote_font_sizeObject (readonly)

Returns the value of attribute footnote_font_size.



11
12
13
# File 'lib/arrolio/flowables/table_flowable.rb', line 11

def footnote_font_size
  @footnote_font_size
end

#gridObject (readonly)

Returns the value of attribute grid.



11
12
13
# File 'lib/arrolio/flowables/table_flowable.rb', line 11

def grid
  @grid
end

#min_row_heightObject (readonly)

Returns the value of attribute min_row_height.



11
12
13
# File 'lib/arrolio/flowables/table_flowable.rb', line 11

def min_row_height
  @min_row_height
end

#tableObject (readonly)

Returns the value of attribute table.



11
12
13
# File 'lib/arrolio/flowables/table_flowable.rb', line 11

def table
  @table
end

Instance Method Details

#do_split(width, remaining_height, _context = nil) ⇒ Object

Splits at welded-group boundaries only — a rowspan cell is never cut by a page break. When the first group already exceeds the remaining space, no head is returned and the engine moves the whole table to the next page. Footnotes travel with the part carrying the last body row.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/arrolio/flowables/table_flowable.rb', line 96

def do_split(width, remaining_height, _context = nil)
  cols = column_widths_for(width)
  heights = row_heights(cols)
  header, body = partition_rows
  header_count = header.length

  head_groups = []
  tail_groups = []
  # The head part also carries the caption and the repeated
  # header rows — they consume the same budget as body rows.
  used = caption_height(width) + heights[0, header_count].sum
  body_row_groups.each do |group|
    group_h = heights[group.first, group.length].sum
    if used + group_h <= remaining_height
      head_groups << group
      used += group_h
    else
      tail_groups << group
    end
  end

  head_rows = head_groups.flat_map { |g| body[g.first - header_count, g.length] }
  tail_rows = tail_groups.flat_map { |g| body[g.first - header_count, g.length] }

  return [nil, whole_table_copy] if head_rows.empty?
  if tail_rows.empty?
    return [build_split(header, head_rows, @continued,
                        footnotes: @table.footnotes), nil]
  end

  [build_split(header, head_rows, @continued, footnotes: []),
   build_split(header, tail_rows, true, footnotes: @table.footnotes)]
end

#emit(x, y, width, _context = nil) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/arrolio/flowables/table_flowable.rb', line 53

def emit(x, y, width, _context = nil)
  cols = column_widths_for(width)
  heights = row_heights(cols)
  boxes = []
  consumed = 0.0
  cursor = y

  # The caption travels with the table: the head part draws the
  # full caption, continuations draw the short form. This keeps
  # the caption from being orphaned at the bottom of a page.
  if @caption_text
    caption_h = emit_caption(x, cursor, width, boxes)
    cursor -= caption_h
    consumed += caption_h
  end

  row_bottoms = row_bottom_positions(cursor, heights)
  grid.placements.each do |placement|
    cell_y = row_bottoms[placement.row_index + placement.rowspan - 1]
    cell_h = heights[placement.row_index, placement.rowspan].sum
    cell_w = column_span_width(cols, placement)
    cell_x = x + cols[0, placement.column_index].sum
    render_cell(placement.cell, cell_x, cell_y, cell_w, cell_h, boxes,
                header: header_row?(placement.row_index))
  end
  consumed += heights.sum
  cursor = row_bottoms[-1] || cursor

  fn_h = emit_footnotes(x, cursor, width, cols, boxes)
  consumed += fn_h

  [boxes, consumed]
end

#height(width, _context = nil) ⇒ Object



34
35
36
37
38
# File 'lib/arrolio/flowables/table_flowable.rb', line 34

def height(width, _context = nil)
  cols = column_widths_for(width)
  caption_height(width) + row_heights(cols).sum +
    footnote_block_height(width, cols)
end

#min_keep_height(width, _context = nil) ⇒ Object

What must stay on the same page as a preceding keep-with-next flowable: the caption, the (repeated) header rows, and the first welded body group — a caption or header alone at a page bottom is an orphan.



44
45
46
47
48
49
50
51
# File 'lib/arrolio/flowables/table_flowable.rb', line 44

def min_keep_height(width, _context = nil)
  cols = column_widths_for(width)
  heights = row_heights(cols)
  header_rows = @table.header.length
  first_body_group = grid.atomic_row_groups.find { |g| g.first >= header_rows }
  keep_rows = first_body_group ? first_body_group.last + 1 : header_rows
  caption_height(width) + heights[0...[keep_rows, heights.length].min].sum
end

#splittable?Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/arrolio/flowables/table_flowable.rb', line 87

def splittable?
  true
end

#whole_table_copyObject



130
131
132
133
# File 'lib/arrolio/flowables/table_flowable.rb', line 130

def whole_table_copy
  build_split(@table.header, @table.body, @continued,
              footnotes: @table.footnotes)
end