mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-04 05:17:24 +02:00
Don't write numbers in exponential notation when saving a pdf
`toFixed(10)` switches to the exponential notation from 1e21 on, which isn't valid PDF syntax, and removing the trailing zeros then dropped a digit of the exponent: 1e30 was written "1e+3" and 1e100 "1e+1". Such a number, necessarily an integer, is now written with all its digits. The trailing zeros are removed with a backward scan, since `toFixed(10)` always produces exactly 10 decimals. Below the 1e21 limit its output is at most 33 characters long, so the previous `$`-anchored regex wasn't a performance issue.
This commit is contained in:
parent
d0779c411e
commit
5375bff642
@ -148,6 +148,31 @@ async function writeArray(array, buffer, transform) {
|
|||||||
buffer.push("]");
|
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) {
|
async function writeValue(value, buffer, transform) {
|
||||||
if (value instanceof Name) {
|
if (value instanceof Name) {
|
||||||
buffer.push(`/${escapePDFName(value.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]).
|
// 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
|
// The numbers must be "rounded" only when pdf.js is producing them and the
|
||||||
// current transformation matrix is well known.
|
// current transformation matrix is well known.
|
||||||
// toFixed(10) avoids scientific notation and rounds; the replace removes
|
buffer.push(numberToPDFString(value));
|
||||||
// trailing zeros (and a trailing dot for integers).
|
|
||||||
buffer.push(value.toFixed(10).replace(/\.?0+$/, ""));
|
|
||||||
} else if (typeof value === "boolean") {
|
} else if (typeof value === "boolean") {
|
||||||
buffer.push(value.toString());
|
buffer.push(value.toString());
|
||||||
} else if (value instanceof Dict) {
|
} else if (value instanceof Dict) {
|
||||||
|
|||||||
@ -295,10 +295,20 @@ describe("Writer", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not use scientific notation for very large numbers", async function () {
|
it("should not use scientific notation for very large numbers", async function () {
|
||||||
// JavaScript produces scientific notation above ~1e21 but such values
|
// JavaScript's toString() and toFixed() produce scientific notation from
|
||||||
// are unlikely in PDFs; values below that threshold must be plain.
|
// 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(1e10)).toEqual("10000000000");
|
||||||
expect(await serialize(1.5e6)).toEqual("1500000");
|
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 () {
|
it("should round to at most 10 decimal places", async function () {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user