Merge pull request #21683 from calixteman/fix/quad-regex-writer

Don't write numbers in exponential notation when saving a pdf
This commit is contained in:
calixteman 2026-08-03 17:49:23 +02:00 committed by GitHub
commit ba7bf7b26c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 38 additions and 5 deletions

View File

@ -148,6 +148,31 @@ async function writeArray(array, buffer, transform) {
buffer.push("]");
}
// The exponential notation isn't valid in a PDF, hence a number is always
// written with all its digits.
function numberToPDFString(value) {
// `toFixed` uses the exponential notation from 1e21 on, so such a number is
// written thanks to BigInt: it's necessarily an integer, and `isInteger` also
// rules out NaN and ±Infinity for which BigInt would throw.
if (Number.isInteger(value) && Math.abs(value) >= 1e21) {
return BigInt(value).toString();
}
// Below that limit `toFixed(10)` never uses the exponential notation (unlike
// `toString` which uses it under 1e-6) and it rounds the value: it always
// adds 10 decimals, hence scan them backwards to remove the trailing zeros,
// and then the dot itself when none of the decimals is left.
const str = value.toFixed(10);
let end = str.length;
while (str[end - 1] === "0") {
end--;
}
if (str[end - 1] === ".") {
end--;
}
return str.slice(0, end);
}
async function writeValue(value, buffer, transform) {
if (value instanceof Name) {
buffer.push(`/${escapePDFName(value.name)}`);
@ -165,9 +190,7 @@ async function writeValue(value, buffer, transform) {
// matrices (e.g. [0.000008 0 0 0.000008 0 0]).
// The numbers must be "rounded" only when pdf.js is producing them and the
// current transformation matrix is well known.
// toFixed(10) avoids scientific notation and rounds; the replace removes
// trailing zeros (and a trailing dot for integers).
buffer.push(value.toFixed(10).replace(/\.?0+$/, ""));
buffer.push(numberToPDFString(value));
} else if (typeof value === "boolean") {
buffer.push(value.toString());
} else if (value instanceof Dict) {

View File

@ -295,10 +295,20 @@ describe("Writer", function () {
});
it("should not use scientific notation for very large numbers", async function () {
// JavaScript produces scientific notation above ~1e21 but such values
// are unlikely in PDFs; values below that threshold must be plain.
// JavaScript's toString() and toFixed() produce scientific notation from
// 1e21 on, which is invalid PDF: such a number must be written with all
// its digits, which are the exact ones of the underlying double.
expect(await serialize(1e10)).toEqual("10000000000");
expect(await serialize(1.5e6)).toEqual("1500000");
expect(await serialize(1e20)).toEqual("100000000000000000000");
expect(await serialize(1e21)).toEqual("1000000000000000000000");
// Removing the trailing zeros of the exponent used to change the value:
// "1e+30" was written "1e+3" and "1e+100" was written "1e+1".
expect(await serialize(1e30)).toEqual("1000000000000000019884624838656");
expect(await serialize(-1e30)).toEqual(
"-1000000000000000019884624838656"
);
expect((await serialize(1e100)).length).toEqual(101);
});
it("should round to at most 10 decimal places", async function () {