Skip to content

Commit 3d9033f

Browse files
committed
fix #7853 -- work in progress; this is close
1 parent cd56199 commit 3d9033f

File tree

4 files changed

+149
-64
lines changed

4 files changed

+149
-64
lines changed

src/packages/frontend/frame-editors/latex-editor/pdfjs-annotation.tsx

Lines changed: 52 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -88,67 +88,6 @@ export default function AnnotationLayer({
8888
}
8989
}
9090

91-
function render_annotations() {
92-
if (annotations == null) return <div />;
93-
const v: JSX.Element[] = [];
94-
for (const annotation0 of annotations) {
95-
// NOTE: We have to do this ugly cast to any because the @types for pdfjs are
96-
// incomplete/wrong for annotations.
97-
const annotation: any = annotation0 as any;
98-
if (annotation.subtype != "Link") {
99-
// We only care about link annotations *right now*, for the purposes of the latex editor.
100-
console.log("Annotation not implemented", annotation);
101-
continue;
102-
}
103-
const [x1, y1, x2, y2] = Util.normalizeRect(annotation.rect);
104-
const page_height = page.view[3];
105-
const left = x1 - 1,
106-
top = page_height - y2 - 1,
107-
width = x2 - x1 + 2,
108-
height = y2 - y1 + 1;
109-
110-
let border = "";
111-
if (annotation.borderStyle.width) {
112-
border = `0.5px solid rgb(${annotation.color[0]}, ${annotation.color[1]}, ${annotation.color[2]})`;
113-
}
114-
115-
// Note: this "annotation" in the onClick below is the right one because we use "let"
116-
// *inside* the for loop above -- I'm not making the typical closure/scopying mistake.
117-
const elt = (
118-
<div
119-
onClick={() => clickAnnotation(annotation)}
120-
key={annotation.id}
121-
style={{
122-
position: "absolute",
123-
left: left * scale,
124-
top: top * scale,
125-
width: width * scale,
126-
height: height * scale,
127-
border: border,
128-
cursor: "pointer",
129-
zIndex: 1, // otherwise, the yellow sync highlight is above url links
130-
}}
131-
/>
132-
);
133-
v.push(elt);
134-
}
135-
136-
// handle highlight which is used for synctex.
137-
if (sync_highlight !== undefined) {
138-
v.push(render_sync_highlight(scale, page.view[2], sync_highlight.y));
139-
}
140-
141-
return (
142-
<div
143-
style={{
144-
position: "absolute",
145-
}}
146-
>
147-
{v}
148-
</div>
149-
);
150-
}
151-
15291
function render_sync_highlight(
15392
scale: number,
15493
width: number,
@@ -172,5 +111,56 @@ export default function AnnotationLayer({
172111
);
173112
}
174113

175-
return render_annotations();
114+
if (annotations == null) {
115+
return <div />;
116+
}
117+
const v: JSX.Element[] = [];
118+
for (const annotation0 of annotations) {
119+
// NOTE: We have to do this ugly cast to any because the @types for pdfjs are
120+
// incomplete/wrong for annotations.
121+
const annotation: any = annotation0 as any;
122+
if (annotation.subtype != "Link") {
123+
// We only care about link annotations *right now*, for the purposes of the latex editor.
124+
console.log("Annotation not implemented", annotation);
125+
continue;
126+
}
127+
const [x1, y1, x2, y2] = Util.normalizeRect(annotation.rect);
128+
const page_height = page.view[3];
129+
const left = x1 - 1,
130+
top = page_height - y2 - 1,
131+
width = x2 - x1 + 2,
132+
height = y2 - y1 + 1;
133+
134+
let border = "";
135+
if (annotation.borderStyle.width) {
136+
border = `0.5px solid rgb(${annotation.color[0]}, ${annotation.color[1]}, ${annotation.color[2]})`;
137+
}
138+
139+
// Note: this "annotation" in the onClick below is the right one because we use "let"
140+
// *inside* the for loop above -- I'm not making the typical closure/scopying mistake.
141+
const elt = (
142+
<div
143+
onClick={() => clickAnnotation(annotation)}
144+
key={annotation.id}
145+
style={{
146+
position: "absolute",
147+
left: left * scale,
148+
top: top * scale,
149+
width: width * scale,
150+
height: height * scale,
151+
border: border,
152+
cursor: "pointer",
153+
zIndex: 1, // otherwise, the yellow sync highlight is above url links
154+
}}
155+
/>
156+
);
157+
v.push(elt);
158+
}
159+
160+
// handle highlight which is used for synctex.
161+
if (sync_highlight !== undefined) {
162+
v.push(render_sync_highlight(scale, page.view[2], sync_highlight.y));
163+
}
164+
165+
return <div style={{ position: "absolute" }}>{v}</div>;
176166
}

