-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix precision rounding issues in LineWrapper #1595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
All these issues with rounding does raise the question as to whether we should be compliant with the spec and use 32bit numbers everywhere. but thats one for the maintainers to decide |
Moved back to draft as in even more obscure cases im still noticing line wrapping when it shouldnt. I will come back with a more comprehensive PR that will rework this. feel free to close in the mean time |
In changelog there's an entry saying that the rounding issues are fixed. Is is accurate? |
@blikblum they were partially fixed. fixed on the Y axis, there is however issues on the X axis which this PR aims to fix. however the solution is not as trivial and raises issues regarding how we handle rounding |
f90ef9e
to
8788842
Compare
Now fixed on the X axis too (note this modifies wordWidth, although this should be only used internally anyway) it forces numbers to be rounded down (into 32bit float) before being compared with other numbers ensuring random numerical precision doesn't introduce weird behaviour |
8788842
to
cb8603c
Compare
In response to this, we have now forced the text calculations to be float32 rather than float64, the difference with extremely small numbers will not be noticeable on actual documents, however the text flow will be much more expected |
lib/utils.js
Outdated
const fArray = new Float32Array(1); | ||
const uArray = new Uint32Array(fArray.buffer); | ||
|
||
export function PDFNumber(n, roundUp = false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this function called with roundUp = true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by default it will roundDown
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could simplify and only implement roundDown. If needed later implement roundUp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only reason i left both was that if we want to do float64 > float32
it would need to be rounded up
@@ -85,10 +85,10 @@ class LineWrapper extends EventEmitter { | |||
} | |||
|
|||
wordWidth(word) { | |||
return ( | |||
return PDFNumber( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not be rounded only when storing the number to the pdf file?
wordWidth is called in many intermediate steps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to ensure that during the line_wrapper computations we are keeping a float32 number so we could either wrap all instances of wordWidth with the rounder, or just do it here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or alternatively we wrap every comparison operation with PDFNumber
to ensure the rounding never affects it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or alternatively we wrap every comparison operation with
PDFNumber
to ensure the rounding never affects it
Lets take the current approach, adding tests to ensure correctness. Later we can try to minimize number of roundings
pdfmake uses a lot of pdfkit internals @liborm85 any thoughts on this? |
ab58315
to
2eda4e6
Compare
@@ -405,4 +405,24 @@ describe('table', function () { | |||
}, | |||
); | |||
}); | |||
|
|||
test('multi page table', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whats the output running with master?
It add ellipsis?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh you meant that as a test, you had commented on #1613 😅 , ill correct now and ill post what it does on master
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can keep this also
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2eda4e6
to
9c33a81
Compare
9c33a81
to
1c1d275
Compare
Failing due to miniscule pixel differences between OS versions (I see the same thing on macos with |
It occurred in my env also. Maybe there's a way to configure the pixel difference tolerance |
we can set it based on num pixels or percentage, @blikblum which would you prefer and what value |
The minimum to pass and specific to this test. |
67f4b51
to
9b6e9ba
Compare
That's fine, pdfmake does not use this LineWrapper. |
I created a unit test for bounded text. Visual tests should be used only when strictly necessary I added a new matcher toContainText Also removed the round up code path since is not being used currently. Please review if its correct For the record if deems necessary to use round up, here is the original code const fArray = new Float32Array(1);
const uArray = new Uint32Array(fArray.buffer);
export function PDFNumber(n, roundUp = false) {
// PDF numbers are strictly 32bit
// so convert this number to a 32bit number
// @see ISO 32000-1 Annex C.2 (real numbers)
const rounded = Math.fround(n);
if (roundUp) {
if (rounded >= n) return rounded;
} else if (rounded <= n) return rounded;
// Will have to perform 32bit float truncation
fArray[0] = n;
// Get the 32-bit representation as integer and shift bits
if (!(roundUp ^ (n > 0))) {
uArray[0] += 1;
} else {
uArray[0] -= 1;
}
// Return the float value
return fArray[0];
}
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@blikblum lgtm
What kind of change does this PR introduce?
Similar to #1583 this fixes the rounding errors occurring during text width calculations
Checklist:
Closes #1611