fix(ui): render PDFs with pdf.js instead of an iframe
Nightly Build / build (push) Successful in 7m59s
Nightly Build / build (push) Successful in 7m59s
On iOS the file viewer showed only the first page of a PDF, with no way to scroll to the rest — the document had to be downloaded and opened in another app. The cause was not ours: WebKit refuses to mount its PDF viewer inside an <iframe>/<object>/<embed> and paints a static first-page thumbnail instead. That hits Safari on iOS and every WKWebView, so the native shell too. The full viewer only exists for a top-level navigation. The desktop browsers do mount a viewer, but each mounts its own — Chrome's toolbar, Safari's page-index sidebar — so the same document also looked different on every machine. Both are answered by drawing the pages ourselves. New <pdf-view> (web/components/shared/pdf-view.js) renders a continuous scroll of canvas pages on the vendored pdf.js, with a zoom control and a page counter, and replaces the iframe for both native .pdf files and server-compiled LaTeX. Three properties are load-bearing: - pdf.js is imported lazily (~450 KB + a 1.2 MB worker), so a session that never opens a PDF never pays for it. - Canvases are created and destroyed as pages scroll. iOS caps the total canvas backing store a page may hold and silently blanks canvases past it, so an eager render would come out empty on exactly the platform this was written for. Off-screen pages keep only a correctly-sized box, which is also what keeps the scrollbar honest. - The text layer (selection, in-page find) is best-effort: it is transparent DOM over the pixels, so its failures are swallowed rather than surfaced. Vendored from pdfjs-dist 6.2.108: pdf.min.mjs, pdf.worker.min.mjs, the standard-font data (needed by PDFs that reference Helvetica/Times without embedding them) and the .textLayer block of pdf_viewer.css. CJK cmaps are deliberately left out. pdf.js 6 needs Safari/iOS 17.4+. Verified in headless Chromium against both a synthetic 12-page PDF using non-embedded Helvetica and a real 14-page paper with embedded fonts and figures: all pages present, last page renders after scrolling, page 1 released off-screen, text layer populated, zoom re-renders at the new scale, no JS errors.
This commit is contained in:
Vendored
+153
@@ -0,0 +1,153 @@
|
||||
/* pdf.js text-layer styles — extracted verbatim from pdfjs-dist@6.2.108
|
||||
web/pdf_viewer.css (.textLayer block only; the rest of that file styles
|
||||
the full pdf.js viewer app, which we don't use). Keep in sync when the
|
||||
vendored pdf.min.mjs is upgraded — TextLayer's DOM contract lives here.
|
||||
Apache-2.0, Mozilla Foundation. */
|
||||
.textLayer{
|
||||
color-scheme:only light;
|
||||
|
||||
position:absolute;
|
||||
text-align:initial;
|
||||
inset:0;
|
||||
overflow:clip;
|
||||
opacity:1;
|
||||
line-height:1;
|
||||
letter-spacing:normal;
|
||||
word-spacing:normal;
|
||||
-webkit-text-size-adjust:none;
|
||||
-moz-text-size-adjust:none;
|
||||
text-size-adjust:none;
|
||||
forced-color-adjust:none;
|
||||
transform-origin:0 0;
|
||||
caret-color:CanvasText;
|
||||
z-index:0;
|
||||
|
||||
&.highlighting{
|
||||
touch-action:none;
|
||||
}
|
||||
|
||||
:is(span, br){
|
||||
color:transparent;
|
||||
position:absolute;
|
||||
white-space:pre;
|
||||
cursor:text;
|
||||
transform-origin:0% 0%;
|
||||
-webkit-user-select:text;
|
||||
-moz-user-select:text;
|
||||
user-select:text;
|
||||
}
|
||||
--min-font-size:1;
|
||||
--text-scale-factor:calc(var(--total-scale-factor) * var(--min-font-size));
|
||||
--min-font-size-inv:calc(1 / var(--min-font-size));
|
||||
|
||||
> :not(.markedContent),
|
||||
.markedContent span:not(.markedContent){
|
||||
z-index:1;
|
||||
|
||||
--font-height:0;
|
||||
font-size:calc(var(--text-scale-factor) * var(--font-height));
|
||||
|
||||
--scale-x:1;
|
||||
--rotate:0deg;
|
||||
transform:rotate(var(--rotate)) scaleX(var(--scale-x)) scale(var(--min-font-size-inv));
|
||||
}
|
||||
|
||||
.markedContent{
|
||||
display:contents;
|
||||
}
|
||||
|
||||
span[role="img"]{
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
user-select:none;
|
||||
cursor:default;
|
||||
}
|
||||
|
||||
.highlight{
|
||||
--highlight-bg-color:rgb(180 0 170 / 0.25);
|
||||
--highlight-selected-bg-color:rgb(0 100 0 / 0.25);
|
||||
--highlight-backdrop-filter:none;
|
||||
--highlight-selected-backdrop-filter:none;
|
||||
|
||||
@media screen and (forced-colors: active){
|
||||
--highlight-bg-color:transparent;
|
||||
--highlight-selected-bg-color:transparent;
|
||||
--highlight-backdrop-filter:var(--hcm-highlight-filter);
|
||||
--highlight-selected-backdrop-filter:var(
|
||||
--hcm-highlight-selected-filter
|
||||
);
|
||||
}
|
||||
|
||||
margin:-1px;
|
||||
padding:1px;
|
||||
background-color:var(--highlight-bg-color);
|
||||
backdrop-filter:var(--highlight-backdrop-filter);
|
||||
border-radius:4px;
|
||||
|
||||
&.appended{
|
||||
position:initial;
|
||||
}
|
||||
|
||||
&.begin{
|
||||
border-radius:4px 0 0 4px;
|
||||
}
|
||||
|
||||
&.end{
|
||||
border-radius:0 4px 4px 0;
|
||||
}
|
||||
|
||||
&.middle{
|
||||
border-radius:0;
|
||||
}
|
||||
|
||||
&.selected{
|
||||
background-color:var(--highlight-selected-bg-color);
|
||||
backdrop-filter:var(--highlight-selected-backdrop-filter);
|
||||
scroll-margin-top:50px;
|
||||
}
|
||||
}
|
||||
|
||||
::-moz-selection{
|
||||
background:rgba(0 0 255 / 0.25);
|
||||
background:color-mix(in srgb, AccentColor, transparent 50%);
|
||||
color:transparent;
|
||||
}
|
||||
|
||||
::selection{
|
||||
background:rgba(0 0 255 / 0.25);
|
||||
background:color-mix(in srgb, AccentColor, transparent 50%);
|
||||
color:transparent;
|
||||
}
|
||||
|
||||
&.selectionRendering{
|
||||
::-moz-selection{
|
||||
background:transparent;
|
||||
color:transparent;
|
||||
}
|
||||
::selection{
|
||||
background:transparent;
|
||||
color:transparent;
|
||||
}
|
||||
}
|
||||
br::-moz-selection{
|
||||
background:transparent;
|
||||
}
|
||||
br::selection{
|
||||
background:transparent;
|
||||
}
|
||||
|
||||
.endOfContent{
|
||||
display:block;
|
||||
position:absolute;
|
||||
inset:100% 0 0;
|
||||
z-index:0;
|
||||
cursor:default;
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
user-select:none;
|
||||
}
|
||||
|
||||
&.selecting .endOfContent{
|
||||
top:0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user