Drawing underlays?

807 views
Skip to first unread message

Kristian Mandrup

unread,
May 22, 2010, 6:11:18 PM5/22/10
to Prawn
Tried to add the ability to draw underlays to a Text box, that is to
render "stuff" below the text layer.
But whatever I render here still ends up covering the text?
How could I make this work?

def draw_fragment(fragment, accumulated_width=0, line_width=0,
word_spacing=0) #:nodoc:
...
if @inked
--->>> draw_fragment_underlays(fragment)

if @align == :justify
@document.word_spacing(word_spacing) {
@document.draw_text!(fragment.text, :at => [x, y],
:kerning => @kerning)
}
else
@document.draw_text!(fragment.text, :at => [x, y],
:kerning => @kerning)
end

draw_fragment_overlays(fragment)
end

Debugging with some puts
=>
underlays
fill rectangle: aaaaaa
draw text
overlays

I guess it works in a rather different way? How do I control the order
of the draw operations!?

Gregory Brown

unread,
May 22, 2010, 6:14:25 PM5/22/10
to prawn...@googlegroups.com
On 5/22/10 6:11 PM, Kristian Mandrup wrote:

> I guess it works in a rather different way? How do I control the order
> of the draw operations!?

You draw the graphics before the text.

-greg

Anuj Dutta

unread,
May 22, 2010, 6:25:30 PM5/22/10
to prawn...@googlegroups.com
So, does that mean graphics becomes a sort of the background. Also, does prawn handle transparency issues with graphics?

Anuj


------------------

Gregory Brown

unread,
May 22, 2010, 6:23:35 PM5/22/10
to prawn...@googlegroups.com
On 5/22/10 6:25 PM, Anuj Dutta wrote:
> So, does that mean graphics becomes a sort of the background.

No. It means that anything you render earlier goes at a lower z order
than anything you render later.

> Also,
> does prawn handle transparency issues with graphics?

Yes, read the examples.

-greg

Anuj Dutta

unread,
May 22, 2010, 7:10:23 PM5/22/10
to prawn...@googlegroups.com
Cool. Thanks. I should have read the examples. Cheersm


------------------

-----Original Message-----
From: Gregory Brown <gregor...@letterboxes.org>
Date: Sat, 22 May 2010 18:23:35
To: <prawn...@googlegroups.com>
Subject: Re: Drawing underlays?

Kristian Mandrup

unread,
May 22, 2010, 7:11:56 PM5/22/10
to Prawn
I have the following "underlay" code

draw_fragment_underlays(fragment)
@document.draw_text!(fragment.text, :at => [x, y], :kerning =>
@kerning)
draw_fragment_overlays(fragment)

--
def draw_fragment_underlays(fragment)
draw_fragment_underlay_box(fragment)
end

def draw_fragment_underlay_box(fragment)
at = fragment.bounding_box.at
at[1] += fragment.height
width = fragment.bounding_box.width
height = fragment.bounding_box.height

@document.join_style :miter
@document.line_width fragment.border if
fragment.border

old_color = @document.stroke_color
@document.stroke_color = fragment.border_color if
fragment.border_color
if fragment.fill_color
puts "fill it: #{fragment.fill_color}"
@document.fill_color fragment.fill_color

@document.fill_and_stroke_rectangle(at, width, height)
else
puts "no filling :( : #{fragment.fill_color}"
@document.stroke_rectangle(at, width, height)
@document.render
end
@document.stroke_color = old_color
end

