Compare commits

...
22 Commits
Author SHA1 Message Date
Tim van der Meij 2581d8f70a Merge pull request #22065 from Snuffleupagus/ChunkedStreamManager-Set-direct-delete
Directly call `Set.prototype.delete` in the `ChunkedStreamManager` code
2026-10-03 19:35:05 +02:00
Tim van der Meij 7445074a76 Merge pull request #22046 from calixteman/rgb-to-rgba-alignment
Fix a few edge cases when converting RGB images in `convertRGBToRGBA` and `ImageResizer`
2026-10-03 19:09:12 +02:00
Tim van der Meij 36692033e1 Merge pull request #22066 from timvandermeij/bump
Bump the stable version in `pdfjs.config`
2026-10-03 19:00:28 +02:00
Tim van der Meij f40ca42ca7 Bump the stable version in pdfjs.config 2026-10-03 18:44:55 +02:00
Jonas Jenwald 1316be0dca Directly call Set.prototype.delete in the ChunkedStreamManager code
There's no point in first checking if a value exists in a Set before deleting it, and this is just a left-over from before the code used Maps and Sets.

Also, remove a couple of unnecessary comments describing how `Set.prototype.add` works (when those where added there wasn't much Set usage in the code-base).
2026-10-03 18:38:26 +02:00
Tim van der Meij d0991a0d53 Merge pull request #22064 from Snuffleupagus/_requestChunks-avoid-pointless-requestId
Avoid creating a pointless `requestId` in the `ChunkedStreamManager.prototype._requestChunks` method
2026-10-03 17:49:02 +02:00
Tim van der Meij d7e710169d Merge pull request #22062 from timvandermeij/updates
Update dependencies to the most recent versions
2026-10-03 17:46:33 +02:00
Jonas Jenwald bcf89431d5 Avoid creating a pointless requestId in the ChunkedStreamManager.prototype._requestChunks method
In cases where all the requested chunks are already available, and dispatching a range-request thus isn't necessary, there's no point in creating an unused `requestId` nor keeping track of it since it'll never be accessed given the early return.
2026-10-03 17:18:28 +02:00
calixteman 7594737f73 Fix a few edge cases when converting RGB images in convertRGBToRGBA and ImageResizer
- `convertRGBToRGBA` ignored the `byteOffset` of the source, which isn't zero
   when e.g. the data of an uncompressed image is read directly from the file,
   and it threw when the source wasn't 4-byte aligned. In that case all the
   pixels are now converted one by one.
   It also used `len >> 2`, which overflows for sources larger than 2 GiB.
 - In `ImageResizer.#rescaleImageData`, when the image had to be converted in
   several chunks:
   - if the chunk height divided the image height, the last chunk was converted
     with the full image height;
   - the rows were sampled relatively to each chunk, hence a chunk height which
     isn't a multiple of `2 ** K` shifted the sub-sampling grid and left the
     last rows of the result empty. They're now sampled on the image grid;
   - the chunk height is rounded to a multiple of 4 when possible, in order to
     keep the RGB data aligned;
   - if not even a single row fitted in the buffer, the loop never ended.
2026-10-03 17:04:16 +02:00
Tim van der Meij d03359cb57 Merge pull request #22061 from Snuffleupagus/SupportedImageMimeTypes-Set
Convert `SupportedImageMimeTypes` into a Set
2026-10-03 16:47:25 +02:00
Tim van der Meij 76f6eb6356 Fix vulnerability in the fast-uri dependency
This patch is automatically generated with `npm audit fix` and fixes
CVE-2026-86472.
2026-10-03 16:33:26 +02:00
Tim van der Meij e987c74cd6 Upgrade eslint-plugin-unicorn to version 77.0.0
This is a major version bump, but the changelog at
https://github.com/sindresorhus/eslint-plugin-unicorn/releases/tag/v77.0.0
doesn't indicate any breaking changes that should impact us.

Note that it does require a number of code changes due to improvements
that were made for the `unicorn/prefer-logical-operator-over-ternary`
(allowing us to remove previously added ignore lines; see
https://github.com/sindresorhus/eslint-plugin-unicorn/issues/3766)
and `unicorn/prefer-ternary` (to detect a new case) rules.
2026-10-03 16:27:45 +02:00
Tim van der Meij bf5f6ea4b1 Upgrade eslint-plugin-jsdoc to version 65.0.2
This is a major version bump, but the changelogs at
https://github.com/gajus/eslint-plugin-jsdoc/releases
don't indicate any breaking changes that should impact us.
2026-10-03 16:27:44 +02:00
Tim van der Meij 7dc3cf0421 Update dependencies to the most recent versions 2026-10-03 16:27:44 +02:00
calixteman e9d1c85c7c Merge pull request #22041 from calixteman/sampled-fn-arity
Check shading function dimensions and bound sampled interpolation
2026-10-03 16:26:30 +02:00
calixteman c67fe1529d Check shading function dimensions and bound sampled interpolation
Expose function input/output counts and reject incompatible shading and
stitching functions.

Reuse interpolation buffers instead of allocating cube vertices per call.
Skip axes at integer sample coordinates, fixing invalid sample accesses
for Size=1 axes.

For functions with more than eight inputs, use simplex interpolation.
It reads at most m + 1 sample-table entries per output for m inputs,
instead of up to 2^m. It can produce different values from multilinear
interpolation.
2026-10-03 16:22:43 +02:00
Tim van der Meij c1c20f6d56 Merge pull request #22060 from Snuffleupagus/FontInfo-rm-numbers
Stop exporting the Font `ascent` and `descent` by default, and remove handling of standalone number entries in the compiled font-info
2026-10-03 16:04:12 +02:00
Jonas Jenwald 1d29095df7 Convert SupportedImageMimeTypes into a Set
Using `Set.prototype.has` should be preferable to `Array.prototype.includes`, and given that `Iterator.prototype.join` is [supported in Firefox](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/join#browser_compatibility) (and polyfilled elsewhere) creating the string-representation isn't a problem.
2026-10-03 16:03:17 +02:00
calixteman fecf042a90 Merge pull request #22058 from calixteman/text-layer-stale-font-cache
Avoid resetting the text-layer measurement font
2026-10-03 15:53:14 +02:00
Jonas Jenwald edd591f54b Remove handling of standalone number entries in the compiled font-info
After the previous patch this code is no longer used.
2026-10-03 14:11:31 +02:00
Jonas Jenwald e43c8ebf23 Stop exporting the Font ascent and descent by default
The only main-thread usage of these properties are in a fallback code-path in the `TextLayer` code, see below, however in that case these properties are accessed via the `TextStyle` object rather than through the font-info:
 - https://github.com/mozilla/pdf.js/blob/87a106c03447d05ab8d759d5f9fa888310a7299e/src/display/text_layer.js#L566-L579

 - https://github.com/mozilla/pdf.js/blob/87a106c03447d05ab8d759d5f9fa888310a7299e/src/display/api.js#L1187-L1194

*Note:* The code to write and read numbers in the compiled font-info will be removed in a follow-up, to make it easier to re-instate that code in the future.
2026-10-03 14:03:49 +02:00
calixteman 62984d48db Avoid resetting the text-layer measurement font
Resizing the canvas in #getAscent reset ctx.font to 10px sans-serif
without updating #canvasCtxFonts. A subsequent 30px run using the
same family could skip setting the font and get an incorrect width.

Remove the resizing left over from the pixel-based fallback removed
in PR #19399. Add a regression test comparing 30px and 33px runs.
2026-10-03 12:21:10 +02:00
37 changed files with 940 additions and 381 deletions
+1 -1
View File
@@ -3345,7 +3345,7 @@ function packageJson() {
bugs: DIST_BUGS_URL,
license: DIST_LICENSE,
optionalDependencies: {
"@napi-rs/canvas": "^1.0.8",
"@napi-rs/canvas": "^1.0.10",
},
browser: {
canvas: false,
+238 -215
View File
@@ -14,26 +14,26 @@
"@fluent/dom": "^0.10.2",
"@metalsmith/layouts": "^3.0.0",
"@metalsmith/markdown": "^1.10.0",
"@napi-rs/canvas": "^1.0.9",
"@types/node": "^26.6.2",
"@napi-rs/canvas": "^1.0.10",
"@types/node": "^26.6.4",
"autoprefixer": "^10.6.1",
"babel-loader": "^10.1.1",
"babel-plugin-istanbul": "^8.0.2",
"babel-plugin-polyfill-corejs3": "^1.0.0",
"cached-iterable": "^0.3.0",
"caniuse-lite": "^1.0.30001810",
"caniuse-lite": "^1.0.30001814",
"core-js": "^3.50.0",
"eslint": "^10.11.0",
"eslint": "^10.12.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-jasmine": "^4.2.2",
"eslint-plugin-jsdoc": "^64.5.4",
"eslint-plugin-jsdoc": "^65.0.2",
"eslint-plugin-no-unsanitized": "^4.1.5",
"eslint-plugin-perfectionist": "^5.11.1",
"eslint-plugin-perfectionist": "^5.12.1",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-regexp": "^3.3.0",
"eslint-plugin-unicorn": "^76.0.0",
"globals": "^17.12.0",
"eslint-plugin-regexp": "^3.3.1",
"eslint-plugin-unicorn": "^77.0.0",
"globals": "^17.13.0",
"gulp": "^5.0.1",
"gulp-cli": "^3.1.0",
"gulp-postcss": "^10.0.0",
@@ -51,15 +51,15 @@
"metalsmith-html-relative": "^2.0.13",
"ordered-read-streams": "^2.0.0",
"postcss": "^8.5.28",
"postcss-discard-comments": "^9.0.3",
"postcss-discard-comments": "^9.0.6",
"postcss-values-parser": "^8.0.0",
"prettier": "^3.9.8",
"puppeteer": "^25.11.0",
"stylelint": "^17.15.0",
"prettier": "^3.9.9",
"puppeteer": "^25.12.0",
"stylelint": "^17.16.0",
"stylelint-prettier": "^5.0.3",
"svglint": "^4.2.1",
"terser-webpack-plugin": "^5.6.1",
"tsc-alias": "^1.9.4",
"tsc-alias": "^1.9.7",
"ttest": "^4.0.0",
"typescript": "^6.0.3",
"vinyl": "^3.0.1",
@@ -2988,9 +2988,9 @@
}
},
"node_modules/@csstools/css-tokenizer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
"integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.2.tgz",
"integrity": "sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==",
"dev": true,
"funding": [
{
@@ -3222,13 +3222,13 @@
}
},
"node_modules/@eslint/css-tree": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-4.0.5.tgz",
"integrity": "sha512-iPmijIAq4hlIJB86PYmY/fcZORHtjphSqICDbwuw32A/JmkhZQ/K/6TjHE03zqf3n5yABpVcbRAMG8Mi9ojy8g==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-4.1.1.tgz",
"integrity": "sha512-A+s89eP+yDAPkKjnIOfjlkGifg5J7GPn01nWx14UjBVWktfkxOU4975F9ZZ13xNxkGiblNcBaAqgKhnQrW6SWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"mdn-data": "2.29.0",
"mdn-data": "2.35.0",
"source-map-js": "^1.2.1"
},
"engines": {
@@ -3236,9 +3236,9 @@
}
},
"node_modules/@eslint/css-tree/node_modules/mdn-data": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.29.0.tgz",
"integrity": "sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==",
"version": "2.35.0",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.35.0.tgz",
"integrity": "sha512-k+1+dEIm2z/BEfdvYzT/fIyAGeDBo/1AJNJI4ilFXJRYoxP/3Ds+GP8shAOIq8jYXY9N5nGB3Ag104NsagmLAA==",
"dev": true,
"license": "CC0-1.0"
},
@@ -3640,9 +3640,9 @@
}
},
"node_modules/@napi-rs/canvas": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.9.tgz",
"integrity": "sha512-QviPdJImDi/jMAvBfqaw+19BndMd/sizXVW3NnpMd3VJGz++QXkOHcP9kWR/smHG0hNjHeyuHFyrx/5lD0oNcQ==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.10.tgz",
"integrity": "sha512-V+qptzsGnPMFxEbhwc6eDNMe5eDrfiBIp8qGpuRjAVIX4Kn2zxvwQ1Xco6Fwe+3tYIe3iLW8LM9D+SvjyJhqbQ==",
"dev": true,
"license": "MIT",
"workspaces": [
@@ -3656,23 +3656,23 @@
"url": "https://github.com/sponsors/Brooooooklyn"
},
"optionalDependencies": {
"@napi-rs/canvas-android-arm64": "1.0.9",
"@napi-rs/canvas-darwin-arm64": "1.0.9",
"@napi-rs/canvas-darwin-x64": "1.0.9",
"@napi-rs/canvas-linux-arm-gnueabihf": "1.0.9",
"@napi-rs/canvas-linux-arm64-gnu": "1.0.9",
"@napi-rs/canvas-linux-arm64-musl": "1.0.9",
"@napi-rs/canvas-linux-riscv64-gnu": "1.0.9",
"@napi-rs/canvas-linux-x64-gnu": "1.0.9",
"@napi-rs/canvas-linux-x64-musl": "1.0.9",
"@napi-rs/canvas-win32-arm64-msvc": "1.0.9",
"@napi-rs/canvas-win32-x64-msvc": "1.0.9"
"@napi-rs/canvas-android-arm64": "1.0.10",
"@napi-rs/canvas-darwin-arm64": "1.0.10",
"@napi-rs/canvas-darwin-x64": "1.0.10",
"@napi-rs/canvas-linux-arm-gnueabihf": "1.0.10",
"@napi-rs/canvas-linux-arm64-gnu": "1.0.10",
"@napi-rs/canvas-linux-arm64-musl": "1.0.10",
"@napi-rs/canvas-linux-riscv64-gnu": "1.0.10",
"@napi-rs/canvas-linux-x64-gnu": "1.0.10",
"@napi-rs/canvas-linux-x64-musl": "1.0.10",
"@napi-rs/canvas-win32-arm64-msvc": "1.0.10",
"@napi-rs/canvas-win32-x64-msvc": "1.0.10"
}
},
"node_modules/@napi-rs/canvas-android-arm64": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.9.tgz",
"integrity": "sha512-4LGXk2/0HVzE29K8SzML5WubgCp++B1FH3qgl35XmSZE+lLdr6P9VRQEnZ0MCLZMTSuJP41yyhtoiVNEuvrTIA==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.10.tgz",
"integrity": "sha512-0sDMvme+7fj6bHERUaBs5SvEyWaORc91VUkaB4RrtG7p6dBrBNGy1of8cyEKDxygd1FlHtW+Thunj8lSixzY7A==",
"cpu": [
"arm64"
],
@@ -3691,9 +3691,9 @@
}
},
"node_modules/@napi-rs/canvas-darwin-arm64": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.9.tgz",
"integrity": "sha512-YNdfLBzY0W/Pep9fo2L6RmoNlNksnn05LRnX66W63R3ij58S25QOTcjdtEt2v8+PnCESzqZsYzUo+QPeIR44NA==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.10.tgz",
"integrity": "sha512-pdHL1TEfFMrnchRYqmPWXKN7hR4XtOYmzJqnh4XhpfWD4/eeSokOGVzrdFP/iQ3sfr2rf0939gOGq9uCp8BGmg==",
"cpu": [
"arm64"
],
@@ -3712,9 +3712,9 @@
}
},
"node_modules/@napi-rs/canvas-darwin-x64": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.9.tgz",
"integrity": "sha512-ceZQSknTEcy3dOXoekv59LTCkXjvnLsq+VW5PeNNDHEPQbRS5Ervkm1EaDa7WLAjiYWMLuSQTRTHao1dEX4prg==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.10.tgz",
"integrity": "sha512-iuuk5plGxAcxurxv2ycF2yR20y5zvaBZ4heQdhPpg3EMFD21PhlY1V+YwSRmYRWzbvKL474UYdw3WIg2qfafyw==",
"cpu": [
"x64"
],
@@ -3733,9 +3733,9 @@
}
},
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.9.tgz",
"integrity": "sha512-XhfI0Wwv4llhd6nnWDtY3kQKjq0r+y1i91PlJlJI24ag2U9WrnwbG1qS3+fDLEyouwEVFchiKkTEHozK+5iUNA==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.10.tgz",
"integrity": "sha512-kH7GFR5Unpm77pKSEsmkqvzeQCRD7/MQ7upmeLLjg4XpQ62dX/R6THFsp6vkF1Pkco2aKUq9KhoLM4W5twdk7g==",
"cpu": [
"arm"
],
@@ -3754,9 +3754,9 @@
}
},
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.9.tgz",
"integrity": "sha512-012oiYtKaE7i9oxc8q7nraT7kDOpLcaCmFLzVe9Ty34RHDdoDzbWLrVh827CNxYh/EADX1eSikA3ymLjo/nNuw==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.10.tgz",
"integrity": "sha512-0/Hj7IwM9pmzdwxiqkp4YLJ8dKqASBrWQ3Y7dWUSU/4dgvCL9O6YNvX7xVUJjonQvdD3IE9ESS/nXa5srQqoVw==",
"cpu": [
"arm64"
],
@@ -3778,9 +3778,9 @@
}
},
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.9.tgz",
"integrity": "sha512-Ls5UWYFFn63casTZEczbeyEg3vRDRkv9lscuGwfchtY5yLLQhgOB8SN4YGxmfJ5vTaBwZ2YUxBS3NtmjJmFXdA==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.10.tgz",
"integrity": "sha512-fmjKM0P2MLF9O6XzCQsRjhB+jlLzaf6tFQU36hMPMsYup+VThYvpjjhqNLOc/Ee3tHUE3r13H2BSJx/yXz4pFA==",
"cpu": [
"arm64"
],
@@ -3802,9 +3802,9 @@
}
},
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.9.tgz",
"integrity": "sha512-hLKEGxV7ZiRHqndePTokgDMdBlo/rDfzg7P4p4QIv9pUhuYobnu3R2NIFLCRghG0nwfo+s2sw+c1xZFeCmEAsw==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.10.tgz",
"integrity": "sha512-O/BUrGwrs3pVP9PpD/3++ZiOpjHMJlmiB7aBgswP8rbe4egPfiFkFxmcCczoLvZa72TaQX5lJ8Udedb6rc/Hvg==",
"cpu": [
"riscv64"
],
@@ -3826,9 +3826,9 @@
}
},
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.9.tgz",
"integrity": "sha512-6kaz3w0QMy77PDWk6rJ1ksIihdad3qzEyX2o2oGT8GwCaypfT5mhjr8buOO5hstyLxcWXDScuz56RsINLtBPIQ==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.10.tgz",
"integrity": "sha512-48HkZPQeAN/R+9NPpY64tceoyCUW5xYYtHKZnC+BG11qiihXJCbH+xfbgGU+OdYp1Q4s84HDl9ILU0KBK6SBOQ==",
"cpu": [
"x64"
],
@@ -3850,9 +3850,9 @@
}
},
"node_modules/@napi-rs/canvas-linux-x64-musl": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.9.tgz",
"integrity": "sha512-xrGvmS3v55hmZ86ls/kBLVNMUTYio3f6Ik0DireemG994VfPAwiA3ZXA0Uf1bByctkB3NQ1Sfb+H5bkdUnnzfQ==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.10.tgz",
"integrity": "sha512-QYHVi7WZ9v+Wm9OQyHLMTnJ8QiBPVX94BgbCHFJC/4bCFMfvX7fRzo4FJPgkRP0ISz3ulYNow/FCbQOPjzhYjg==",
"cpu": [
"x64"
],
@@ -3874,9 +3874,9 @@
}
},
"node_modules/@napi-rs/canvas-win32-arm64-msvc": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.9.tgz",
"integrity": "sha512-yjmVS3ArZeRVCP7jqbPq4rpZa/BhTeI7ELE2XqJg3snICQBDevLZyArxswHkiTnT34KRic33/4fLirrHI+SY8A==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.10.tgz",
"integrity": "sha512-bWK+YajM/8sL0mMgLtug2s063FVBPzFV4QAn987CQomMKlmzrBY6Trz43KY+24hq7IOa3qNhUAgP/XMEF50ZEQ==",
"cpu": [
"arm64"
],
@@ -3895,9 +3895,9 @@
}
},
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.9.tgz",
"integrity": "sha512-QlSYQdMQslB81nlABo9wNfQ6npFhE7/O+saCZdqVGueGanyRk4jCogD5EwQenfP3kIq9e+mm6GreQBjX5MrA8g==",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.10.tgz",
"integrity": "sha512-9WtzW85PaIhtB27iXaR/9RrLCqHLbqD3Z8Q+kh17NjFxwQXFXU5vWbYH4vEH+drM307TTyK3hidZgpt09G5T3g==",
"cpu": [
"x64"
],
@@ -4004,9 +4004,9 @@
}
},
"node_modules/@puppeteer/browsers": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.2.2.tgz",
"integrity": "sha512-q2BU4YfO9h/Wt7IcWPcggpOOqLk2Tbs1hDwolvKZrweRjy751OJBKMN9zO5bfD0pzU7X/tvKw/exQds4pM/LOg==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.2.3.tgz",
"integrity": "sha512-2Bt3m6dDAJqmZehn0wiXSYyQmuhyiHHZ7FjvrVaS1W2RoFGV6eCK71UvKYkNJjkwC1Q9nobjhvTMhLQztFVINA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -4144,9 +4144,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz",
"integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==",
"version": "26.6.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.4.tgz",
"integrity": "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4165,14 +4165,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.70.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.0.tgz",
"integrity": "sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==",
"version": "8.71.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.0.tgz",
"integrity": "sha512-aABjw5rjBacYONVPaPiWOCjJu0vEF4a25iQuodlmQYL1trtLZ0X/y+2Vzl3BKI1odM4LnwLE1oUDXYp1wzx1TQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.70.0",
"@typescript-eslint/types": "^8.70.0",
"@typescript-eslint/tsconfig-utils": "^8.71.0",
"@typescript-eslint/types": "^8.71.0",
"debug": "^4.4.3"
},
"engines": {
@@ -4187,14 +4187,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.70.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.0.tgz",
"integrity": "sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==",
"version": "8.71.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.0.tgz",
"integrity": "sha512-gWF0BhUcnjZxSpLE8ngS/59n2SB0J3YqRxvX1+2aoRJk9hNtHSLOV+TcarFiOr5ipXm3yc1QrI4c9YZc8zyCxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.70.0",
"@typescript-eslint/visitor-keys": "8.70.0"
"@typescript-eslint/types": "8.71.0",
"@typescript-eslint/visitor-keys": "8.71.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4205,9 +4205,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.70.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.0.tgz",
"integrity": "sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==",
"version": "8.71.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.0.tgz",
"integrity": "sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4222,9 +4222,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.70.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.0.tgz",
"integrity": "sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==",
"version": "8.71.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.0.tgz",
"integrity": "sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4236,16 +4236,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.70.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.0.tgz",
"integrity": "sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==",
"version": "8.71.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.0.tgz",
"integrity": "sha512-PEEF4G5sLLWAS5BpPrUvms4ySZkiBQQZM4z+3ReI46axK5Vqr/vXBQatJQIZZOYdGyPUAKTtsrWzpqKuU+3DEw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.70.0",
"@typescript-eslint/tsconfig-utils": "8.70.0",
"@typescript-eslint/types": "8.70.0",
"@typescript-eslint/visitor-keys": "8.70.0",
"@typescript-eslint/project-service": "8.71.0",
"@typescript-eslint/tsconfig-utils": "8.71.0",
"@typescript-eslint/types": "8.71.0",
"@typescript-eslint/visitor-keys": "8.71.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -4277,16 +4277,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.70.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.0.tgz",
"integrity": "sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==",
"version": "8.71.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.0.tgz",
"integrity": "sha512-pKR/tEMVrXZG23UFKUn5BQf3zfmfk7KQceI2cGzywZ5nxM5Eu3hEJU1utjWzydtzBbcJAQhHN8iPCxobHpPcZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.70.0",
"@typescript-eslint/types": "8.70.0",
"@typescript-eslint/typescript-estree": "8.70.0"
"@typescript-eslint/scope-manager": "8.71.0",
"@typescript-eslint/types": "8.71.0",
"@typescript-eslint/typescript-estree": "8.71.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4301,13 +4301,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.70.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.0.tgz",
"integrity": "sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==",
"version": "8.71.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.0.tgz",
"integrity": "sha512-8eQ9R218XORK+KLosnf4bu/QsUXvUyVwTbArg7/0NMB1Pu87OJKvj4nhFblkYE8gQV73mW1dx1ptlPCkwRGa7A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.70.0",
"@typescript-eslint/types": "8.71.0",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -5245,9 +5245,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.11.20",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz",
"integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==",
"version": "2.11.27",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.27.tgz",
"integrity": "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -5351,9 +5351,9 @@
}
},
"node_modules/browserslist": {
"version": "4.28.9",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz",
"integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==",
"version": "4.29.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz",
"integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==",
"dev": true,
"funding": [
{
@@ -5371,11 +5371,11 @@
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.11.20",
"caniuse-lite": "^1.0.30001810",
"electron-to-chromium": "^1.5.420",
"node-releases": "^2.0.54",
"update-browserslist-db": "^1.3.2"
"baseline-browser-mapping": "^2.11.26",
"caniuse-lite": "^1.0.30001813",
"electron-to-chromium": "^1.5.439",
"node-releases": "^2.0.57",
"update-browserslist-db": "^1.3.3"
},
"bin": {
"browserslist": "cli.js"
@@ -5474,9 +5474,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001810",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
"integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
"version": "1.0.30001814",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz",
"integrity": "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==",
"dev": true,
"funding": [
{
@@ -5695,9 +5695,9 @@
}
},
"node_modules/cliui/node_modules/ansi-regex": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz",
"integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==",
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz",
"integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6050,6 +6050,22 @@
"node": ">=4"
}
},
"node_modules/cssnano-utils": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-8.1.0.tgz",
"integrity": "sha512-Mzcc0ju/a19+qiqwPk+b6ieJa+tFVNNnrD2gLbk7CmENdJvlUtCuII+sIRZnfl1shHEKGQpmG2rwRpNaqe43xA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@csstools/css-tokenizer": "^4.0.2"
},
"engines": {
"node": "^22.22.3 || ^24.15.0 || >=26.0"
},
"peerDependencies": {
"postcss": "^8.5.28"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -6109,9 +6125,9 @@
}
},
"node_modules/devtools-protocol": {
"version": "0.0.1680125",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1680125.tgz",
"integrity": "sha512-rVARKAvOFJCFkvgX01VLvYqKIvt+cRCqvUxIhB2oAqVGPalxk3w3sb5TUGDagpow/EFblBpm/Zl/29QA2dd+jA==",
"version": "0.0.1687809",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1687809.tgz",
"integrity": "sha512-t7kSb+UKYdyxcmqy98U2P86ne6rCve3DIJ4+lp54i/hhClgFuG35JrLj23rO14Jw3sWDsp7i+22QhNU0+/qk8g==",
"dev": true,
"license": "BSD-3-Clause"
},
@@ -6249,9 +6265,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.427",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz",
"integrity": "sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==",
"version": "1.5.444",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.444.tgz",
"integrity": "sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ==",
"dev": true,
"license": "ISC"
},
@@ -6400,9 +6416,9 @@
}
},
"node_modules/eslint": {
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.11.0.tgz",
"integrity": "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==",
"version": "10.12.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.12.0.tgz",
"integrity": "sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==",
"dev": true,
"license": "MIT",
"workspaces": [
@@ -6561,15 +6577,15 @@
}
},
"node_modules/eslint-plugin-jsdoc": {
"version": "64.5.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-64.5.4.tgz",
"integrity": "sha512-xfRhXPSSWT612muJ6XTvxuVJ8zYHv99qNgBqPVqF28aSqoy4s4XbSkrWXnYA2hl5ejC02yEpk6fqkj3BmJ6Xbg==",
"version": "65.0.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-65.0.2.tgz",
"integrity": "sha512-zy1VuN7NNUmfXk15B5ZDAGTXBiye+JskSJZ22Qd5E3VlUluX/RHf8TXYTj8yradaKKJt+69aylhJS22TX/aeRA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@es-joy/jsdoccomment": "~0.98.0",
"@es-joy/resolve.exports": "1.2.0",
"@typescript-eslint/utils": "^8.70.0",
"@typescript-eslint/utils": "^8.70.1",
"are-docs-informative": "^0.1.1",
"comment-parser": "1.4.9",
"debug": "^4.4.3",
@@ -6633,12 +6649,13 @@
}
},
"node_modules/eslint-plugin-perfectionist": {
"version": "5.11.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-5.11.1.tgz",
"integrity": "sha512-xloHScnOpin9Jy3CegfzoBeY33ASk0spLyM2sJs0LyNHQjv9m0osUD2PYNT7NrJieZbUR2Ebn4A5Q0+0dOcv+A==",
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-5.12.1.tgz",
"integrity": "sha512-krdSE7Dz+TCtcVr2+rfNhlOIFFBJcUdoRvjaN7JQnsaVnxJw+BZh9wMkY3MSLCJat+yW+UPTQLKS82Epc7MGZA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "^8.70.0",
"@typescript-eslint/utils": "^8.70.0",
"natural-orderby": "^5.0.0"
},
@@ -6647,6 +6664,11 @@
},
"peerDependencies": {
"eslint": "^8.45.0 || ^9.0.0 || ^10.0.0"
},
"peerDependenciesMeta": {
"eslint": {
"optional": true
}
}
},
"node_modules/eslint-plugin-prettier": {
@@ -6681,9 +6703,9 @@
}
},
"node_modules/eslint-plugin-regexp": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-3.3.0.tgz",
"integrity": "sha512-TT0JTQbW7CRKohntpGAsMdQ1K3MsmJK4gdAhKJtIwlJ0JvuYxauymFgJdvqxIY4lqNj0EuRZWVrymQtEdJYEFg==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-3.3.1.tgz",
"integrity": "sha512-58bGUci/AaWAo1ETj6Lsnmy6QXwnSyVONRA805828nnqp7vrTwdaQMc02vtowmYB3A5OB3SLr3r5HgmfxX4dLg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6703,32 +6725,32 @@
}
},
"node_modules/eslint-plugin-unicorn": {
"version": "76.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-76.0.0.tgz",
"integrity": "sha512-3ywJrCMHKYhcBP5yFzGaOOX3dY7+iR3hvpuoS/0EnhRFgVWLejly+t4fOdJvWQxJdHZl1C+QBW7hybXPDld4Pw==",
"version": "77.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-77.0.0.tgz",
"integrity": "sha512-aayznMCKTXlPAsoyUjRok862sz3BJAfxpQX/t79AT/uVQTmWsCx3FXfXo+9hfhL8aR2mMZQYyXoDZo3/ly6lJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.10.1",
"@eslint/css-tree": "^4.0.5",
"browserslist": "^4.28.8",
"@eslint/css-tree": "^4.1.1",
"browserslist": "^4.29.1",
"change-case": "^5.4.4",
"ci-info": "^4.4.0",
"core-js-compat": "^3.50.0",
"detect-indent": "^7.0.2",
"entities": "^8.0.0",
"entities": "^8.1.0",
"find-up-simple": "^1.0.1",
"globals": "^17.11.0",
"globals": "^17.12.0",
"identifier-regex": "^1.1.0",
"indent-string": "^5.0.0",
"is-builtin-module": "^5.0.0",
"pluralize": "^8.0.0",
"quote-js-string": "^0.1.0",
"regjsparser": "^0.13.2",
"regjsparser": "^0.13.3",
"reserved-identifiers": "^1.2.0",
"semver": "^7.8.5",
"strip-indent": "^4.1.1",
"yaml": "^2.9.0"
"yaml": "^2.9.1"
},
"engines": {
"node": ">=22"
@@ -6741,9 +6763,9 @@
}
},
"node_modules/eslint-plugin-unicorn/node_modules/entities": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
"integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz",
"integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -7008,9 +7030,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
"integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
"integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
"dev": true,
"funding": [
{
@@ -7492,9 +7514,9 @@
}
},
"node_modules/globals": {
"version": "17.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz",
"integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==",
"version": "17.13.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz",
"integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9578,9 +9600,9 @@
}
},
"node_modules/node-releases": {
"version": "2.0.54",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz",
"integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==",
"version": "2.0.57",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz",
"integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -10125,13 +10147,14 @@
}
},
"node_modules/postcss-discard-comments": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-9.0.3.tgz",
"integrity": "sha512-qz3KiFa8/YNYzjEY7/FUpAewO3thNGbHqCkyRHX5z7CJ8mo2VPJxHq9nsFfOVbI1sByiixMSEoHBj7kAvtvMUA==",
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-9.0.6.tgz",
"integrity": "sha512-jK5bfKASmV7fSf6rL8XEU4uJaQUe5/5OFkA8pzHooUugV73uIg2JFEMnNwdHBOXJ+bAcGPZ7b7DxkXZdtqG7Bg==",
"dev": true,
"license": "MIT",
"dependencies": {
"postcss-selector-parser": "^7.1.6"
"@csstools/css-tokenizer": "^4.0.2",
"cssnano-utils": "^8.0.2"
},
"engines": {
"node": "^22.22.3 || ^24.15.0 || >=26.0"
@@ -10258,9 +10281,9 @@
}
},
"node_modules/prettier": {
"version": "3.9.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.8.tgz",
"integrity": "sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==",
"version": "3.9.9",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz",
"integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -10331,18 +10354,18 @@
}
},
"node_modules/puppeteer": {
"version": "25.11.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.11.0.tgz",
"integrity": "sha512-n+psxZFH/qlJ3gwTZMxTLUZoE98FAOEjT/BSxHXvPmNCGAfN/kq0UnIZ8m9RB4rAVvJr7zRHUnKKg98GLMUyyQ==",
"version": "25.12.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.12.0.tgz",
"integrity": "sha512-agM9cYcxbvmU17w9qtF17s7/Vv7Dhbyig6AWwIgvQQLR2dKZdPqNvRPanac7xH4iMPcNYOCfyCkwqCnR4nVOcA==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "3.2.2",
"@puppeteer/browsers": "3.2.3",
"chromium-bidi": "17.0.2",
"devtools-protocol": "0.0.1680125",
"devtools-protocol": "0.0.1687809",
"lilconfig": "^3.1.3",
"puppeteer-core": "25.11.0",
"puppeteer-core": "25.12.0",
"typed-query-selector": "^2.12.2"
},
"bin": {
@@ -10353,15 +10376,15 @@
}
},
"node_modules/puppeteer-core": {
"version": "25.11.0",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.11.0.tgz",
"integrity": "sha512-Ujym7JbxoAqanaxGcHgmTomfhC01HfDCSuGZ8Hs9boHrDjzbNVMnFRJo2KtbDT9LCRgeJoFnkn9JQZ41eEUrLw==",
"version": "25.12.0",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.12.0.tgz",
"integrity": "sha512-z6LQUt5SH7jwGtGA+nMtJuEjZIsqPiwQ+EFHCYuVDQrJpvifNmmGuyDvTzAKPyFzihxgHc5f3aMzu2kn8Ps37A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "3.2.2",
"@puppeteer/browsers": "3.2.3",
"chromium-bidi": "17.0.2",
"devtools-protocol": "0.0.1680125",
"devtools-protocol": "0.0.1687809",
"typed-query-selector": "^2.12.2",
"webdriver-bidi-protocol": "0.4.3",
"ws": "^8.21.3"
@@ -10556,9 +10579,9 @@
"license": "MIT"
},
"node_modules/regjsparser": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz",
"integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==",
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.3.tgz",
"integrity": "sha512-ycwFAS14Jw4mppvmK4GR/J6u3WpWpjkEApehuHtLc/8VpPNpDMbQ4WjqwplXifGeyKOzHSFLmSPqzksDQE2Sfg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -11255,9 +11278,9 @@
"license": "MIT"
},
"node_modules/stylelint": {
"version": "17.15.0",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.15.0.tgz",
"integrity": "sha512-mWIkesYQvQjf4Kvdeu9ns0IL8/K/wtjaGxPqsPd6DlLZvaQxGysJsocxUDrBcyHQ5VleAk4uSMat4yMrVED7PA==",
"version": "17.16.0",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.16.0.tgz",
"integrity": "sha512-ejlWnbJ13x1GU32+zZXa6sYQJsKKkwSdv2B4rAdO3OWdEmvhSJKcXi5pOphQYSzyaj1kXpuGJr7pcMBNfDIuPw==",
"dev": true,
"funding": [
{
@@ -12242,9 +12265,9 @@
}
},
"node_modules/tsc-alias": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.9.4.tgz",
"integrity": "sha512-vfhh/N9xQ6oRgdAvuXVbhHSsNtbk+D4LFbJ9YrFPTTP9evGAvf2Xc3MQ7lTVmnJNN+FnuoKHn3524CKDzFYOag==",
"version": "1.9.7",
"resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.9.7.tgz",
"integrity": "sha512-nGOOLMkaociegx2Y9J1lnbqrledf1EfSq3Ds4H4CLySG5RyD58KrR4ahFIAgpeMVJz4BIhJND3ETt02o9zKQAw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12341,9 +12364,9 @@
}
},
"node_modules/typed-query-selector": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz",
"integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==",
"version": "2.12.3",
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.3.tgz",
"integrity": "sha512-/dtuMZyutHLlrxWhhC1uib00ELUr2dZ8C9B8YzZYsJmeE3V56OxRblrSM2UaF142pcMtak7ehZ6+poWGeehNIg==",
"dev": true,
"license": "MIT"
},
@@ -12531,9 +12554,9 @@
}
},
"node_modules/update-browserslist-db": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz",
"integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
"integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==",
"dev": true,
"funding": [
{
@@ -13110,9 +13133,9 @@
}
},
"node_modules/ws": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
"version": "8.22.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz",
"integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -13172,9 +13195,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz",
"integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==",
"dev": true,
"license": "ISC",
"bin": {
@@ -13188,9 +13211,9 @@
}
},
"node_modules/yargs": {
"version": "18.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
"integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz",
"integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -13216,9 +13239,9 @@
}
},
"node_modules/yargs/node_modules/ansi-regex": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz",
"integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==",
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz",
"integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -13229,9 +13252,9 @@
}
},
"node_modules/yargs/node_modules/string-width": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
"integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz",
"integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==",
"dev": true,
"license": "MIT",
"dependencies": {
+14 -14
View File
@@ -9,26 +9,26 @@
"@fluent/dom": "^0.10.2",
"@metalsmith/layouts": "^3.0.0",
"@metalsmith/markdown": "^1.10.0",
"@napi-rs/canvas": "^1.0.9",
"@types/node": "^26.6.2",
"@napi-rs/canvas": "^1.0.10",
"@types/node": "^26.6.4",
"autoprefixer": "^10.6.1",
"babel-loader": "^10.1.1",
"babel-plugin-istanbul": "^8.0.2",
"babel-plugin-polyfill-corejs3": "^1.0.0",
"cached-iterable": "^0.3.0",
"caniuse-lite": "^1.0.30001810",
"caniuse-lite": "^1.0.30001814",
"core-js": "^3.50.0",
"eslint": "^10.11.0",
"eslint": "^10.12.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-jasmine": "^4.2.2",
"eslint-plugin-jsdoc": "^64.5.4",
"eslint-plugin-jsdoc": "^65.0.2",
"eslint-plugin-no-unsanitized": "^4.1.5",
"eslint-plugin-perfectionist": "^5.11.1",
"eslint-plugin-perfectionist": "^5.12.1",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-regexp": "^3.3.0",
"eslint-plugin-unicorn": "^76.0.0",
"globals": "^17.12.0",
"eslint-plugin-regexp": "^3.3.1",
"eslint-plugin-unicorn": "^77.0.0",
"globals": "^17.13.0",
"gulp": "^5.0.1",
"gulp-cli": "^3.1.0",
"gulp-postcss": "^10.0.0",
@@ -46,15 +46,15 @@
"metalsmith-html-relative": "^2.0.13",
"ordered-read-streams": "^2.0.0",
"postcss": "^8.5.28",
"postcss-discard-comments": "^9.0.3",
"postcss-discard-comments": "^9.0.6",
"postcss-values-parser": "^8.0.0",
"prettier": "^3.9.8",
"puppeteer": "^25.11.0",
"stylelint": "^17.15.0",
"prettier": "^3.9.9",
"puppeteer": "^25.12.0",
"stylelint": "^17.16.0",
"stylelint-prettier": "^5.0.3",
"svglint": "^4.2.1",
"terser-webpack-plugin": "^5.6.1",
"tsc-alias": "^1.9.4",
"tsc-alias": "^1.9.7",
"ttest": "^4.0.0",
"typescript": "^6.0.3",
"vinyl": "^3.0.1",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"stableVersion": "6.3.289",
"stableVersion": "6.4.299",
"baseVersion": "3801e03652642f11689fdf0d2b1a62b6626e4d17",
"versionPrefix": "6.4."
}
+6 -13
View File
@@ -82,8 +82,6 @@ class ChunkedStream extends Stream {
const endChunk = Math.floor((end - 1) / chunkSize) + 1;
for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
// Since a value can only occur *once* in a `Set`, there's no need to
// manually check `Set.prototype.has()` before adding the value here.
this._loadedChunks.add(curChunk);
}
}
@@ -107,8 +105,6 @@ class ChunkedStream extends Stream {
: Math.floor(position / this.chunkSize);
for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
// Since a value can only occur *once* in a `Set`, there's no need to
// manually check `Set.prototype.has()` before adding the value here.
this._loadedChunks.add(curChunk);
}
}
@@ -259,9 +255,9 @@ class ChunkedStream extends Stream {
class ChunkedStreamManager {
#aborted = false;
currRequestId = 0;
#requestId = 0;
_chunksNeededByRequest = new Map();
#chunksNeededByRequest = new Map();
#loadedStreamCapability = Promise.withResolvers();
@@ -324,10 +320,7 @@ class ChunkedStreamManager {
}
_requestChunks(chunks) {
const requestId = this.currRequestId++;
const chunksNeeded = new Set();
this._chunksNeededByRequest.set(requestId, chunksNeeded);
for (const chunk of chunks) {
if (!this.stream.hasChunk(chunk)) {
chunksNeeded.add(chunk);
@@ -337,6 +330,8 @@ class ChunkedStreamManager {
if (chunksNeeded.size === 0) {
return Promise.resolve();
}
const requestId = this.#requestId++;
this.#chunksNeededByRequest.set(requestId, chunksNeeded);
const capability = Promise.withResolvers();
this._promisesByRequest.set(requestId, capability);
@@ -469,10 +464,8 @@ class ChunkedStreamManager {
this._requestsByChunk.delete(curChunk);
for (const requestId of requestIds) {
const chunksNeeded = this._chunksNeededByRequest.get(requestId);
if (chunksNeeded.has(curChunk)) {
chunksNeeded.delete(curChunk);
}
const chunksNeeded = this.#chunksNeededByRequest.get(requestId);
chunksNeeded.delete(curChunk);
if (chunksNeeded.size > 0) {
continue;
+2 -2
View File
@@ -80,14 +80,12 @@ const PRIVATE_USE_AREAS = [
const PDF_GLYPH_SPACE_UNITS = 1000;
const EXPORT_DATA_PROPERTIES = [
"ascent",
"bbox",
"black",
"bold",
// "charProcOperatorList" is handled separately, since it's not compiled.
"cssFontInfo",
"data",
"descent",
"disableFontFace",
"fallbackName",
"fontExtraProperties",
@@ -103,10 +101,12 @@ const EXPORT_DATA_PROPERTIES = [
];
const EXPORT_DATA_EXTRA_PROPERTIES = [
"ascent",
"composite",
"defaultEncoding",
"defaultVMetrics",
"defaultWidth",
"descent",
"differences",
"isMonospace",
"isSerifFont",
+130 -49
View File
@@ -100,7 +100,14 @@ function toNumberArray(arr) {
return arr;
}
// See chapter 3, page 109 of the PDF reference
// Expose input/output counts for validation by callers.
function setArity(fn, numInputs, numOutputs) {
fn.numInputs = numInputs;
fn.numOutputs = numOutputs;
return fn;
}
// ISO 32000-1:2008, 7.10.2.
function interpolate(x, xmin, xmax, ymin, ymax) {
// A degenerate `Domain` entry, e.g. `[0 0]`, occurs in real-world PDFs.
return xmin === xmax
@@ -109,6 +116,10 @@ function interpolate(x, xmin, xmax, ymin, ymax) {
}
class PDFFunction {
// Limit multilinear interpolation's 2^m vertex growth for m inputs.
// Above this limit, simplex interpolation uses at most m + 1 vertices.
static MAX_MULTILINEAR_INPUTS = 8;
static getSampleArray(size, outputSize, bps, stream) {
let length = outputSize;
for (const s of size) {
@@ -160,11 +171,23 @@ class PDFFunction {
for (const fn of fnObj) {
fnArray.push(this.parse(factory, xref.fetchIfRef(fn)));
}
return function (src, srcOffset, dest, destOffset) {
for (let i = 0, ii = fnArray.length; i < ii; i++) {
fnArray[i](src, srcOffset, dest, destOffset + i);
}
};
if (fnArray.length === 1) {
return fnArray[0];
}
// Combine single-output functions with matching input counts.
const numInputs = fnArray[0]?.numInputs ?? 0;
if (fnArray.some(fn => fn.numInputs !== numInputs || fn.numOutputs !== 1)) {
throw new FormatError("Invalid array of functions.");
}
return setArity(
function (src, srcOffset, dest, destOffset) {
for (let i = 0, ii = fnArray.length; i < ii; i++) {
fnArray[i](src, srcOffset, dest, destOffset + i);
}
},
numInputs,
fnArray.length
);
}
static constructSampled(factory, fn, dict) {
@@ -198,22 +221,26 @@ class PDFFunction {
const decode = toNumberArray(dict.getArray("Decode")) || range;
const samples = this.getSampleArray(size, outputSize, bps, fn);
// const mask = 2 ** bps - 1;
return function constructSampledFn(src, srcOffset, dest, destOffset) {
// See chapter 3, page 110 of the PDF reference.
const useSimplex = inputSize > PDFFunction.MAX_MULTILINEAR_INPUTS;
// Sample strides and weights for axes needing interpolation.
const steps = new Float64Array(inputSize);
const weights0 = new Float64Array(inputSize);
const weights1 = new Float64Array(inputSize);
// Active axis indices, sorted by decreasing fractional part.
const sorted = useSimplex ? new Uint32Array(inputSize) : null;
// Sample offsets and weights, reused across calls.
const maxVertices = useSimplex ? inputSize + 1 : 1 << inputSize;
const vertices = new Uint32Array(maxVertices);
const vertexWeights = new Float64Array(maxVertices);
// Building the cube vertices: its part and sample index
// http://rjwagner49.com/Mathematics/Interpolation.pdf
const cubeVertices = 1 << inputSize;
const cubeN = new Float64Array(cubeVertices).fill(1);
const cubeVertex = new Uint32Array(cubeVertices);
let i, j;
let k = outputSize,
pos = 1;
// Map x_i to y_j for 0 <= i < m using the sampled function.
for (i = 0; i < inputSize; ++i) {
function constructSampledFn(src, srcOffset, dest, destOffset) {
// ISO 32000-1:2008, 7.10.2.
let base = 0,
k = outputSize,
numActive = 0;
// Map inputs to sample coordinates.
for (let i = 0; i < inputSize; ++i) {
// x_i' = min(max(x_i, Domain_2i), Domain_2i+1)
const domain_2i = domain[2 * i];
const domain_2i_1 = domain[2 * i + 1];
@@ -233,44 +260,78 @@ class PDFFunction {
const size_i = size[i];
e = MathClamp(e, 0, size_i - 1);
// Adjusting the cube: N and vertex sample index
const e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1;
const n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0);
const n1 = e - e0; // (e - e0) / (e1 - e0);
const offset0 = e0 * k;
const offset1 = offset0 + k; // e1 * k
for (j = 0; j < cubeVertices; j++) {
if (j & pos) {
cubeN[j] *= n1;
cubeVertex[j] += offset1;
} else {
cubeN[j] *= n0;
cubeVertex[j] += offset0;
}
const e0 = Math.floor(e); // e1 = e0 + 1
base += e0 * k;
// Integer sample coordinates need no interpolation along this axis.
if (e !== e0) {
steps[numActive] = k;
weights0[numActive] = e0 + 1 - e; // (e1 - e) / (e1 - e0)
weights1[numActive] = e - e0; // (e - e0) / (e1 - e0)
numActive++;
}
k *= size_i;
pos <<= 1;
}
for (j = 0; j < outputSize; ++j) {
// Sum all cube vertices' samples portions
vertices[0] = base;
vertexWeights[0] = 1;
let numVertices = 1;
if (!useSimplex) {
// Build the 2^numActive vertices and their product weights.
// https://rjwagner49.com/Mathematics/Interpolation.pdf
for (let i = 0; i < numActive; i++) {
const step = steps[i],
w0 = weights0[i],
w1 = weights1[i];
for (let j = 0; j < numVertices; j++) {
vertices[numVertices + j] = vertices[j] + step;
vertexWeights[numVertices + j] = vertexWeights[j] * w1;
vertexWeights[j] *= w0;
}
numVertices <<= 1;
}
} else if (numActive > 0) {
// Sort axes by decreasing fraction to find the containing simplex.
// Results can differ from multilinear interpolation.
for (let i = 0; i < numActive; i++) {
const w1 = weights1[i];
let j = i;
for (; j > 0 && weights1[sorted[j - 1]] < w1; j--) {
sorted[j] = sorted[j - 1];
}
sorted[j] = i;
}
// Use consecutive differences in [1, ...fractions, 0] as weights.
vertexWeights[0] = weights0[sorted[0]];
for (let i = 0; i < numActive; i++) {
const a = sorted[i];
vertices[i + 1] = vertices[i] + steps[a];
vertexWeights[i + 1] =
i + 1 < numActive
? weights1[a] - weights1[sorted[i + 1]]
: weights1[a];
}
numVertices = numActive + 1;
}
for (let j = 0; j < outputSize; ++j) {
// Weighted sum of the samples for this output.
let rj = 0;
for (i = 0; i < cubeVertices; i++) {
rj += samples[cubeVertex[i] + j] * cubeN[i];
for (let i = 0; i < numVertices; i++) {
rj += samples[vertices[i] + j] * vertexWeights[i];
}
// r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1,
// Decode_2j, Decode_2j+1)
// getSampleArray already scales samples by 1 / (2^bps - 1).
rj = interpolate(rj, 0, 1, decode[2 * j], decode[2 * j + 1]);
// y_j = min(max(r_j, range_2j), range_2j+1)
dest[destOffset + j] = MathClamp(rj, range[2 * j], range[2 * j + 1]);
}
};
}
return setArity(constructSampledFn, inputSize, outputSize);
}
static constructInterpolated(factory, dict) {
const domain = toNumberArray(dict.getArray("Domain"));
const c0 = toNumberArray(dict.getArray("C0")) || [0];
const c1 = toNumberArray(dict.getArray("C1")) || [1];
const n = dict.get("N");
@@ -281,13 +342,18 @@ class PDFFunction {
}
const length = diff.length;
return function constructInterpolatedFn(src, srcOffset, dest, destOffset) {
function constructInterpolatedFn(src, srcOffset, dest, destOffset) {
const x = n === 1 ? src[srcOffset] : src[srcOffset] ** n;
for (let j = 0; j < length; ++j) {
dest[destOffset + j] = c0[j] + x * diff[j];
}
};
}
return setArity(
constructInterpolatedFn,
domain ? domain.length / 2 : 1,
length
);
}
static constructStitched(factory, dict) {
@@ -307,12 +373,20 @@ class PDFFunction {
for (const fn of dict.get("Functions")) {
fns.push(this.parse(factory, xref.fetchIfRef(fn)));
}
// Each sub-function takes one input and produces all outputs (Table 41).
const numOutputs = fns[0]?.numOutputs ?? 0;
if (
fns.length === 0 ||
fns.some(fn => fn.numInputs !== 1 || fn.numOutputs !== numOutputs)
) {
throw new FormatError("Incompatible sub-functions for stitched function");
}
const bounds = toNumberArray(dict.getArray("Bounds"));
const encode = toNumberArray(dict.getArray("Encode"));
const tmpBuf = new Float32Array(1);
return function constructStitchedFn(src, srcOffset, dest, destOffset) {
function constructStitchedFn(src, srcOffset, dest, destOffset) {
// Clamp to domain.
const v = MathClamp(src[srcOffset], domain[0], domain[1]);
// calculate which bound the value is in
@@ -337,7 +411,8 @@ class PDFFunction {
);
// call the appropriate function
fns[i](tmpBuf, 0, dest, destOffset);
};
}
return setArity(constructStitchedFn, inputSize, numOutputs);
}
static constructPostScript(factory, fn, dict) {
@@ -353,19 +428,25 @@ class PDFFunction {
}
const psCode = fn.getString();
const numInputs = domain.length / 2,
numOutputs = range.length / 2;
try {
if (factory.useWasm) {
const wasmFn = buildPostScriptWasmFunction(psCode, domain, range);
if (wasmFn) {
return wasmFn; // (src, srcOffset, dest, destOffset) → void
return setArity(wasmFn, numInputs, numOutputs);
}
}
} catch {}
warn("Failed to compile PostScript function to wasm, falling back to JS");
return buildPostScriptJsFunction(psCode, domain, range);
return setArity(
buildPostScriptJsFunction(psCode, domain, range),
numInputs,
numOutputs
);
}
}
+21 -12
View File
@@ -209,7 +209,7 @@ class ImageResizer {
if (width * height * 4 > MAX_INT_32) {
// The resulting RGBA image is too large.
// We just rescale the data.
const result = this.#rescaleImageData();
const result = this._rescaleImageData();
if (result) {
return result;
}
@@ -306,22 +306,22 @@ class ImageResizer {
return imgData;
}
#rescaleImageData() {
_rescaleImageData(maxSize = MAX_INT_32) {
const { _imgData: imgData } = this;
const { data, width, height, kind } = imgData;
const rgbaSize = width * height * 4;
// K is such as width * height * 4 / 2 ** K <= 2 ** 31 - 1
const K = Math.ceil(Math.log2(rgbaSize / MAX_INT_32));
// Choose K so rgbaSize / 2 ** K <= maxSize.
const K = Math.ceil(Math.log2(rgbaSize / maxSize));
const newWidth = width >> K;
const newHeight = height >> K;
let rgbaData;
let maxHeight = height;
// We try to allocate the buffer with the maximum size but it can fail.
// Try allocating the full RGBA buffer.
try {
rgbaData = new Uint8Array(rgbaSize);
} catch {
// n is such as 2 ** n - 1 > width * height * 4
// Try smaller buffers until an allocation succeeds.
let n = Math.floor(Math.log2(rgbaSize + 1));
while (true) {
@@ -334,6 +334,14 @@ class ImageResizer {
}
maxHeight = Math.floor((2 ** n - 1) / (width * 4));
if (maxHeight === 0) {
// No RGBA row fits in the buffer.
return null;
}
if (maxHeight >= 4) {
// Keep RGB chunk offsets divisible by 4.
maxHeight -= maxHeight % 4;
}
const newSize = width * maxHeight * 4;
if (newSize < rgbaData.length) {
rgbaData = new Uint8Array(newSize);
@@ -345,10 +353,11 @@ class ImageResizer {
let srcPos = 0;
let newIndex = 0;
const step = Math.ceil(height / maxHeight);
const remainder = height % maxHeight === 0 ? height : height % maxHeight;
for (let k = 0; k < step; k++) {
const h = k < step - 1 ? maxHeight : remainder;
// Sample every 2 ** K rows, independent of chunk boundaries.
const lastRow = newHeight << K;
let row = 0;
for (let y = 0; y < height; y += maxHeight) {
const h = Math.min(maxHeight, height - y);
({ srcPos } = convertToRGBA({
kind,
src: data,
@@ -359,8 +368,8 @@ class ImageResizer {
srcPos,
}));
for (let i = 0, ii = h >> K; i < ii; i++) {
const buf = src32.subarray((i << K) * width);
for (const end = Math.min(y + h, lastRow); row < end; row += 1 << K) {
const buf = src32.subarray((row - y) * width);
for (let j = 0; j < newWidth; j++) {
dest32[newIndex++] = buf[j << K];
}
+1 -10
View File
@@ -162,18 +162,9 @@ function compileFontInfo(font) {
boolBit = 0;
}
}
assert(
offset === FONT_INFO.OFFSET_NUMBERS,
"compileFontInfo: Boolean properties offset mismatch"
);
for (const prop of FONT_INFO.numbers) {
view.setFloat64(offset, font[prop]);
offset += 8;
}
assert(
offset === FONT_INFO.OFFSET_BBOX,
"compileFontInfo: Number properties offset mismatch"
"compileFontInfo: Boolean properties offset mismatch"
);
writeArray(
+21 -3
View File
@@ -57,6 +57,24 @@ function getColorConversionBatchSize(count, numComps) {
);
}
// Input count follows the shading type; output count matches the color space
// (ISO 32000-1:2008, 8.7.4.5).
function parseShadingFunction(
fnObj,
pdfFunctionFactory,
numInputs,
numOutputs
) {
const fn = pdfFunctionFactory.create(fnObj, /* parseArray = */ true);
if (fn.numInputs !== numInputs || fn.numOutputs !== numOutputs) {
throw new FormatError(
`Invalid shading function: expected ${numInputs}-in ${numOutputs}-out, ` +
`got ${fn.numInputs}-in ${fn.numOutputs}-out.`
);
}
return fn;
}
class Pattern {
// eslint-disable-next-line no-unused-private-class-members
static #hasGPU = false;
@@ -187,7 +205,7 @@ class RadialAxialShading extends BaseShading {
: [false, false];
const fnObj = dict.getRaw("Function");
const fn = pdfFunctionFactory.create(fnObj, /* parseArray = */ true);
const fn = parseShadingFunction(fnObj, pdfFunctionFactory, 1, cs.numComps);
// Use lcm(1,2,3,4,5,6,7,8,10) = 840 (including 9 increases this to 2520)
// to catch evenly spaced stops. oeis.org/A003418
@@ -477,7 +495,7 @@ class FunctionBasedShading extends BaseShading {
if (!fnObj) {
throw new FormatError("FunctionBasedShading: missing /Function");
}
const fn = pdfFunctionFactory.create(fnObj, /* parseArray = */ true);
const fn = parseShadingFunction(fnObj, pdfFunctionFactory, 2, cs.numComps);
// Domain [x0, x1, y0, y1]; defaults to [0, 1, 0, 1].
const [x0, x1, y0, y1] = lookupRect(dict.getArray("Domain"), [0, 1, 0, 1]);
@@ -756,7 +774,7 @@ class MeshShading extends BaseShading {
const fnObj = dict.getRaw("Function");
const fn = fnObj
? pdfFunctionFactory.create(fnObj, /* parseArray = */ true)
? parseShadingFunction(fnObj, pdfFunctionFactory, 1, cs.numComps)
: null;
this.coords = [];
-2
View File
@@ -1001,9 +1001,7 @@ class PSStackToTree {
// Boolean operands: true xor true = false xor false = false.
// Integer operands: n xor n = 0.
return new PsConstNode(
/* eslint-disable unicorn/prefer-logical-operator-over-ternary */
first.valueType === PS_VALUE_TYPE.boolean ? false : 0
/* eslint-enable unicorn/prefer-logical-operator-over-ternary */
);
// TOKEN.mod, TOKEN.div, TOKEN.idiv are NOT simplified here:
// x op x is undefined when x = 0, so we cannot fold without knowing
+4 -5
View File
@@ -92,11 +92,10 @@ function stringToPDFString(str, keepEscapeSequence = false) {
));
const buffer = stringToBytes(str);
const decoded = decoder.decode(buffer);
if (keepEscapeSequence || !decoded.includes("\x1b")) {
return decoded;
}
// eslint-disable-next-line no-control-regex
return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, "");
return keepEscapeSequence || !decoded.includes("\x1b")
? decoded
: // eslint-disable-next-line no-control-regex
decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, "");
} catch (ex) {
warn(`stringToPDFString: "${ex}".`);
}
+2 -2
View File
@@ -554,7 +554,7 @@ class OutputScale {
// See https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types
// to know which types are supported by the browser.
const SupportedImageMimeTypes = [
const SupportedImageMimeTypes = new Set([
"image/apng",
"image/avif",
"image/bmp",
@@ -564,7 +564,7 @@ const SupportedImageMimeTypes = [
"image/svg+xml",
"image/webp",
"image/x-icon",
];
]);
class ColorScheme {
static get isDarkMode() {
+2 -2
View File
@@ -67,7 +67,7 @@ class StampEditor extends AnnotationEditor {
/** @inheritdoc */
static isHandlingMimeForPasting(mime) {
return SupportedImageMimeTypes.includes(mime);
return SupportedImageMimeTypes.has(mime);
}
/** @inheritdoc */
@@ -238,7 +238,7 @@ class StampEditor extends AnnotationEditor {
document.body.append(input);
}
input.type = "file";
input.accept = SupportedImageMimeTypes.join(",");
input.accept = SupportedImageMimeTypes.keys().join(",");
const signal = this._uiManager._signal;
this.#bitmapPromise = new Promise(resolve => {
input.addEventListener(
-8
View File
@@ -544,14 +544,6 @@ class FontFaceObject {
return this.#fontData.vertical;
}
get ascent() {
return this.#fontData.ascent;
}
get descent() {
return this.#fontData.descent;
}
get bbox() {
return this.#fontData.bbox;
}
-13
View File
@@ -163,19 +163,6 @@ class FontInfo {
return shadow(this, "vertical", this.#readBoolean(9));
}
#readNumber(index) {
assert(index < FONT_INFO.numbers.length, "Invalid number index");
return this.#view.getFloat64(FONT_INFO.OFFSET_NUMBERS + index * 8);
}
get ascent() {
return shadow(this, "ascent", this.#readNumber(0));
}
get descent() {
return shadow(this, "descent", this.#readNumber(1));
}
#readArray(offset, arrLen, lookupName, increment) {
const len = this.#view.getUint8(offset++);
if (len === 0) {
+2 -3
View File
@@ -499,6 +499,7 @@ class TextLayer {
this.#canvasContexts.set(lang, ctx);
// Also, initialize state for the `#ensureCtxFont` method.
// Resizing the canvas resets ctx.font but leaves this cache stale.
this.#canvasCtxFonts.set(ctx, { size: 0, family: "" });
}
return ctx;
@@ -550,14 +551,12 @@ class TextLayer {
}
const ctx = this.#getCtx(lang);
ctx.canvas.width = ctx.canvas.height = DEFAULT_FONT_SIZE;
this.#ensureCtxFont(ctx, DEFAULT_FONT_SIZE, fontFamily);
const metrics = ctx.measureText("");
const ascent = metrics.fontBoundingBoxAscent;
const descent = Math.abs(metrics.fontBoundingBoxDescent);
ctx.canvas.width = ctx.canvas.height = 0;
let ratio = 0.8; // DEFAULT_FONT_ASCENT
if (ascent) {
@@ -584,4 +583,4 @@ class TextLayer {
}
}
export { TextLayer };
export { DEFAULT_FONT_SIZE, TextLayer };
-1
View File
@@ -131,7 +131,6 @@ export class SandboxSupportBase {
this.win.alert(cMsg);
},
confirm: cMsg =>
// eslint-disable-next-line unicorn/prefer-logical-operator-over-ternary
typeof cMsg !== "string" ? false : this.win.confirm(cMsg),
prompt: (cQuestion, cDefault) =>
typeof cQuestion !== "string" || typeof cDefault !== "string"
-1
View File
@@ -671,7 +671,6 @@ class CheckboxField extends RadioButtonField {
}
isBoxChecked(nWidget) {
// eslint-disable-next-line unicorn/prefer-logical-operator-over-ternary
return this._value === "Off" ? false : super.isBoxChecked(nWidget);
}
+5 -2
View File
@@ -84,8 +84,11 @@ function convertRGBToRGBA({
}) {
let i = 0;
const len = width * height * 3;
const len32 = len >> 2;
const src32 = new Uint32Array(src.buffer, srcPos, len32);
const byteOffset = src.byteOffset + srcPos;
// Use 32-bit reads for aligned sources; otherwise convert pixel by pixel.
const len32 = byteOffset % 4 === 0 ? Math.floor(len / 4) : 0;
const src32 =
len32 > 0 ? new Uint32Array(src.buffer, byteOffset, len32) : null;
const alphaMask = FeatureTest.isLittleEndian ? 0xff000000 : 0xff;
if (FeatureTest.isLittleEndian) {
+1 -5
View File
@@ -37,13 +37,9 @@ class FONT_INFO {
"vertical",
];
static numbers = ["ascent", "descent"];
static strings = ["fallbackName", "loadedName"];
static OFFSET_NUMBERS = Math.ceil((this.bools.length * 2) / 8);
static OFFSET_BBOX = this.OFFSET_NUMBERS + this.numbers.length * 8;
static OFFSET_BBOX = Math.ceil((this.bools.length * 2) / 8);
static OFFSET_FONT_MATRIX = this.OFFSET_BBOX + 1 + 2 * 4;
-1
View File
@@ -951,7 +951,6 @@ class Driver {
useWorkerFetch: task.useWorkerFetch,
enableXfa: task.enableXfa,
isOffscreenCanvasSupported:
// eslint-disable-next-line unicorn/prefer-logical-operator-over-ternary
task.isOffscreenCanvasSupported === false ? false : undefined,
disableFontFace: task.disableFontFace === true,
...(this.#pdfWorker ? { worker: this.#pdfWorker } : {}),
-2
View File
@@ -328,10 +328,8 @@ async function waitForEvent({
if (timeoutId) {
clearTimeout(timeoutId);
}
/* eslint-disable unicorn/prefer-logical-operator-over-ternary */
// eslint-disable-next-line no-eval
resolve(validate ? eval(`(${validate})`)(e) : true);
/* eslint-enable unicorn/prefer-logical-operator-over-ternary */
};
element.addEventListener(name, callback, { once: true });
}),
-2
View File
@@ -1122,7 +1122,6 @@ describe("Text layer", () => {
`.page[data-page-number = "1"] .endOfContent`,
undefined,
{
/* eslint-disable unicorn/prefer-logical-operator-over-ternary */
prePageSetup: page =>
page.evaluateOnNewDocument(() => {
const { supports } = CSS;
@@ -1131,7 +1130,6 @@ describe("Text layer", () => {
? false
: supports.call(CSS, property, value);
}),
/* eslint-enable unicorn/prefer-logical-operator-over-ternary */
},
(_page, browserName) => ({
imagesRightClickMinSize: browserName === "firefox" ? 16 : -1,
+1
View File
@@ -962,3 +962,4 @@
!tiling-pattern-tiny-steps.pdf
!tiling-pattern-tiny-steps-large.pdf
!tiling-pattern-fold-order.pdf
!axial_shading_many_inputs.pdf
Binary file not shown.
+7
View File
@@ -14561,5 +14561,12 @@
"md5": "3b88d507cdaa55cf3379efea51880288",
"rounds": 1,
"type": "eq"
},
{
"id": "axial_shading_many_inputs",
"file": "pdfs/axial_shading_many_inputs.pdf",
"md5": "8df066525f75c3c5769df3e55c1552fd",
"rounds": 1,
"type": "eq"
}
]
+1
View File
@@ -29,6 +29,7 @@
"font_loader_spec.js",
"font_substitutions_spec.js",
"fonts_spec.js",
"function_spec.js",
"image_resizer_spec.js",
"image_utils_spec.js",
"jpeg_stream_spec.js",
+221
View File
@@ -0,0 +1,221 @@
/* Copyright 2026 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Dict } from "../../src/core/primitives.js";
import { FormatError } from "../../src/shared/util.js";
import { PDFFunctionFactory } from "../../src/core/function.js";
import { Stream } from "../../src/core/stream.js";
import { XRefMock } from "./test_utils.js";
describe("function", function () {
let factory;
beforeAll(function () {
factory = new PDFFunctionFactory({ xref: new XRefMock() });
});
afterAll(function () {
factory = null;
});
function createSampledStream({ domain, range, size, samples }) {
const dict = new Dict();
dict.set("FunctionType", 0);
dict.set("Domain", domain);
dict.set("Range", range);
dict.set("Size", size);
dict.set("BitsPerSample", 8);
const bytes = new Uint8Array(samples);
return new Stream(bytes, 0, bytes.length, dict);
}
function createSampledFunction(params) {
return factory.create(createSampledStream(params));
}
function createExponentialFunction(c0, c1) {
const dict = new Dict();
dict.set("FunctionType", 2);
dict.set("Domain", [0, 1]);
dict.set("C0", c0);
dict.set("C1", c1);
dict.set("N", 1);
return dict;
}
function createStitchedFunction(fns) {
const dict = new Dict();
dict.set("FunctionType", 3);
dict.set("Domain", [0, 1]);
dict.set("Functions", fns);
dict.set("Bounds", [0.5]);
dict.set("Encode", [0, 1, 0, 1]);
return factory.create(dict);
}
function evaluate(fn, ...src) {
const dest = new Float64Array(fn.numOutputs);
fn(new Float64Array(src), 0, dest, 0);
return Array.from(dest);
}
// Samples are 255 where the first two inputs are both 1, and 0 elsewhere.
function createAndFunction(numInputs) {
const count = 1 << numInputs;
const samples = new Array(count).fill(0);
for (let i = 3; i < count; i += 4) {
samples[i] = 255;
}
return createSampledFunction({
domain: new Array(numInputs).fill([0, 1]).flat(),
range: [0, 1],
size: new Array(numInputs).fill(2),
samples,
});
}
describe("Sampled function", function () {
it("must interpolate multilinearly", function () {
const fn = createAndFunction(2);
expect(fn.numInputs).toEqual(2);
expect(fn.numOutputs).toEqual(1);
expect(evaluate(fn, 0.5, 0.25)).toEqual([0.125]);
expect(evaluate(fn, 1, 0.5)).toEqual([0.5]);
expect(evaluate(fn, 1, 1)).toEqual([1]);
});
it("must use simplex interpolation for many inputs", function () {
const fn = createAndFunction(9);
expect(fn.numInputs).toEqual(9);
expect(evaluate(fn, 0.5, 0.25, 0, 0, 0, 0, 0, 0, 0)).toEqual([0.25]);
expect(evaluate(fn, 1, 0.5, 0, 0, 0, 0, 0, 0, 0)).toEqual([0.5]);
expect(evaluate(fn, 1, 1, 0, 1, 0, 1, 0, 1, 0)).toEqual([1]);
});
it("must handle inputs with a single sample", function () {
const fn = createSampledFunction({
domain: [0, 1, 0, 1, 0, 1],
range: [0, 1],
size: [2, 1, 2],
samples: [0, 51, 102, 255],
});
expect(evaluate(fn, 0.5, 0.3, 0)).toEqual([0.1]);
expect(evaluate(fn, 1, 0.3, 1)).toEqual([1]);
});
it("must evaluate functions with many single-sample inputs", function () {
const numInputs = 18;
const fn = createSampledFunction({
domain: new Array(numInputs).fill([0, 1]).flat(),
range: [0, 1],
size: new Array(numInputs).fill(1),
samples: [51],
});
expect(evaluate(fn, ...new Array(numInputs).fill(0.5))).toEqual([0.2]);
});
});
describe("Array of functions", function () {
it("must combine 1-out functions", function () {
const fn = factory.create(
[
createExponentialFunction([0], [1]),
createExponentialFunction([1], [0]),
],
/* parseArray = */ true
);
expect(fn.numInputs).toEqual(1);
expect(fn.numOutputs).toEqual(2);
expect(evaluate(fn, 0.25)).toEqual([0.25, 0.75]);
});
it("must accept a single n-out function", function () {
const fn = factory.create(
[createExponentialFunction([0, 0, 0], [1, 1, 1])],
/* parseArray = */ true
);
expect(fn.numInputs).toEqual(1);
expect(fn.numOutputs).toEqual(3);
});
it("must reject several n-out functions", function () {
expect(() =>
factory.create(
[
createExponentialFunction([0, 0], [1, 1]),
createExponentialFunction([0, 0], [1, 1]),
],
/* parseArray = */ true
)
).toThrowError(FormatError, "Invalid array of functions.");
});
});
describe("Stitching function", function () {
it("must have the outputs of its sub-functions", function () {
const fn = createStitchedFunction([
createExponentialFunction([0, 0, 0], [1, 1, 1]),
createExponentialFunction([1, 0, 0], [0, 0, 1]),
]);
expect(fn.numInputs).toEqual(1);
expect(fn.numOutputs).toEqual(3);
expect(evaluate(fn, 0.25)).toEqual([0.5, 0.5, 0.5]);
expect(evaluate(fn, 0.75)).toEqual([0.5, 0, 0.5]);
});
it("must reject sub-functions with several inputs", function () {
expect(() =>
createStitchedFunction([
createExponentialFunction([0, 0, 0], [1, 1, 1]),
createSampledStream({
domain: [0, 1, 0, 1],
range: [0, 1, 0, 1, 0, 1],
size: [2, 2],
samples: new Array(12).fill(0),
}),
])
).toThrowError(
FormatError,
"Incompatible sub-functions for stitched function"
);
});
it("must reject sub-functions with different outputs", function () {
expect(() =>
createStitchedFunction([
createExponentialFunction([0, 0, 0], [1, 1, 1]),
createExponentialFunction([0], [1]),
])
).toThrowError(
FormatError,
"Incompatible sub-functions for stitched function"
);
});
it("must reject an empty array of sub-functions", function () {
expect(() => createStitchedFunction([])).toThrowError(
FormatError,
"Incompatible sub-functions for stitched function"
);
});
});
});
+101
View File
@@ -13,9 +13,110 @@
* limitations under the License.
*/
import { ImageKind } from "../../src/shared/util.js";
import { ImageResizer } from "../../src/core/image_resizer.js";
describe("image_resizer", function () {
describe("rescaleImageData", function () {
const Uint8ArrayConstructor = Uint8Array;
function getLimitedUint8Array(maxSize) {
return function (size) {
if (size > maxSize) {
throw new RangeError("Simulated allocation failure");
}
if (size === 0) {
// Fail instead of hanging if a chunk height regresses to zero.
throw new Error("Cannot allocate an empty chunk");
}
return new Uint8ArrayConstructor(size);
};
}
beforeEach(function () {
// Keep the rescaled pixels in imgData.data.
spyOn(ImageResizer, "needsToBeResized").and.returnValue(true);
});
it("samples the rows on the image grid across chunks", function () {
const allocSpy = spyOn(globalThis, "Uint8Array");
for (const kindName of ["RGB_24BPP", "GRAYSCALE_1BPP"]) {
const kind = ImageKind[kindName];
const isRGB = kind === ImageKind.RGB_24BPP;
for (const [width, height, maxAllocation, reducePower, chunkHeight] of [
[9, 17, Infinity, 1, 17],
[9, 17, 63, 1, 1],
[13, 17, 127, 1, 2],
[9, 18, 127, 1, 3],
[9, 35, 511, 3, 12],
]) {
const context = `${kindName} ${width}x${height} image, ${chunkHeight}-row chunks`;
const rowSize = isRGB ? width * 3 : Math.ceil(width / 8);
// `Uint8Array` is spied upon, hence use the original constructor.
const data = Uint8ArrayConstructor.from(
{ length: rowSize * height },
(_, i) => (73 * i + (i >> 3) + 19) & 255
);
const imgData = { data, width, height, kind };
const factor = 2 ** reducePower;
const newWidth = Math.floor(width / factor);
const newHeight = Math.floor(height / factor);
const expected = new Uint8ArrayConstructor(newWidth * newHeight * 4);
for (let y = 0; y < newHeight; y++) {
for (let x = 0; x < newWidth; x++) {
const srcX = x * factor;
const srcY = y * factor;
const destPos = (y * newWidth + x) * 4;
if (isRGB) {
const srcPos = srcY * rowSize + srcX * 3;
expected.set(data.subarray(srcPos, srcPos + 3), destPos);
} else {
const byte = data[srcY * rowSize + (srcX >> 3)];
const value = ((byte >> (7 - (srcX & 7))) & 1) * 255;
expected.fill(value, destPos, destPos + 3);
}
expected[destPos + 3] = 255;
}
}
allocSpy.and.callFake(getLimitedUint8Array(maxAllocation));
const resizer = new ImageResizer(imgData, false);
expect(resizer._rescaleImageData((width * height * 4) / factor))
.withContext(context)
.toBeNull();
expect(imgData.width).withContext(context).toEqual(newWidth);
expect(imgData.height).withContext(context).toEqual(newHeight);
expect(imgData.kind)
.withContext(context)
.toEqual(ImageKind.RGBA_32BPP);
expect(imgData.data)
.withContext(context)
.toEqual(new Uint32Array(expected.buffer));
}
}
});
it("falls back without changing the image when no row fits", function () {
const data = new Uint8Array(9 * 17 * 3);
const imgData = {
data,
width: 9,
height: 17,
kind: ImageKind.RGB_24BPP,
};
const original = { ...imgData };
// One RGBA row needs 36 bytes; allocation is limited to 31.
spyOn(globalThis, "Uint8Array").and.callFake(getLimitedUint8Array(31));
const resizer = new ImageResizer(imgData, false);
expect(resizer._rescaleImageData((9 * 17 * 4) / 2)).toBeNull();
expect(imgData).toEqual(original);
expect(imgData.data).toBe(data);
expect(ImageResizer.needsToBeResized).not.toHaveBeenCalled();
});
});
describe("getReducePower", function () {
// Canvas limits depend on the runtime, so derive expectations from them.
+39
View File
@@ -346,5 +346,44 @@ describe("image_utils", function () {
expect(result.srcPos).toEqual(15);
expect(dest[0]).toEqual(RED);
});
it("handles a source with a byteOffset for RGB_24BPP", function () {
// Skip four bytes outside the source view.
const buffer = new Uint8Array([
1, 2, 3, 4, 255, 255, 255, 0, 0, 0, 255, 255, 255, 0, 0, 0, 255, 0, 0,
]).buffer;
const src = new Uint8Array(buffer, 4);
const dest = new Uint32Array(5);
const result = convertToRGBA({
src,
dest,
width: 5,
height: 1,
kind: ImageKind.RGB_24BPP,
});
expect(result.srcPos).toEqual(15);
expect(result.destPos).toEqual(5);
expect(dest).toEqual(new Uint32Array([WHITE, BLACK, WHITE, BLACK, RED]));
});
it("handles a source which isn't 4-byte aligned for RGB_24BPP", function () {
// byteOffset + srcPos = 1 + 2 = 3 is not 4-byte aligned.
const buffer = new Uint8Array([
1, 2, 3, 255, 255, 255, 0, 0, 0, 255, 255, 255, 0, 0, 0, 255, 0, 0,
]).buffer;
const src = new Uint8Array(buffer, 1);
const dest = new Uint32Array(5);
const result = convertToRGBA({
src,
dest,
srcPos: 2,
width: 5,
height: 1,
kind: ImageKind.RGB_24BPP,
});
expect(result.srcPos).toEqual(17);
expect(result.destPos).toEqual(5);
expect(dest).toEqual(new Uint32Array([WHITE, BLACK, WHITE, BLACK, RED]));
});
});
});
+1
View File
@@ -74,6 +74,7 @@ async function initializePDFJS(callback) {
"pdfjs-test/unit/font_loader_spec.js",
"pdfjs-test/unit/font_substitutions_spec.js",
"pdfjs-test/unit/fonts_spec.js",
"pdfjs-test/unit/function_spec.js",
"pdfjs-test/unit/image_resizer_spec.js",
"pdfjs-test/unit/image_utils_spec.js",
"pdfjs-test/unit/jpeg_stream_spec.js",
+1 -5
View File
@@ -64,8 +64,6 @@ describe("obj_bin_transform", function () {
missingFile: true,
remeasure: true,
vertical: true,
ascent: 1,
descent: -1,
bbox: [1, 1, 1, 1],
fontMatrix: [1, 1, 1, 1, 1, 1],
fallbackName: "string",
@@ -122,7 +120,7 @@ describe("obj_bin_transform", function () {
describe("FontInfo", function () {
it("must roundtrip correctly for FontInfo", function () {
let sizeEstimate = 57; // fixed offset until the strings
let sizeEstimate = 41; // fixed offset until the strings
const { encoder } = InfoUtils;
sizeEstimate += 4 + 4 * (4 + encoder.encode("string").length);
sizeEstimate += 4 + 4; // cssFontInfo and systemFontInfo
@@ -140,8 +138,6 @@ describe("obj_bin_transform", function () {
expect(deserialized.missingFile).toBeTrue();
expect(deserialized.remeasure).toBeTrue();
expect(deserialized.vertical).toBeTrue();
expect(deserialized.ascent).toEqual(1);
expect(deserialized.descent).toEqual(-1);
expect(deserialized.bbox).toEqual([1, 1, 1, 1]);
expect(deserialized.fontMatrix).toEqual([1, 1, 1, 1, 1, 1]);
expect(deserialized.fallbackName).toEqual("string");
+60 -1
View File
@@ -34,6 +34,8 @@ describe("pattern", function () {
dest[destOffset + 1] = src[srcOffset + 1];
dest[destOffset + 2] = 0;
},
numInputs = 2,
numOutputs = 3,
} = {}) {
const dict = new Dict();
dict.set("ShadingType", 1);
@@ -49,7 +51,7 @@ describe("pattern", function () {
dict.set("Background", background);
}
dict.set("Function", {
fn,
fn: Object.assign(fn, { numInputs, numOutputs }),
});
const pdfFunctionFactory = {
@@ -116,6 +118,7 @@ describe("pattern", function () {
dest[destOffset + 2] = 0.5;
dest.fill(0, destOffset + 3, destOffset + numComps);
},
numOutputs: numComps,
});
const [, , , colors] = shading.getIR();
@@ -147,11 +150,67 @@ describe("pattern", function () {
dest[destOffset] =
src[srcOffset] === 1 || src[srcOffset + 1] === 1 ? 1 : 0;
},
numOutputs: 1,
});
const [, , , colors] = shading.getIR();
expect(colors.length).toEqual(144);
expect(Array.from(colors)).toEqual(new Array(144).fill(0));
});
it("must ignore a function with the wrong dimensions", function () {
expect(createFunctionBasedShading({ numInputs: 1 }).getIR()).toEqual([
"Dummy",
]);
expect(createFunctionBasedShading({ numOutputs: 1 }).getIR()).toEqual([
"Dummy",
]);
});
});
describe("RadialAxialShading", function () {
function createAxialShading({ numInputs, numOutputs }) {
const dict = new Dict();
dict.set("ShadingType", 2);
dict.setIfName("ColorSpace", "DeviceRGB");
dict.set("Coords", [0, 0, 100, 0]);
dict.set("Extend", [true, true]);
dict.set("Function", {
fn: Object.assign(
(src, srcOffset, dest, destOffset) => {
dest.fill(src[srcOffset], destOffset, destOffset + numOutputs);
},
{ numInputs, numOutputs }
),
});
return Pattern.parseShading(
dict,
/* xref = */ { fetchIfRef: obj => obj },
/* res = */ null,
/* pdfFunctionFactory = */ { create: fnObj => fnObj.fn },
new GlobalColorSpaceCache(),
new LocalColorSpaceCache()
);
}
it("must accept a 1-in, n-out function", function () {
const ir = createAxialShading({ numInputs: 1, numOutputs: 3 }).getIR();
expect(ir[0]).toEqual("RadialAxial");
expect(ir[3]).toEqual([
[0, "#000000"],
[1, "#ffffff"],
]);
});
it("must ignore a function with the wrong dimensions", function () {
expect(
createAxialShading({ numInputs: 18, numOutputs: 3 }).getIR()
).toEqual(["Dummy"]);
expect(
createAxialShading({ numInputs: 1, numOutputs: 1 }).getIR()
).toEqual(["Dummy"]);
});
});
});
+53 -1
View File
@@ -13,10 +13,11 @@
* limitations under the License.
*/
import { DEFAULT_FONT_SIZE, TextLayer } from "../../src/display/text_layer.js";
import { buildGetDocumentParams } from "./test_utils.js";
import { getDocument } from "../../src/display/api.js";
import { isNodeJS } from "../../src/shared/util.js";
import { TextLayer } from "../../src/display/text_layer.js";
import { OutputScale } from "../../src/display/display_utils.js";
describe("textLayer", function () {
it("creates textLayer from ReadableStream", async function () {
@@ -251,4 +252,55 @@ describe("textLayer", function () {
await loadingTask.destroy();
});
it("measures the text with the correct font after computing the ascent", async function () {
if (isNodeJS) {
pending("document.createElement is not supported in Node.js.");
}
const loadingTask = getDocument(buildGetDocumentParams("basicapi.pdf"));
const pdfDocument = await loadingTask.promise;
const page = await pdfDocument.getPage(1);
// Proportional widths should produce the same horizontal scale.
const items = [10, 11].map(fontSize => ({
str: "Hello World",
dir: "ltr",
width: 5 * fontSize,
height: fontSize,
transform: [fontSize, 0, 0, fontSize, 50, 700],
fontName: "g_d0_f1",
hasEOL: false,
}));
const styles = {
g_d0_f1: {
ascent: 0.75,
descent: -0.25,
fontFamily: "sans-serif",
vertical: false,
},
};
// Force the ascent to be computed for this font.
TextLayer.cleanup();
// Use DPR 1 to avoid rounding the first measurement away from 30px.
spyOnProperty(OutputScale, "pixelRatio", "get").and.returnValue(1);
// Ensure that 30px is still the font size used to compute the ascent.
expect(DEFAULT_FONT_SIZE).toEqual(30);
const textLayer = new TextLayer({
textContentSource: { items, styles, lang: "en" },
container: document.createElement("div"),
// Measure the first run at the 30px size used to compute the ascent.
viewport: page.getViewport({ scale: 3 }),
});
await textLayer.render();
const [scaleX1, scaleX2] = textLayer.textDivs.map(({ style }) =>
parseFloat(style.getPropertyValue("--scale-x"))
);
expect(scaleX1 / scaleX2).toBeCloseTo(1, 1);
await loadingTask.destroy();
});
});
-1
View File
@@ -880,7 +880,6 @@ const defaultOptions = new Map([
// On Android, there is almost no chance to have the font we want so we
// don't use the system fonts in this case (bug 1882613).
/** @type {boolean|undefined} */
// eslint-disable-next-line unicorn/prefer-logical-operator-over-ternary
value: (
typeof PDFJSDev === "undefined"
? window.isGECKOVIEW
+4 -4
View File
@@ -228,7 +228,7 @@ class SignatureManager {
);
this.#initTabButtons(typeButton, drawButton, imageButton, panels);
imagePicker.accept = SupportedImageMimeTypes.join(",");
imagePicker.accept = SupportedImageMimeTypes.keys().join(",");
eventBus.on(
"storedsignatureschanged",
@@ -569,7 +569,7 @@ class SignatureManager {
"change",
async () => {
const file = this.#imagePicker.files?.[0];
if (!file || !SupportedImageMimeTypes.includes(file.type)) {
if (!file || !SupportedImageMimeTypes.has(file.type)) {
this.#showError("Upload");
this.#dialog.classList.toggle("waiting", false);
return;
@@ -590,7 +590,7 @@ class SignatureManager {
e => {
const { dataTransfer } = e;
for (const { type } of dataTransfer.items) {
if (!SupportedImageMimeTypes.includes(type)) {
if (!SupportedImageMimeTypes.has(type)) {
continue;
}
dataTransfer.dropEffect =
@@ -612,7 +612,7 @@ class SignatureManager {
return;
}
for (const file of files) {
if (SupportedImageMimeTypes.includes(file.type)) {
if (SupportedImageMimeTypes.has(file.type)) {
this.#extractSignature(file);
break;
}