asciidoctor / asciidoctor-pdf

:page_with_curl: Asciidoctor PDF: A native PDF converter for AsciiDoc based on Asciidoctor and Prawn, written entirely in Ruby.
https://docs.asciidoctor.org/pdf-converter/latest/
MIT License
1.14k stars 500 forks source link

Add instance_variable_set :@node, node to convert_table to support table continuation #2471

Closed bai-yi-bai closed 7 months ago

bai-yi-bai commented 7 months ago

This request is based on the AsciiDoctor Zulipchat discussion: Long Tables: Table Caption on Every Page after Page Break?

My understanding that once this is in place, it should make it easier for me to write an extended converter which will allow me to add "(Continued)" to tables which span multiple pages.

mojavelinux commented 7 months ago

Once this change is made, the following code can be used to override add_header to add the table caption (block title) to each continued page. The add_header method is called to repeat the table header on subsequent pages.

Prawn::Table.prepend (Module.new do
  def add_header(*)
    height = 0
    if @node.title?
      height += (@pdf.ink_caption @node, dry_run: true)
      @pdf.ink_caption @node
    end
    height + super
  end
end)

If you want append text to the title, pass a custom string to ink_caption instead of the node. For example:

Prawn::Table.prepend (Module.new do
  def add_header(*)
    height = 0
    if @node.title?
      title = %(#{@node.captioned_title} (continued))
      height += (@pdf.ink_caption title, dry_run: true)
      @pdf.ink_caption title
    end
    height + super
  end
end)
mojavelinux commented 7 months ago

It's possible to apply the custom add header logic only to the current converter instance so as to avoid modifying the global instance of prawn table.

class MyPDFConverter < (Asciidoctor::Converter.for 'pdf')
  register_for 'pdf'

  def init_pdf(*)
    super
    extend PrawnTableInterfaceExt # necessary since prawn-table is mixed in after the pdf is initialized
  end
end

module PrawnTableInterfaceExt
  def table data, options = {}, &block
    t = (Prawn::Table.new data, self, options, &block).extend PrawnTableExt
    t.draw
    t
  end
end

module PrawnTableExt
  def add_header(*)
    height = 0
    if @node.title?
      height += (@pdf.ink_caption @node, category: :table, dry_run: true)
      @pdf.ink_caption @node, category: :table
    end
    height + super
  end
end
mojavelinux commented 7 months ago

Technically, this is possible to customize the header without the proposed changed. However, as you will see, the code has to resort to defining and removing a temporary method to do so (and it wouldn't work with nested tables). I think this helps justify why this proposed change is important.

class MyPDFConverter < (Asciidoctor::Converter.for 'pdf')
  register_for 'pdf'

  def convert_table node
    singleton_class.define_method :table, (method :table_with_node).curry.(node)
    result = super
    singleton_class.send :remove_method, :table
    result
  end

  def table_with_node node, data, options = {}, &block
    t = (Prawn::Table.new data, self, options, &block).extend PrawnTableExt
    t.instance_variable_set :@node, node
    t.draw
    t
  end
end

module PrawnTableExt
  def add_header(*)
    height = 0
    if @node.title?
      height += (@pdf.ink_caption @node, category: :table, dry_run: true)
      @pdf.ink_caption @node, category: :table
    end
    height + super
  end
end