This patch adds right-click support for images in the PDF, allowing
users to download them. To minimize memory consumption, we:
- Do not store the images separately, and instead crop them out of the
PDF page canvas
- Only extract the images when needed (i.e. when the user right-clicks
on them), rather than eagery having all of them available.
To do so, we layer one empty 0x0 canvas per image, stretched to cover
the whole image, and only populate its contents on right click.
These images need to be inside the text layer: they cannot be _behind_
it, otherwise they would be covered by the text layer's container and
not be clickable, and they cannot be in front of it, otherwise they
would make the text spans unselectable.
This feature is managed by a new preference, `imagesRightClickMinSize`:
- when it's set to `-1`, right-click support is disabled
- when set to `0`, all images are available for right click
- when set to a positive integer, only images whose width and height are
greater than or equal to that value (in the PDF page frame of
reference) are available for right click.
This features is disabled by default outside of MOZCENTRAL, as it
significantly degrades the text selection experience in non-Firefox
browsers.
This leads to slightly shorter code[1] when initializing classes, and in some cases we can even remove the constructors, which shouldn't hurt; see https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-class-fields.md
It's probably possible to also change a lot of these class fields to private ones[2], however it's often difficult to tell at a glance if that's safe hence this patch only does this for the `PDFRenderingQueue`.
---
[1] This reduces the size of the `gulp mozcentral` output by 999 bytes, for a mostly mechanical code change.
[2] That sort of re-factoring should generally be done separately, on a class-by-class basis, to reduce the risk of regressions.
This should help reduce the maintenance burden of the code, since you no longer need to remember to update separate code when touching the different page/thumbnail classes.
Given that we either use the `L10n` class directly or extend it via `GenericL10n`, it should no longer be necessary to keep the interface-definition.
This should help reduce the maintenance burden of the code, since you no longer need to remember to update separate code when touching the `L10n` class.
The bug was supposed to be fixed by #20471 but here there are some annotations in the pdf.
When those annotations are added to the DOM, the struct tree has to be rendered but without
the text layer (because of asynchronicity).
So this patch is making sure that the modifications in the text layer are done once the
layer is rendered.
But keep a lower quality when enableOptimizedPartialRendering is true because we need to compensate the time
used to compute the bboxes and since subsequent rendering are faster it's more acceptable to see
a lower quality image for few tenths of seconds.
Before this patch, when `enableOptimizedPartialRendering`
is enabled we would record the bounding boxes of the
various operations on the first render.
This patches change it to happen on the first render that we
know will also need a detail view, so that the performance
cost is not paid for the case when the detail view is not used.
The size of the canvas has significant impact on the rendering
performance. If we are going to render a high-res detail
view on top of the full-page canvas, we can further
reduce the full-page canvas resolution to improve
rendering time without affecting the resolution seen by
the user.
Users will se the lower resolution when quickly scrolling around the
page, but it will then be replaced with the high-res
detail view.
In order to see the issue this patch is fixing:
- open a pdf with some highlights and a comment on page 1, at page 7
- open the comment sidebar
- click on the comment on page 1
Opening at page 7 lets a not fully rendered page which means that when jumping to it
with the sidebar, we re-use what we've instead of redrawing it.
This PR changes the way we store bounding boxes so that they use less
memory and can be more easily shared across threads in the future.
Instead of storing the bounding box and list of dependencies for each
operation that renders _something_, we now only store the bounding box
of _every_ operation and no dependencies list. The bounding box of
each operation covers the bounding box of all the operations affected
by it that render something. For example, the bounding box of a
`setFont` operation will be the bounding box of all the `showText`
operations that use that font.
This affects the debugging experience in pdfBug, since now the bounding
box of an operation may be larger than what it renders itself. To help
with this, now when hovering on an operation we also highlight (in red)
all its dependents. We highlight with white stripes operations that do
not affect any part of the page (i.e. with an empty bbox).
To save memory, we now save bounding box x/y coordinates as uint8
rather than float64. This effectively gives us a 256x256 uniform grid
that covers the page, which is high enough resolution for the usecase.
This commit is a first step towards #6419, and it can also help with
first compute which ops can affect what is visible in that part of
the page.
This commit adds logic to track operations with their respective
bounding boxes. Only operations that actually cause something to
be rendered have a bounding box and dependencies.
Consider the following example:
```
0. setFillRGBColor
1. beginText
2. showText "Hello"
3. endText
4. constructPath [...] -> eoFill
```
here we have three rendering operations: the showText op (2) and the
path (4). (2) depends on (0), (1) and (3), while (4) only depends on
(0). Both (2) and (4) have a bounding box.
This tracking happens when first rendering a PDF: we then use the
recorded information to optimize future partial renderings of a PDF, so
that we can skip operations that do not affected the PDF area on the
canvas.
All this logic only runs when the new `enableOptimizedPartialRendering`
preference, disabled by default, is enabled.
The bounding boxes and dependencies are also shown in the pdfBug
stepper. When hovering over a step now:
- it highlights the steps that they depend on
- it highlights on the PDF itself the bounding box
This is a precursor to moving the call into a
worker thread to let us use `OffscreenCanvas`. The
current position wouldn't work since we make
transformations to the canvas object after the
getContext call, which isn't allowed for
OffscreenCanvas. Also it isn't allowed to clone or
`transferControlToOffscreen` the canvas after the
`getContext` call.
This way it helps to reduce the overall canvas dimensions and make the rendering faster.
The drawback is that when scrolling, the page can be blurry in waiting for the rendering.
The default value is 200% on desktop and will be 100% for GeckoView.
It's already enabled by default in Firefox, and since there's no open issues regarding auto-linking I suppose that we can attempt to enable it unconditionally.
This addresses an inconsistency in the viewer, since the thumbnails don't respect the `maxCanvasPixels` option.
Note that, as far as I know, this has not lead to any bugs since the thumbnails render with a fixed (and small) width, however it really cannot hurt to address this (especially after the introduction of the `maxCanvasDim` option).
To support this a new `OutputScale`-method was added, to avoid having to duplicate code in multiple files.
Browsers not only limit the maximum total canvas area, but additionally also limit their maximum width/height which affects PDF documents with e.g. very tall and narrow pages.
To address this we add a new `maxCanvasDim` viewer-option, which in Firefox will use a browser preference, such that both the total canvas area and the width/height will affect when CSS-zooming is used.
Auto-linking requires a normal textLayer which XFA documents obviously don't have, and currently XFA documents cause "pointless" error messages to be logged in the console.
When rendering big PDF pages at high zoom levels, we currently fall back
to CSS zoom to avoid rendering canvases with too many pixels. This
causes zoomed in PDF to look blurry, and the text to be potentially
unreadable.
This commit adds support for rendering _part_ of a page (called
`PDFPageDetailView` in the code), so that we can render portion of a
page in a smaller canvas without hiting the maximun canvas size limit.
Specifically, we render an area of that page that is slightly larger
than the area that is visible on the screen (100% larger in each
direction, unless we have to limit it due to the maximum canvas size).
As the user scrolls around the page, we re-render a new area centered
around what is currently visible.
This base class contains the generic logic for:
- Creating a canvas and showing when appropriate
- Rendering in the canvas
- Keeping track of the rendering state
Rather than modifying the "raw" dimensions of the page, we'll instead apply the `userUnit` as an *additional* scale-factor via CSS.
*Please note:* It's not clear to me if this solution is fully correct either, or if there's other problems with it, but it at least *appears* to work.
---
With these changes, the following CSS variables are now assumed to be available/set as necessary: `--total-scale-factor`, `--scale-factor`, `--user-unit`, `--scale-round-x`, and `--scale-round-y`.
While investigating a bug, that I've not yet had time to fully investigate and report, I found that if there's ever an error thrown from the `Autolinker` class it'll prevent the annotationEditorLayer from rendering *and* the renderTask itself will be treated as having failed.
Automatically detect links in the text content of a file and automatically
generate link annotations at the appropriate locations to achieve
automatic link detection and hyperlinking.
It fixes#19239.
When the canvas isn't existing the editor has no image: it's fine because the editor is invisible.
Once it's made visible, the canvas is set when the annotation layer has been rendered.
Instead of conditionally checking if the `.cavnasWrapper` already
has a child element and then inserting the `canvas` before it, we can
use `.prepend` which always injects the new element as the first
child.
While drawing, in zooming fast enough, it's possible, intermittently, to have the canvas
after the svg which makes the svg invisible.
So this patch makes sure to have the canvas at the right position.
Converting errors to string drops their stack trace, making it more
difficult to debug their actual reason. We can instead pass the error
objects as-is to console.warn/error, so that Firefox/Chrome devtools
will show both the stack trace of the console.warn/error call, and the
original stack trace of the error.
This commit also enables the `unicorn/no-console-spaces` ESLint rule,
which avoids accidental extra spaces when passing multiple parameters to
`console.*` methods.
The idea is to insert a span in the text layer with an aria-role set to img
and use the bounding box provided by the attribute field in the tag dict in
order to have non-null dimensions for the image to make it "visible".
Right now, editable annotations are using their own canvas when they're drawn, but
it induces several issues:
- if the annotation has to be composed with the page then the canvas must be correctly
composed with its parent. That means we should move the canvas under canvasWrapper
and we should extract composing info from the drawing instructions...
Currently it's the case with highlight annotations.
- we use some extra memory for those canvas even if the user will never edit them, which
the case for example when opening a pdf in Fenix.
So with this patch, all the editable annotations are drawn on the canvas. When the
user switches to editing mode, then the pages with some editable annotations are redrawn but
without them: they'll be replaced by their counterpart in the annotation editor layer.