Starting new page if Bounding Box doesn't fit

2,419 views
Skip to first unread message

Valera

unread,
Oct 13, 2009, 4:18:14 PM10/13/09
to Prawn
I'm generating a somewhat-repetitive PDF file. The part that's
repeated is a chunk of data, including a two tables and some images.
This is usually repeated about a couple dozen times. The only problem
is when a bounding box is at the bottom of the page and runs out of
room, it will place the rest of the text/table/whatever at the same
location on the next page.

I know that this is how bounding boxes are supposed to work. The
preferred way to do this (in my opinion) is to figure out if a the box
will have enough room, and if not, then start a new page before
drawing it.

However, it's impossible (right?) to determine the height the box will
be (content varies, could be 10 lines, could be 20 lines, or anything
in between) until it's rendered to the file, at which point it's too
late to start a new page before that box.

Any way to figure out the height of the box before it's rendered? I
thought about adding up the line heights, but there could (probably
would) be line wrapping, so again I can't tell how many lines there
will be until after rendering.

I also though about using a Table, but that only accepts text, no
complicated stuff like other tables/images.

What are your suggestions? How would you go about (or have gone about)
doing this?

Any help is greatly appreciated!!

Brad Ediger

unread,
Oct 13, 2009, 5:15:09 PM10/13/09
to prawn...@googlegroups.com
On Tue, Oct 13, 2009 at 3:18 PM, Valera <vetr...@gmail.com> wrote:
> I know that this is how bounding boxes are supposed to work. The
> preferred way to do this (in my opinion) is to figure out if a the box
> will have enough room, and if not, then start a new page before
> drawing it.

This is pretty much the exact motivation for the group() feature
that's new in master and scheduled for Prawn 0.6:

http://github.com/sandal/prawn/blob/master/lib/prawn/document.rb#L392

If that's not quite what you're looking for, you should be able to
adapt it for your needs. The big change was the transactional
rendering support that we factored in, which lets you render a block
and then roll it back (for example, if it overflowed onto another
page, as in this example).

Brad

Valera

unread,
Oct 14, 2009, 8:28:27 AM10/14/09
to Prawn
Thanks!! That seems like exactly what I need! Very grateful for your
help!!!

On Oct 13, 5:15 pm, Brad Ediger <b...@bradediger.com> wrote:

Valera

unread,
Oct 14, 2009, 10:23:11 AM10/14/09
to Prawn
I've tried it in my code and it worked great!

However, I have two bounding_box(es) inside the group block. If these
run out of space, they will go onto the next page like normal bounding
boxes (same position). The only reason I have to nested bounding_box
(es) is because they have fixed width, to control text flow.

With that in mind, I can see from the source code (correct me if I'm
wrong) that group() doesn't take any parameters like width or
whatever.

So my question is how whether I would have to abandon bounding_box
altogether if I want to achieve the desired grouping behavior?

Valera

unread,
Oct 14, 2009, 2:15:21 PM10/14/09
to Prawn
That worked up to a point. When I started to seriously test the
grouping behavior and re-doing the more intricate features of the
layout, things started to go wrong...

I've taken screenshots of the PDF:
original: http://picasaweb.google.com/lh/photo/zTDEOHznl8qkozVILn1_GA?feat=directlink
annotated: http://picasaweb.google.com/lh/photo/Oc_kDZ2QSN_iK4zOASUT-g?feat=directlink

Basically, to take care of the header at the top of the page, I used a
bounding_box that encompasses the main content. This has worked
perfectly (before) for keeping the content and header separate. This
is the blue border on the annotated pic.

Inside the bounding_box, I have the group(), indicated in red in the
annotated pic.

To avoid the issue described in my previous post, I used two span(s)
(green in annotated pic). In order to get the spans' tops to be at the
same level, I record the cursor position before rendering the left one
and then place an invisible bounding_box (pink in pic) at that level
to move the cursor back to there. Then I continue with rendering the
second span.

Everything is fine until the page ends. Then I notice two things:
1) Each of the spans gets its own page...
2) group() ignores its containing bounding_box and renders the block
at the top of the page.

