prawn
prawn copied to clipboard
Prawn's use of floats is causing wrapping problems
In my print on demand application, we have to do a lot of look-ahead calculations so we can properly arrange lots of text boxes in columns. During the course of all the work to figure out spacing, we ended up introducing a float rounding error. Below is a minimal test script that shows the problem in action.
require 'prawn'
Prawn::Document.generate('output.pdf') do |pdf|
pdf.font 'Helvetica'
width = pdf.width_of('Item 2', {size: 15}) # => 41.684999999999995
# SOMEWHERE ELSE - This is just standard float rounding errors sneaking in as
# we add and subtract from the width related to other things being drawn on
# the document.
width += 1.01
width += 1.01
width -= 2.02
# Back to our story
pdf.formatted_text_box(
[{text: 'Item 2', font: 'Helvetica', size: 15, color: '000000'}],
{ at: [250, 720] , width: width, height: 72, align: :left, valign: :top }
)
pdf.stroke { pdf.rectangle [250, 720], width, 72 }
end

Now in my application, I worked around this by taking the output of #width_of
and storing it in a BigDecimal, doing all my work in fixed precision, then passed it back into prawn.
require 'prawn'
require 'bigdecimal'
Prawn::Document.generate('output.pdf') do |pdf|
pdf.font 'Helvetica'
width = pdf.width_of('Item 2', {size: 15}) # => 41.684999999999995
width = BigDecimal.new width, 16
# SOMEWHERE ELSE - This is just standard float rounding errors sneaking in as
# we add and subtract from the width related to other things being drawn on
# the document.
width += 1.01
width += 1.01
width -= 2.02
# Back to our story
pdf.formatted_text_box(
[{text: 'Item 2', font: 'Helvetica', size: 15, color: '000000'}],
{ at: [250, 720] , width: width, height: 72, align: :left, valign: :top }
)
pdf.stroke { pdf.rectangle [250, 720], width, 72 }
end

I wanted to throw out there a general question of do we think this is ok? In PDF::Core we round to 4 decimal places on output, so if we enforced some kind of limit to our precision around there we wouldn't be really changing output. I would consider returning BigDecimal but I have concerns about the cap on positive sized numbers that would enforce
irb(main):004:0> BigDecimal.new(12345678.89, 5).to_f
=> 12346000.0
Does anyone have any thoughts on this? @mojavelinux Have you run into similar problems with Asciidoctor?
/cc @cheba
@packetmonkey Don't know if this is still a problem for you but floating point precision is a general problem not directly related to prawn. You could try using a higher precision with BigDecimal
or you could use Rational
instead (however, this would imply using rational numbers throughout, i.e. converting all floats to rational before operations: x += 1.01.to_r
).
The way I handled this in Asciidoctor PDF is to avoid having the numbers compound. That's along the lines with what @gettalong is suggesting. I try to keep the numbers in the original format until I need the actual number, then convert to float and pass the result to Prawn. When I do have to round a bit, I round in the favor of extra space so we don't go over. (so basically I have the luxury of working around the problem)
I think we'll stay with Floats in Prawn and PDF::Core. At least in this major version of Prawn. Float shenanigans are a known phenomenon. We round floats only at the very end right when we put them into the final document. The error is usually not too big so unless you need absolute precision it probably doesn't make much difference. Most output devices can't output at that precision (at 100% zoom). If you absolutely need that precision, you can do calculations in decimal. I can't guarantee it but I believe in most cases Prawn and PDF::Core can handle decimals.
Anyway, I'm putting it on my big list of features for a major version bump and will consider it later. This is not happening in the current iteration of Prawn.