Class: Pdfrb::Layout::MultiPageTableBox

Inherits:
Box
  • Object
show all
Defined in:
lib/pdfrb/layout/multi_page_table_box.rb

Overview

Multi-page table: wraps a single TableBox's worth of rows and splits it into per-page fragments when it doesn't fit a single frame. Each fragment is a TableBox whose fit? returns true for the available height; the splitter yields them in document order, optionally repeating a header row on each fragment.

This is intentionally a thin coordinator: it doesn't re-layout cells; it just partitions rows. The actual row drawing is delegated to TableBox, so col/row spans continue to work — but spans that cross a split boundary are clamped at the split (their rowspan is truncated to the rows that fit).

Constant Summary collapse

DEFAULT_MIN_ROWS_PER_PAGE =
3
DEFAULT_REPEAT_HEADER =
true

Instance Attribute Summary collapse

Attributes inherited from Box

#height, #style, #width

Instance Method Summary collapse

Methods inherited from Box

#draw, #empty?, #supports_position_flow?

Constructor Details

#initialize(rows:, column_widths: nil, header_row_count: 0, min_rows_per_page: DEFAULT_MIN_ROWS_PER_PAGE) ⇒ MultiPageTableBox

Returns a new instance of MultiPageTableBox.

Parameters:

  • rows (Array<Array<Pdfrb::Layout::TableBox::Cell, Box>>)

    the full table content.

  • column_widths (Array<Numeric>, nil) (defaults to: nil)

    explicit widths.

  • header_row_count (Integer) (defaults to: 0)

    number of leading rows to repeat at the top of each page fragment. 0 disables.

  • min_rows_per_page (Integer) (defaults to: DEFAULT_MIN_ROWS_PER_PAGE)

    minimum number of body rows on each page; if the split would produce fewer, the fragment is deferred to the next page.



30
31
32
33
34
35
36
37
38
# File 'lib/pdfrb/layout/multi_page_table_box.rb', line 30

def initialize(rows:, column_widths: nil, header_row_count: 0,
               min_rows_per_page: DEFAULT_MIN_ROWS_PER_PAGE, **)
  super(**)
  @rows = rows
  @column_widths = column_widths
  @header_row_count = header_row_count
  @min_rows_per_page = min_rows_per_page
  @fragments = nil
end

Instance Attribute Details

#column_widthsObject (readonly)

Returns the value of attribute column_widths.



20
21
22
# File 'lib/pdfrb/layout/multi_page_table_box.rb', line 20

def column_widths
  @column_widths
end

#header_row_countObject (readonly)

Returns the value of attribute header_row_count.



20
21
22
# File 'lib/pdfrb/layout/multi_page_table_box.rb', line 20

def header_row_count
  @header_row_count
end

#min_rows_per_pageObject (readonly)

Returns the value of attribute min_rows_per_page.



20
21
22
# File 'lib/pdfrb/layout/multi_page_table_box.rb', line 20

def min_rows_per_page
  @min_rows_per_page
end

#rowsObject (readonly)

Returns the value of attribute rows.



20
21
22
# File 'lib/pdfrb/layout/multi_page_table_box.rb', line 20

def rows
  @rows
end

Instance Method Details

#draw_content(canvas, x, y) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/pdfrb/layout/multi_page_table_box.rb', line 87

def draw_content(canvas, x, y)
  return if @fragments.nil? || @fragments.empty?

  # Draw the first fragment in place; the Composer is
  # responsible for placing subsequent fragments on later
  # pages by calling draw on each fragment directly.
  offset_y = y
  @fragments.each do |frag|
    frag.draw(canvas, x, offset_y)
    offset_y -= frag.height
  end
end

#each_fragment(&block) ⇒ Object

Iterate fragments. Yields each TableBox fragment; if a block is absent, returns an Enumerator. Useful for the Composer flow: the first fragment is drawn at the current cursor, subsequent fragments trigger a page break.



104
105
106
107
108
# File 'lib/pdfrb/layout/multi_page_table_box.rb', line 104

def each_fragment(&block)
  return enum_for(:each_fragment) unless block

  @fragments.each(&block)
end

#fit?(available_width, available_height) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
83
84
85
# File 'lib/pdfrb/layout/multi_page_table_box.rb', line 80

def fit?(available_width, available_height)
  @fragments = fragments(available_width: available_width,
                         first_page_height: available_height,
                         later_page_height: available_height)
  @fragments.any?
end

#fragments(available_width:, first_page_height:, later_page_height:) ⇒ Object

Returns the per-page fragments as an Array of TableBox. The first fragment fits within available_height; each subsequent fragment fits within page_height (the next page may have more room because there's no preceding content).

Mutates internal state (the @fragments cache). Idempotent for the same dimensions.



47
48
49
50
51
52
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
# File 'lib/pdfrb/layout/multi_page_table_box.rb', line 47

def fragments(available_width:, first_page_height:, later_page_height:)
  compute_column_widths(available_width)
  header = @header_row_count.positive? ? @rows.first(@header_row_count) : []
  body = @rows[@header_row_count..] || []

  fragments = []
  cursor = 0
  first_pass = true
  while cursor < body.length
    available = first_pass ? first_page_height : later_page_height
    max_count = rows_that_fit(body, cursor, available)
    count = if max_count.zero? && cursor.zero?
              # First fragment must contain at least one row
              # even if it overflows; otherwise the table is
              # undrawable.
              [@min_rows_per_page, body.length].min
            elsif max_count < @min_rows_per_page
              # Force at least min_rows_per_page (may overflow).
              [@min_rows_per_page, body.length - cursor].min
            else
              max_count
            end
    break if count.zero?

    chunk = body[cursor, count]
    rows_for_fragment = header + chunk
    fragments << build_fragment(rows_for_fragment)
    cursor += count
    first_pass = false
  end
  fragments
end