My question is what is causing #1 (outer bounding_box, or invisible
bounding_box, or something else entirely)?
As for #2, I think that's a bug, or maybe that's how it's supposed to
work...

In the meantime, I will look at Prawn's source code to try to see what
is causing the problem and how it can be fixed...

Thanks again for pointing me in the direction of group().

Valera

unread,
Oct 14, 2009, 2:27:37 PM10/14/09
to Prawn
Sorry for posting so many times, but I replaced the invisible
bounding_box with a y= assignment to move the cursor. Unfortunately,
that did not change the PDF at all.

At least now I know that wasn't causing the problem.

On Oct 14, 2:15 pm, Valera <vetru...@gmail.com> wrote:
> That worked up to a point. When I started to seriously test the
> grouping behavior and re-doing the more intricate features of the
> layout, things started to go wrong...
>
> I've taken screenshots of the PDF:
> original:http://picasaweb.google.com/lh/photo/zTDEOHznl8qkozVILn1_GA?feat=dire...
> annotated:http://picasaweb.google.com/lh/photo/Oc_kDZ2QSN_iK4zOASUT-g?feat=dire...

Brad Ediger

unread,
Oct 14, 2009, 2:51:23 PM10/14/09
to prawn...@googlegroups.com
On Wed, Oct 14, 2009 at 1:15 PM, Valera <vetr...@gmail.com> wrote:
>
> That worked up to a point. When I started to seriously test the
> grouping behavior and re-doing the more intricate features of the
> layout, things started to go wrong...
>
> I've taken screenshots of the PDF:
> original: http://picasaweb.google.com/lh/photo/zTDEOHznl8qkozVILn1_GA?feat=directlink
> annotated: http://picasaweb.google.com/lh/photo/Oc_kDZ2QSN_iK4zOASUT-g?feat=directlink

I'm starting to get a picture of what's going on here. But this is
still pretty complicated. It would help if you could reduce it to a
minimal test case (with code and non-confidential data) that explains
the problem and how you're trying to solve it. That way the rest of us
can try tweaking things and can reproduce the errors on our own.

In Prawn terms, what group() does is:

1. Attach itself to its containing bounding box, and intercept any
time that bounding box tries to flow to a new page inside the group()
block.

2. If/when that happens, roll back any of the drawing that has been
done, tell the bounding box to start a new page, and draw again there.
This "tell the bounding box to start a new page" part may be where the
issue comes into play -- the transaction code by itself does not
adjust the y-position, leaving it up to the bounding box to do that.
Take a look at the group() code and ask any questions you might have.

I haven't done anything as complicated as your nested bounding box
example, and I wrote the code for group(), so it's very possible that
this is a bug. :-) Like I said, if you can mock this up in a
pure-Prawn example that the rest of us can run, it will be easier to
see what you're trying to do.

Brad

Valera

unread,
Oct 14, 2009, 4:13:13 PM10/14/09
to Prawn
Thanks a lot for the help... It seems I've found a way to do this
without any bounding_box(es) or span(s) in the group().

Basically, now, I just use the text method and move the y-position
around: (pdf is the Prawn::Document object)
pdf.group do
[...some text...]
ref_y=pdf.y
[...do all the stuff from first bounding_box/span...]
pdf.y=ref_y
[...do all the stuff from second bounding_box/span, but indent it
by some value (160 in my case)...]
[...all pdf.text "..." is now pdf.text "...", :at =>
[160,pdf.cursor]...]
[...all pdf.stroke_horizontal_rule is now
pdf.stroke_horizontal_line 160, pdf.bounds.right...]
[...pdf.table gets a :position => 160 argument passed to it...]
end

That way, there are no bounding_box(es) or span(s) and group() results
in the expected behavior.