But my pdf ends up with a filled rectangle covering my text :(
How do I ensure the filled rectangle is RENDERED before (and UNDER)
the text that is rendered after, thus simulating z-order or multiple
"layers"?
Or do I have to handle this outside of the "core" prawn?
Thanks!


On May 22, 6:23 pm, Gregory Brown <gregory_br...@letterboxes.org>
wrote:

Kristian Mandrup

unread,
May 22, 2010, 7:19:45 PM5/22/10
to Prawn
Inserted some forced render calls, but still the same effect.
Not this is INSIDE prawn, in prawn/text/box.rb

if @inked
draw_fragment_underlays(fragment)
@document.render
if @align == :justify
@document.word_spacing(word_spacing) {
puts "draw text justify"
@document.draw_text!(fragment.text, :at => [x, y],
:kerning => @kerning)
}
else
puts "draw text"
@document.draw_text!(fragment.text, :at => [x, y],
:kerning => @kerning)
end
puts "overlays"
@document.render
draw_fragment_overlays(fragment)
@document.render


Daniel Nelson

unread,
May 22, 2010, 10:07:43 PM5/22/10
to prawn...@googlegroups.com
> But my pdf ends up with a filled rectangle covering my text :(
> How do I ensure the filled rectangle is RENDERED before (and UNDER)
> the text that is rendered after, thus simulating z-order or multiple
> "layers"?
> Or do I have to handle this outside of the "core" prawn?
> Thanks!

Formatted text already has fragment level callbacks after fragment
rendering. It would be trivial to add an additional
pre-render-callback. See
Prawn::Text::Formatted::Fragment#callback_object through #finished and
Prawn::Text::Formatted::Box#draw_fragment.

- Daniel

Kristian Mandrup

unread,
May 23, 2010, 2:47:00 AM5/23/10
to Prawn
Thanks :) Just what I was looking for! I'll take a look at it ASAP.

Kristian Mandrup

unread,
May 23, 2010, 12:24:11 PM5/23/10
to Prawn
I only see a reference to the 'finished' method in wrap

# wrap.rb

def wrap(array) #:nodoc:
printed_fragments << fragment.text

--> fragment.started # insert here????

format_and_draw_fragment(fragment, accumulated_width,
@line_wrap.width, word_spacing)
accumulated_width += fragment.width
fragment.finished

---
Are you proposing something like this?

# box.rb
def draw_fragment(fragment, accumulated_width=0, line_width=0,
word_spacing=0) #:nodoc:
fragment.started
...
fragment.finished
end

---
Prawn::Text::Formatted::Fragment

def callback_start_object
callback[:start_object]
end

def callback_start_method
callback[:start_method]
end

def callback_start_arguments
callback[:start_arguments]
end

def started
if callback_start_object && callback_start_method
if callback_start_arguments
callback_start_object.send(callback_start_method, self,
*callback_start_arguments)
else
callback_start_object.send(callback_start_method, self)
end
end
end
private

def callback_start
@format_state[:callback_start] || {}
end

Kristian Mandrup

unread,
May 23, 2010, 12:48:49 PM5/23/10
to Prawn
Looking into it a bit closer, the approach with a callback looks like
a pretty akward path for what I'm trying to achieve.
All I want is to add the ability to render something before the text
inside Prawn::Text::Formatted::Fragment, in case certain options such
as :background are set on the fragment @format_state.
An internal feature addition, not something I want to control
externally.
I wouldn't want to have to use callbacks for this if I can avoid it.
A callback solution might be to add an 'underlay' callback in the
Fragment#initialize method depending on whether the @format_state has
a :background entry. This seems a bit crazy IMO. Callbacks are for
external use, not to be used like this from an internal context,
right?

In the end I just might have to use my original solution, something
like this. All I was trying to do was to internalize this kind of
behavior in Prawn core somehow.

b = Prawn::Text::Formatted::Box.new(line, options.merge(:document
=> pdf))

b.render(:dry_run => true)
puts "box width: #{b.width}, bounds width: #{pdf.bounds.width}"
pdf.fill_color("ffeeee")
pdf.fill_and_stroke_rectangle(at, min(b.width, pdf.bounds.width),
b.height)
b.render
----------------------------

Callback "solution" ?


class FragmentUnderlayCallback
def draw_fragment_underlay_box(fragment)
at = fragment.bounding_box.at
at[1] += fragment.height
width = fragment.bounding_box.width
height = fragment.bounding_box.height

@document.join_style :miter
@document.line_width fragment.border if fragment.border

old_color = @document.stroke_color
@document.stroke_color = fragment.border_color if
fragment.border_color
if fragment.fill_color
puts "fill it: #{fragment.fill_color}"
@document.fill_color fragment.fill_color

@document.fill_and_stroke_rectangle(at, width, height)
else
puts "no filling :( : #{fragment.fill_color}"
@document.stroke_rectangle(at, width,
height)
end
@document.stroke_color = old_color
end
end

def underlay?(format_options)
format_options[:background_color] ||
format_options[:background_image]
end

def draw_line(pdf, line, options, height_adj = 0)
# height = line_height(pdf, line, options)
b = nil
pdf.pad_top(height_adj) do
at = [0, pdf.cursor]
options.merge!(:at => at)
puts "options: #{options.inspect}"

callback_object = FragmentUnderlayCallback.new
callback = {:callback => { :object => callback_object, :method
=> :draw_fragment_underlay_box } }
line.each{|o| o.merge!(callback) if underlay?(o)}

b = Prawn::Text::Formatted::Box.new(line, options.merge(:document
=> pdf))
# b.render(:dry_run => true)
b.render
end
b.height
end

Kristian Mandrup

unread,
May 23, 2010, 1:06:50 PM5/23/10
to Prawn
I doesn't make any difference what order the drawing operations in
draw_fragment come in.
This produces the same result

if @inked
draw_fragment_overlays(fragment)
@document.draw_text!(fragment.text, :at => [x, y], :kerning =>
@kerning)
end

As this

if @inked
draw_fragment_overlays(fragment)
@document.draw_text!(fragment.text, :at => [x, y], :kerning =>
@kerning)
end

So it seems, that graphics are always drawn on top of Text. How can I
circumvent this behavior for a fragment?

It seems it is all controlled from wrap.rb:

def format_and_draw_fragment(fragment, accumulated_width,
line_width, word_spacing)
@arranger.apply_color_and_font_settings(fragment) do
draw_fragment(fragment, accumulated_width,
line_width, word_spacing)
end
end

wrap(array)
fragment.started
format_and_draw_fragment(fragment, accumulated_width,
@line_wrap.width, word_spacing)
accumulated_width += fragment.width
fragment.finished

--

I guess what I need is to force a dry-run rendering the Text fragment,
then render the Fragment underlay (background), then render the Text
for real (on top) somehow,
all controlled from this 'wrap' function?

I am lost...

Kristian Mandrup

unread,
May 23, 2010, 1:13:42 PM5/23/10
to Prawn
using Text::Formatted::Box.new in conjunction with #render(:dry_run =>
true) enables one to do look-ahead
calculations prior to placing text on the page, or to determine how
much vertical space was consumed by the printed text

