Yarin
Yarin

Reputation: 183989

Controlling content flow with Prawn

Let's say we want to display a title on the first page that takes up the top half of the page. The bottom half of the page should then fill up with our article text, and the text should continue to flow over into the subsequent pages until it runs out:

enter image description here

This is a pretty basic layout scenario but I don't understand how one would implement it in Prawn.

Here's some example code derived from their online documentation:

pdf = Prawn::Document.new do
  text "The Prince", :align => :center, :size => 48
  text "Niccolò Machiavelli", :align => :center, :size => 20
  move_down 42

  column_box([0, cursor], :columns => 3, :width => bounds.width) do
  text((<<-END.gsub(/\s+/, ' ') + "\n\n") * 20)
   All the States and Governments by which men are or ever have been ruled,
   have been and are either Republics or Princedoms. Princedoms are either
   hereditary, in which the bla bla bla bla .....
   END
  end
end.render

but that will just continue to show the title space for every page:

enter image description here

What's the right way to do this?

Upvotes: 13

Views: 5539

Answers (5)

Baylor Rae&#39;
Baylor Rae&#39;

Reputation: 4010

I know this is old, but I thought I'd share that a new option has been added to fix this in v0.14.0.

:reflow_margins is an option that sets column boxes to fill their parent boxes on new page creation.

column_box(reflow_margins: true, columns: 3)

Upvotes: 10

user3556792
user3556792

Reputation: 21

Use floats.

float do
        span((bounds.width / 3) - 20, :position => :left) do
                # Row Table Code
        end
end

float do
        span((bounds.width / 3) - 20, :position => :center) do
                # Row Table Code
        end
end

float do
        span((bounds.width / 3) - 20, :position => :right) do
                # Row Table Code
        end
end

Upvotes: 2

Patrick Hurley
Patrick Hurley

Reputation: 381

I have been fighting with this same problem. I ended up subclassing ColumnBox and adding a helper to invoke it like so:

module Prawn
  class Document

    def reflow_column_box(*args, &block)
      init_column_box(block) do |parent_box|
        map_to_absolute!(args[0])
        @bounding_box = ReflowColumnBox.new(self, parent_box, *args)
      end
    end

    private

    class ReflowColumnBox < ColumnBox 
      def move_past_bottom 
        @current_column = (@current_column + 1) % @columns
        @document.y = @y
        if 0 == @current_column
          @y = @parent.absolute_top
          @document.start_new_page
        end
      end
    end
  end
end

Then it is invoked exactly like a normal column box, but on the next page break will reflow to the parents bounding box. Change your line:

  column_box([0, cursor], :columns => 3, :width => bounds.width) do

to

  reflow_column_box([0, cursor], :columns => 3, :width => bounds.width) do

Hope it helps you. Prawn is pretty low level, which is a two-edged sword, it sometimes fails to do what you need, but the tools are there to extend and build more complicated structures.

Upvotes: 10

hellion
hellion

Reputation: 4850

Use Prawns grid layout instead. It is very well documented...and easier to control your layout.

Upvotes: -1

mario
mario

Reputation: 1248

So, the column_box method creates a bounding box. The documented behavior of the bounding box is that it starts at the same position as on the previous page if it changes to the next page. So the behavior you are seeing is basically correct, also not what you want. The suggested workaround I have found by googling is to use a span instead, because spans do not have this behavior.

The problem now is, how to build text columns with spans? They don't seem to support spans natively. I tried to build a small script that mimicks columns with spans. It creates one span for each column and aligns them accordingly. Then, the text is written with text_box, which has the overflow: :truncate option. This makes the method return the text that did not fit in the text box, so that this text can then be rendered in the next column. The code probably needs some tweaking, but it should be enough to demonstrate how to do this.

require 'prawn'

text_to_write = ((<<-END.gsub(/\s+/, ' ') + "\n\n") * 20)
 All the States and Governments by which men are or ever have been ruled,
 have been and are either Republics or Princedoms. Princedoms are either
 hereditary, in which the bla bla bla bla .....
 END

pdf = Prawn::Document.generate("test.pdf") do
  text "The Prince", :align => :center, :size => 48
  text "Niccolò Machiavelli", :align => :center, :size => 20
  move_down 42

  starting_y = cursor
  starting_page = page_number

  span(bounds.width / 3, position: :left) do
    text_to_write = text_box text_to_write, at: [bounds.left, 0], overflow: :truncate
  end

  go_to_page(starting_page)
  move_cursor_to(starting_y)

  span(bounds.width / 3, position: :center) do
    text_to_write = text_box text_to_write, at: [bounds.left, 0], overflow: :truncate
  end

  go_to_page(starting_page)
  move_cursor_to(starting_y)

  span(bounds.width / 3, position: :right) do
    text_box text_to_write, at: [bounds.left, 0]
  end
end

I know this is not an ideal solution. However, this was the best I could come up with.

Upvotes: 2

Related Questions