Unfortunately, I don't (anymore) have the problematic code, so I'm
unable :( to post it here for you... But I hope my code helps you
somewhat (or anybody else for that matter).

Anyway, thanks for your help, though.

And I think that my problem #2 is probably a bug. I got around it by
removing the outer bounding_box, and making the top margin bigger,
while keeping the header in the same position...


On Oct 14, 2:51 pm, Brad Ediger <b...@bradediger.com> wrote:
> On Wed, Oct 14, 2009 at 1:15 PM, Valera <vetru...@gmail.com> wrote:
>
> > That worked up to a point. When I started to seriously test the
> > grouping behavior and re-doing the more intricate features of the
> > layout, things started to go wrong...
>
> > I've taken screenshots of the PDF:
> > original:http://picasaweb.google.com/lh/photo/zTDEOHznl8qkozVILn1_GA?feat=dire...
> > annotated:http://picasaweb.google.com/lh/photo/Oc_kDZ2QSN_iK4zOASUT-g?feat=dire...

Brad Ediger

unread,
Oct 14, 2009, 4:21:12 PM10/14/09
to prawn...@googlegroups.com
On Wed, Oct 14, 2009 at 3:13 PM, Valera <vetr...@gmail.com> wrote:
>
> Thanks a lot for the help... It seems I've found a way to do this
> without any bounding_box(es) or span(s) in the group().
>
> Basically, now, I just use the text method and move the y-position
> around: (pdf is the Prawn::Document object)
> pdf.group do
>    [...some text...]
>    ref_y=pdf.y
>    [...do all the stuff from first bounding_box/span...]
>    pdf.y=ref_y
>    [...do all the stuff from second bounding_box/span, but indent it
> by some value (160 in my case)...]
>    [...all pdf.text "..." is now pdf.text "...", :at =>
> [160,pdf.cursor]...]
>    [...all pdf.stroke_horizontal_rule is now
> pdf.stroke_horizontal_line 160, pdf.bounds.right...]
>    [...pdf.table gets a :position => 160 argument passed to it...]
> end
>
> That way, there are no bounding_box(es) or span(s) and group() results
> in the expected behavior.

I'm glad that solved your problem. FYI, there's an undocumented (thus
as-yet unsupported) method called mask() that allows you to execute a
block while preserving the value of a few attributes. Your three lines
after "...some text..." could be written like this:

pdf.mask(:y) { ... do all the stuff from first bounding box / span ... }

I'll caution you again that it's undocumented and unsupported, but
it's probably a more readable option than manually mucking around with
the y attribute.

Brad

Valera

unread,
Oct 15, 2009, 8:38:03 AM10/15/09
to Prawn
Thanks again. To help you test/implement features, I've tried to
remember what I had before...

Here's what I think I had before: (not with exact text, instead of
data.each, I'll do #.times)

pdf.header pdf.margin_box.top_left do
pdf.text "Activities"
pdf.text "From #{@date_start} to #{@date_end}"
pdf.text "Physician: XXX"
end

pdf.bounding_box [pdf.margin_box.left - 50, pdf.margin_box.top] do
20.times do |n|
pdf.group do
pdf.stroke_horizontal_rule
pdf.text "Patient #, Patient Name"

top_edge = pdf.cursor
pdf.bounding_box [pdf.margin_box.left, pdf.cursor], :width
=> 150 do
pdf.text "activity"
data = Array.new
(rand(7)+1).times do
data << ["Something", "date"]
end
pdf.table data, :position => :center, :row_colors =>
['ffffff'], :vertical_padding => 2, :horizontal_padding => 2, :align
=> {0 => :left, 1 => :right}, :column_widths => {0 => 100, 1 =>
50}, :border_width => 0
end

pdf.y = top_edge
pdf.bounding_box [pdf.margin_box.left + 160,
pdf.cursor], :width => pdf.margin_box.width - 160 do
pdf.text "plan"
pdf.stroke_horizontal_rule
data = [["Header", "A", "B", "D"]]
(rand(7)+1).times do
data << ["Plan", "To", "Do", "Date"]
end
pdf.table data, :position => :left, :row_colors =>
['ffffff'], :vertical_padding => 2, :horizontal_padding => 2, :align
=> {0 => :left, 1 => :left, 2 => :left, 3 => :right}, :column_widths
=> {0 => 70, 1 => 50, 2 => 50, 3 => 70}, :border_width => 0
pdf.stroke_horizontal_rule
pdf.text "Some Comments..."
end
end
end
end

Hope this helps Prawn's development!

On Oct 14, 4:21 pm, Brad Ediger <b...@bradediger.com> wrote:
Reply all
Reply to author
Forward
0 new messages