src/packages/frontend/frame-editors/latex-editor/pdfjs-canvas-page.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ Render a single PDF page using canvas.
1010
import type { PDFPageProxy, PDFPageViewport } from "pdfjs-dist/webpack.mjs";
1111
import { useCallback, useEffect, useRef } from "react";
1212
import { useDebouncedCallback } from "use-debounce";
13-
1413
import AnnotationLayer, { SyncHighlight } from "./pdfjs-annotation";
14+
import TextLayer from "./pdfjs-text";
1515

1616
interface Props {
1717
page: PDFPageProxy;
@@ -61,7 +61,7 @@ export default function CanvasPage({
6161
const ctx = canvas.getContext("2d");
6262
if (ctx == null) {
6363
console.error(
64-
"pdf.js -- unable to get a 2d canvas, so not rendering page"
64+
"pdf.js -- unable to get a 2d canvas, so not rendering page",
6565
);
6666
return;
6767
}
@@ -111,6 +111,11 @@ export default function CanvasPage({
111111
clickAnnotation={clickAnnotation}
112112
syncHighlight={syncHighlight}
113113
/>
114+
<TextLayer
115+
page={page}
116+
scale={scale}
117+
viewport={viewport}
118+
/>
114119
<div ref={divRef} />
115120
</div>
116121
);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/* See https://github.com/mozilla/pdf.js/blob/a7e1bf64c4c7a42c7577ce9490054faa1c4eda99/test/text_layer_test.css */
2+
3+
.cocalc-pdfjs-text-layer {
4+
position: absolute;
5+
inset: 0;
6+
line-height: 1;
7+
opacity: 1;
8+
9+
:is(span, br) {
10+
color: black;
11+
position: absolute;
12+
white-space: pre;
13+
transform-origin: 0% 0%;
14+
border: solid 1px rgb(255 0 0 / 0.5);
15+
background-color: rgb(255 255 32 / 0.1);
16+
box-sizing: border-box;
17+
}
18+
19+
.markedContent {
20+
border: none;
21+
background-color: transparent;
22+
}
23+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3+
* License: MS-RSL – see LICENSE.md for details
4+
*/
5+
6+
/*
7+
Render the text layer on top of a page to support selection.
8+
9+
How the hell did I figure this code out? First, there is nightmare of misleading and
10+
useless outdated google hits from the last 10+ years. Finally, searching the
11+
pdfjs git repo yielded this, which was helpful, which is I guess how this is
12+
implemented for their own viewer:
13+
14+
15+
https://github.com/mozilla/pdf.js/blob/a7e1bf64c4c7a42c7577ce9490054faa1c4eda99/web/text_layer_builder.js#L40
16+
17+
18+
This wasn't helpful:
19+
20+
https://github.com/mozilla/pdf.js/blob/a7e1bf64c4c7a42c7577ce9490054faa1c4eda99/examples/text-only/pdf2svg.mjs#L24
21+
*/
22+
23+
import type { PDFPageProxy } from "pdfjs-dist/webpack.mjs";
24+
import { useEffect, useRef } from "react";
25+
import { TextLayer } from "pdfjs-dist";
26+
import "./pdfjs-text.css";
27+
28+
interface Props {
29+
page: PDFPageProxy;
30+
scale: number;
31+
viewport;
32+
}
33+
34+
export default function PdfjsTextLayer({ page, scale, viewport }: Props) {
35+
const divRef = useRef<HTMLDivElement | null>(null);
36+
37+
// useEffect(() => {
38+
// (async () => setTextContent(await page.getTextContent()))();
39+
// }, [page]);
40+
41+
useEffect(() => {
42+
const elt = divRef.current;
43+
if (!elt) {
44+
return;
45+
}
46+
(async () => {
47+
const t = new TextLayer({
48+
textContentSource: page.streamTextContent({
49+
includeMarkedContent: true,
50+
disableNormalization: true,
51+
}),
52+
container: elt,
53+
viewport,
54+
});
55+
elt.innerHTML = "";
56+
await t.render();
57+
})();
58+
}, [page, scale]);
59+
60+
return (
61+
<div
62+
ref={divRef}
63+
style={{ "--scale-factor": scale } as any}
64+
className="cocalc-pdfjs-text-layer"
65+
/>
66+
);
67+
}

0 commit comments

Comments
 (0)