gettalong / hexapdf

Versatile PDF creation and manipulation for Ruby
https://hexapdf.gettalong.org
Other
1.21k stars 69 forks source link

How to reposition content to bottom of a page #273

Closed siemdo closed 8 months ago

siemdo commented 9 months ago

I'm facing a challenge while generating a PDF and attempting to reposition content within the document. My objective is to move "This is more body text" and "This is even more body text" to the bottom of the first page while keeping "This is a header" and "This is a body text" at the top. The data array is provided via an API and the number of items between the "split" and "new_page" items can vary.

I've attempted various approaches such as using boxes, TextFragments, and TextLayouter but I'm not having any luck. I'm hoping you can point me in the right direction.

Thank you for your assistance!

data = [
  { type: "text", text: "This is a header", style: :header },
  { type: "text", text: "This is a body text", style: :content },
  { type: "split" },
  { type: "text", text: "This is more body text", style: :content },
  { type: "text", text: "This is even more body text", style: :content },
  { type: "new_page" },
  { type: "text", text: "This is a header", style: :header },
  { type: "text", text: "This is a body text", style: :content }
]

composer = HexaPDF::Composer.new

data.each do |item|
  case item[:type]
  when "text"
    composer.text(item[:text], style: item[:style])
  when "split"
    # move "This is more body text" and "This is even more body text" to the bottom of the first page while retaining "This is a header" and "This is a body text" at the top
  when "new_page"
    composer.new_page
  end
end

composer.write("test.pdf")
gettalong commented 9 months ago

This is currently not easily possible because there is no support in Frame for putting a box to its bottom. I will implement this for the next release.

gettalong commented 8 months ago

I have implemented the needed changes and pushed them to the Github devel branch. The following will now work:

require 'hexapdf'

data = [
  { type: "text", text: "This is a header", style: :header },
  { type: "text", text: "This is a body text", style: :content },
  { type: "split" },
  { type: "text", text: "This is more body text", style: :content },
  { type: "text", text: "This is even more body text", style: :content },
  { type: "new_page" },
  { type: "text", text: "This is a header", style: :header },
  { type: "text", text: "This is a body text", style: :content }
]

composer = HexaPDF::Composer.new

valign = :top
collect = []
data.each do |item|
  case item[:type]
  when "text"
    if valign == :bottom
      collect << item
    else
      composer.text(item[:text], valign: valign)
    end
  when "split"
    valign = :bottom
  when "new_page"
    collect.reverse_each do |item|
      composer.text(item[:text], valign: :bottom, mask_mode: :fill_horizontal)
    end
    composer.new_page
    valign = :top
  end
end

composer.write("test.pdf")

What this code does is:

Alternatively, you could collect the items, put them into a container box (e.g. composer.column(columns: 1) {|col| ... }) and then output that whole container box aligned to the bottom.