In a knockout (KO) group each painting operator ("element") composites against
the group's initial backdrop instead of accumulating onto prior elements
of the same group. The backend renders each element to a per-group pooled
temp canvas (keyed off `#groupStackMeta`), builds a binary alpha mask via
a new `feFuncA` filter (`addKnockoutFilter`), `destination-out`s the
group canvas through that mask, restores the initial backdrop into the
cleared footprint for non-isolated groups (cropped to the same mask so
sparse groups don't bleed the whole rectangle), and finally paints the
element on top with the parent's blend mode. Path / clip / transform ops
are mirrored back to the group canvas via `mirrorContextOperations` so
graphics state stays in sync between elements; only the raster pixels
land on the temp canvas.
The temp canvas is forced to source-over for the element raster (`multiply`
on a transparent backdrop would zero the color) and the original GCO is
restored before `copyCtxState` writes back, so the parent's blend mode
survives for the final composite.
Also handled:
- Nested KO groups (the level is incremented for KO, reset to 0 for
non-KO subgroups so an ancestor KO doesn't leak in).
- Non-isolated non-KO subgroups inside a KO parent (`hasInnerBackdrop`
path: blend the elements against the subgroup's running backdrop for
color, mask with the elements-only canvas).
- Soft masks installed inside a KO element (`applySMaskInPlace` in
`compose`, which runs the SMask destination-in directly on the temp
canvas; the existing blit-to-suspended step is gated by `if (!ctx)`).
- Type-3 text, shading fills, image-mask groups, inline images and the
solid-color mask path: each is wrapped in `#begin/#endKnockoutElement`.
- `endDrawing` cleanup so cancelled rendering doesn't leak pooled
canvases or stale knockout state.
Given that this function is only ever used in `src/core/` code, let's avoid a little bit of dead code in the *built* `pdf.mjs` file.
Also, place the `AnnotationPrefix` and `AnnotationEditorPrefix` constants together in `src/shared/util.js` since that should aid readability.
Note that `Ref`s and `Name`s are cached globally[1], since that helps reduce object creation (a lot) during parsing.
That cache will be cleared after a period of inactivity in the viewer[2], which is why those primitives cannot *safely* be compared with just `===`/`!==` and also (partially) why abstractions such as `RefSet`/`RefSetCache` are necessary.
Currently `deepCompare` doesn't handle `Ref`s and `Name`s correctly, which may lead to future *intermittent* bugs in any code using the `deepCompare` helper function.
---
[1] This applies to `Cmd` as well, however that doesn't matter in the context of this patch.
[2] Currently, and for more than a decade, set to 30 seconds.
Previously this was only supported in Firefox, however when merging PDFs the `PDFViewerApplication.onSaveAndLoad` method will provide a `filename` unconditionally.
This is a left-over from very old code, which pre-dates the introduction of the `PDFDocumentLoadingTask` and it's nothing more than an alias for its `destroy` method.
Given that `PDFDocumentProxy` already provides a way to access the underlying `PDFDocumentLoadingTask` instance, it shouldn't be necessary to have an alias for one of its methods.
*Please note:* For any existing code relying on the removed method, updating it should be as simple as replacing `pdfDocument.destroy()` with `pdfDocument.loadingTask.destroy()`.
---
[1] If the `PDFDocumentProxy` class was added today, there's no chance that it'd include a `destroy` method.
This is a left-over from very old code[1], before there were a lot of `getDocument` options and when most of the library configuration was done via the (since removed) `PDFJS` global.
Given all the functionality added through the years, which require configuration[2], in practice it's now unlikely that calling `getDocument` without additional options will work except for the most trivial PDFs.
---
[1] If the `getDocument` function was added today, there's no chance that it'd support anything other than a parameter object.
[2] Note things such as CMaps, standard fonts, wasm-based image decoders, and ICC-based colour spaces.
The delay between chunks when testing streaming is necessary to avoid the entire PDF document arriving all at once, since that would render those unit-tests somewhat pointless.
However, the delay is unnecessarily large which causes these unit-tests to be slower than necessary.
Also, update the range unit-tests to check the expected number of fetches *exactly* since those values are not supposed to vary.
For now OffscreenCanvas in worker threads doesn't support ctx.filter,
so we need to fall back to a more expensive pixel-buffer SMask filtering in that case.
As a side effect, this also allows to support correctly smask in Safari.
Prepare reusable soft-mask canvases for filtered and backdrop-dependent masks,
and use a faster destination-in composition path where possible.
Handle Alpha SMask /BC correctly, preserve OOB alpha behavior, and mirror canvas path
operations needed while rendering inside soft-mask mode (mirrored clip was buggy).
Add reftest PDFs covering Alpha masks, transfer functions, backdrop/OOB
alpha, and the optimized composition paths.
This fixes two things that I overlooked in PR 21225, more specifically:
- Use proper, rather than semi, private class fields in `WasmImage`.
- Make tracking of `WasmImage` instances optional, to avoid keeping data alive permanently in the `IMAGE_DECODERS` build.
Using Array.prototype.shift() to drain the traversal queue makes each
visited node move the remaining queued entries. For large name/number
trees this can make getAll() spend quadratic time in queue management.
Iterate over the queue with for...of instead. Children pushed while
iterating are still visited, and the queue no longer needs repeated
front removals.
Given that the (browser) `gulp unittest` coverage includes the `external/` folder, it seems reasonable to include that folder in the coverage report when running unit-tests in Node.js as well.
Given that these classes are, with the exception of their `decode` methods, virtually identical this helps reduce code duplication and simplifies maintenance.
These changes reduce the size of the `gulp mozcentral` build-target by `1292` bytes, which obviously isn't a lot but still cannot hurt.
This was just released, see https://nodejs.org/en/blog/release/v26.0.0, hence it seems like a good idea to start running unit-tests in that version.
Also, stop running the unit-tests in Node.js version 25 since it'll soon reach EOL anyway and testing in three separate Node.js versions ought to suffice.