So only the Box is only rendered as a whole, not the individual
fragments. So I have to control it from the box level.
First do a dry run to collect look-ahead info. Then somehow render the
backgrounds for each fragment separately, then render the box for
real.
Then wrap it all as a simple easy-to-use function.

Kristian Mandrup

unread,
May 23, 2010, 1:29:57 PM5/23/10
to Prawn
Finally solved it using the "outer" box approach.

b = Prawn::Text::Formatted::Box.new(fragments,
options.merge(:document => pdf))
# collect fragment meta info used for drawing underlays
etc.
b.render(:dry_run => true)

# optional background for box
# pdf.fill_color("ffeeee")
# pdf.fill_and_stroke_rectangle(at, min(b.width,
pdf.bounds.width), b.height)

# background/underlay for each fragment
b.fragments.each do |fragment|
draw_fragment_underlay_box(pdf, fragment)
end

# render fragments including texts and overlays
b.render

Daniel Nelson

unread,
May 23, 2010, 9:14:23 PM5/23/10
to prawn...@googlegroups.com
> a :background entry. This seems a bit crazy IMO. Callbacks are for
> external use, not to be used like this from an internal context,
> right?

Prawn aims to provide a highly extensible foundation for generating
PDF documents. The PDF specification includes a long list of
annotations. Prawn does not need to be able to produce all of these
annotations, but it should provide an extensible foundation for
building these annotations. Plus, people may want to be able to do
things other than what Prawn supports out of the box (currently,
support includes color (rgb or CYMK), font, font size, bold, italics,
underline, strikethrough, subscript, superscript, and links...maybe
forgetting something here). Callbacks are very simple to define, so I
don't know why you wouldn't want to use them. It is certainly not
"crazy" to use them.

It sounds like the real problem is that callbacks currently fire only
after a text fragment has been rendered. I certainly want formatted
text to enable full extensibility of formatted text. So the real
question becomes: what is the best api? I'll pose that question a
separate thread for the core developers.

-Daniel

Kristian Mandrup

unread,
May 24, 2010, 1:15:03 AM5/24/10
to Prawn
Hi Daniel,

Thanks for the feedback. I was just lost trying to get a grip of how
Prawn works on the inside and thus how to extend it the right way.
Would be nice with some kind of documentation on the general flow/
architecture of the rendering process.
I thought the fragments would somehow render themselves, whereas in
fact rendering is all done from the context of the Text Box.
So it's the Text Box that must control the rendering order and ensure
some elements are rendered on top of others. When in need of meta
information, it's sometimes necessary to use the
dry-run approach before performing the real-deal render. I think I'm
getting the hang of it.

Would be nice with more support for callbacks for sure :) Always a
good way to provide flexibility/hooks for extensions.
However I don't see why I should use callbacks in my scenario. I'm
trying to add more CSS like styling options to core, where some
require rendering before/underneath the text, others "above" it.
I would like to add background, border and margin, padding etc. as
extra styling options.

This effort is part of the HOWRAH project (html2pdf) I've started
working on with Ajun. Parts of it this project makes sense as
externals to prawn, other parts as additions to Prawn core.
Maybe Prawn should be split up in more modules like other projects?
But I guess even considering this would only make sense after 1.0 when
prawn is somewhat more stable again ;)
Cheers!

- Kristian

Gregory Brown

unread,
May 24, 2010, 6:48:08 AM5/24/10
to prawn...@googlegroups.com
On 5/24/10 1:15 AM, Kristian Mandrup wrote:
> Hi Daniel,
>
> Thanks for the feedback. I was just lost trying to get a grip of how
> Prawn works on the inside and thus how to extend it the right way.
> Would be nice with some kind of documentation on the general flow/
> architecture of the rendering process.

> Parts of it this project makes sense as
> externals to prawn, other parts as additions to Prawn core.
> Maybe Prawn should be split up in more modules like other projects?

Kristian: You are very new to this project and I don't think that
qualifies you for deciding what belongs in core and what doesn't, and
how Prawn should be split up.

Please don't call our APIs crazy or complain about how it's not like
other projects. We are happy to help you with specific questions, but I
think that you are mixing up some of the real problems you're having
with the experience of just not knowing your way around yet.

This is a complex project, take some time to get to know it. While
html2pdf is important, I see more emails from you on this list than
emails about Prawn presently. That's fine if it was necessary to get
past the initial hump, but it should not be that way moving forward.

-greg
Reply all
Reply to author
Forward
0 new messages