Controlling content flow with Prawn
Asked Answered
M

5

13

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?

Mazur answered 24/6, 2013 at 18:9 Comment(0)
C
10

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.

Cirque answered 4/7, 2013 at 3:50 Comment(4)
I have also created a pull request for a option flag to column_box, but as they are (slowly) trying for release and this a feature pull not a bug fix I would not expect it anytime soon. See github.com/prawnpdf/prawn/pull/512 for another approach (which would require more monkey patching).Cirque
I'll have to play around with this and see if I can make it work for me, but seems like the right direction. Thanks for sharing your experience with this.Mazur
You are very welcome. Let me know if it does not work for you as it might indicate a latent bug that will eventually bite me :-)Cirque
There's a bug here where if you specify the height of the reflow_column_box on the first page it'll maintain that height on later pages despite resetting the y position. I'm working on a fix but advice welcomed.Jussive
S
10

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)
Snowinsummer answered 25/7, 2014 at 13:43 Comment(0)
S
2

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.

Sheers answered 29/6, 2013 at 13:41 Comment(3)
@mario- I tried your example but it only generated a single page.Mazur
That's true. If that last call to text_box still returns leftover text, you have to start a new page yourself I guess. If you intend on using this solution, you should probably wrap that whole procedure in a method. As I said, this is more a workaround than a solution, but I did not find anything better.Sheers
@mario- OK, thanks for this. I was hoping Prawn had better layout support and I wouldn't be reduced to manually doing things like this, but this might be the only solution after all..Mazur
S
2

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
Sole answered 21/4, 2014 at 13:43 Comment(1)
How would you then clear a float? as in the css equivalent of clear: bothJocularity
K
-1

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

Katusha answered 29/6, 2013 at 15:11 Comment(4)
Don't see how grids would help at all with this layout scenario. Could you demonstrate?Mazur
Sorry for the delay...no internet for quit a while. I like using the grid layout because you have great control. I havent played with your layout but, I would think that placing the 'title' across columns (using the grid) your remaining content would flow without repeating the title area.Katusha
@hellion- Still don't see how that would help. Please include some example code.Mazur
i've used the grid and floats. I'll use floats instead of the grid when I can.Idealistic

© 2022 - 2024 — McMap. All rights reserved.