Skip to content

Commit 3d77eb9

Browse files
committed
Use the new text analyzer features and upgraded PDF.js
1 parent d730d9e commit 3d77eb9

24 files changed

+950
-435
lines changed

pdfjs/build

+26-20
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
11
#!/bin/bash
22

3+
# Set paths for the PDF.js build and target directories
34
PDFJS_BUILD=./pdfjs/pdf.js/build/generic-legacy
4-
DEV_BUILD=./build/dev/pdf
5-
WEB_BUILD=./build/web/pdf
6-
ZOTERO_BUILD=./build/zotero/pdf
5+
BUILD_BASE=./build
76

8-
rm -rf $DEV_BUILD
9-
rm -rf $WEB_BUILD
10-
rm -rf $ZOTERO_BUILD
7+
# Define builds array
8+
BUILDS=("dev" "web" "zotero")
119

10+
# Move into the PDF.js directory, install dependencies, and build
1211
cd pdfjs/pdf.js
1312
npm ci
14-
# Zotero with FF 60 ESR runs only the `legacy` build
13+
# Zotero with FF 115 ESR runs only the `legacy` build
1514
npx gulp generic-legacy
1615
cd ../../
1716

18-
mkdir -p build/dev/pdf
19-
cp -r $PDFJS_BUILD/* $DEV_BUILD/
20-
cp -r pdfjs/viewer.css $DEV_BUILD/web/
21-
rm $DEV_BUILD/web/*.pdf
17+
# Loop over each build and set up directories
18+
for BUILD in "${BUILDS[@]}"; do
19+
BUILD_DIR="$BUILD_BASE/$BUILD/pdf"
2220

23-
mkdir -p build/web/pdf
24-
cp -r $PDFJS_BUILD/* $WEB_BUILD/
25-
cp -r pdfjs/viewer.css $WEB_BUILD/web/
26-
rm $WEB_BUILD/web/*.pdf
21+
# Remove the existing build directory
22+
rm -rf "$BUILD_DIR"
2723

28-
mkdir -p build/zotero/pdf
29-
cp -r $PDFJS_BUILD/* $ZOTERO_BUILD/
30-
cp -r pdfjs/viewer.css $ZOTERO_BUILD/web/
31-
rm $ZOTERO_BUILD/web/*.pdf
32-
rm $ZOTERO_BUILD/web/*.map
24+
# Create necessary directories
25+
mkdir -p "$BUILD_DIR/build"
26+
mkdir -p "$BUILD_DIR/web/images"
27+
28+
# Copy files from PDFJS_BUILD to the current build directory
29+
cp -r "$PDFJS_BUILD/LICENSE" "$BUILD_DIR/"
30+
cp -r "$PDFJS_BUILD/build/pdf.mjs" "$BUILD_DIR/build/"
31+
cp -r "$PDFJS_BUILD/build/pdf.worker.mjs" "$BUILD_DIR/build/"
32+
cp -r "$PDFJS_BUILD/web/cmaps" "$BUILD_DIR/web/"
33+
cp -r "$PDFJS_BUILD/web/standard_fonts" "$BUILD_DIR/web/"
34+
cp -r "$PDFJS_BUILD/web/viewer.html" "$BUILD_DIR/web/"
35+
cp -r "$PDFJS_BUILD/web/viewer.mjs" "$BUILD_DIR/web/"
36+
cp -r "$PDFJS_BUILD/web/images/loading-icon.gif" "$BUILD_DIR/web/images/"
37+
cp -r "pdfjs/viewer.css" "$BUILD_DIR/web/"
38+
done

pdfjs/pdf.js

Submodule pdf.js updated 694 files

src/common/components/reader-ui.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ function View(props) {
7575
<OverlayPopup
7676
params={state[name + 'ViewOverlayPopup']}
7777
onOpenLink={props.onOpenLink}
78+
onNavigate={props.onNavigate}
7879
/>
7980
}
8081
</div>
@@ -135,7 +136,6 @@ const ReaderUI = React.forwardRef((props, ref) => {
135136
<Sidebar
136137
type={props.type}
137138
view={state.sidebarView}
138-
enableOutlineView={!!state.outline.length}
139139
filter={state.filter}
140140
onChangeView={props.onChangeSidebarView}
141141
onChangeFilter={props.onChangeFilter}

src/common/components/sidebar/outline-view.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,9 @@ function OutlineView({ outline, onNavigate, onOpenLink, onUpdate}) {
101101
);
102102
}
103103

104-
105-
106-
107-
108104
return (
109-
<div className="outline-view" data-tabstop="1">
110-
{renderItems(outline)}
105+
<div className={cx('outline-view', { loading: outline === null })} data-tabstop="1">
106+
{outline === null ? <div className="spinner"/> : renderItems(outline)}
111107
</div>
112108
);
113109
}

src/common/components/sidebar/sidebar.js

-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ function Sidebar(props) {
4242
className={cx('toolbar-button', { active: props.view === 'outline' })}
4343
title="Show Document Outline (double-click to expand/collapse all items)"
4444
tabIndex={-1}
45-
disabled={!props.enableOutlineView}
4645
onClick={() => props.onChangeView('outline')}
4746
><IconOutline/></button>
4847
</div>

src/common/components/toolbar.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ function Toolbar(props) {
156156
onBlur={handlePageNumberBlur}
157157
/>)}
158158
{props.pageLabel && (
159-
<span id="numPages">&nbsp;<div>{props.pageIndex + 1}/{props.pagesCount}</div></span>
159+
<span id="numPages">&nbsp;<div>{!(props.type === 'pdf' && props.pageIndex + 1 == props.pageLabel)
160+
&& (props.pageIndex + 1)} / {props.pagesCount}</div></span>
160161
)}
161162
</div>
162163
<div className="center tools">

src/common/components/view-popup/common/view-popup.js

+37-21
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ import cx from 'classnames';
55
// TODO: Resizing window doesn't properly reposition annotation popup on x axis, in EPUB view
66
function ViewPopup({ id, rect, className, uniqueRef, padding, children, onRender }) {
77
const [popupPosition, setPopupPosition] = useState(null);
8-
const [update, setUpdate] = useState();
98
const containerRef = useRef();
109
const xrect = useRef();
1110

1211
const initialized = useRef(false);
13-
1412
const pos = useRef(null);
1513

14+
// Update the popup position when the `rect` changes
1615
useEffect(() => {
1716
if (xrect.current) {
1817
let dx = rect[0] - xrect.current[0];
@@ -21,20 +20,24 @@ function ViewPopup({ id, rect, className, uniqueRef, padding, children, onRender
2120

2221
pos.current.left += dx;
2322
pos.current.top += dy;
23+
24+
setPopupPosition({}); // Trigger re-render
25+
} else {
26+
xrect.current = rect;
2427
}
25-
});
28+
}, [rect]);
2629

2730
useLayoutEffect(() => {
2831
if (initialized.current) {
2932
onRender && onRender();
3033
}
31-
});
34+
}, [popupPosition]);
3235

3336
useLayoutEffect(() => {
3437
updatePopupPosition();
3538
// Editor needs more time to get its final dimensions
3639
setTimeout(updatePopupPosition, 0);
37-
}, [uniqueRef]);
40+
}, [uniqueRef, rect]);
3841

3942
function updatePopupPosition() {
4043
if (!containerRef.current) {
@@ -49,43 +52,56 @@ function ViewPopup({ id, rect, className, uniqueRef, padding, children, onRender
4952
viewRect = [0, 0, viewRect.width, viewRect.height];
5053

5154
let annotationCenterLeft = rect[0] + (rect[2] - rect[0]) / 2;
52-
5355
let left = annotationCenterLeft - width / 2;
5456

55-
let isTop = true;
56-
57+
let side;
5758
let top;
58-
if (rect[3] + height + padding < viewRect[3]) {
59+
if (left < 0) {
60+
side = 'right';
61+
left = rect[2] + padding;
62+
top = rect[1] + ((rect[3] - rect[1]) - height) / 2;
63+
if (top < 0) {
64+
top = rect[1];
65+
} else if (top + height > viewRect[3]) {
66+
top = (rect[1] + (rect[3] - rect[1])) - height;
67+
}
68+
} else if (left + width > viewRect[2]) {
69+
side = 'left';
70+
left = rect[0] - width - padding;
71+
top = rect[1] + ((rect[3] - rect[1]) - height) / 2;
72+
if (top < 0) {
73+
top = rect[1];
74+
} else if (top + height > viewRect[3]) {
75+
top = (rect[1] + (rect[3] - rect[1])) - height;
76+
}
77+
} else if (rect[3] + height + padding < viewRect[3]) {
5978
top = rect[3] + padding;
60-
isTop = false;
61-
}
62-
else if (rect[1] - padding - height > 0) {
79+
side = 'bottom';
80+
} else if (rect[1] - padding - height > 0) {
6381
top = rect[1] - padding - height;
64-
}
65-
else {
82+
side = 'top';
83+
} else {
6684
top = rect[3] + padding;
67-
isTop = false;
85+
side = 'top';
6886
}
6987

7088
xrect.current = rect;
89+
pos.current = { top, left, side };
7190

72-
pos.current = { top, left, isTop };
73-
74-
setPopupPosition({});
91+
setPopupPosition({}); // Trigger re-render
7592
initialized.current = true;
7693
}
7794

78-
7995
let pointerClass = {};
8096
if (pos.current) {
81-
pointerClass['page-popup-' + (pos.current.isTop ? 'top' : 'bottom')] = true;
97+
pointerClass['page-popup-' + pos.current.side] = true;
8298
}
8399

84100
return (
85101
<div
86102
ref={containerRef}
87103
className={cx('view-popup', className, { ...pointerClass })}
88-
style={pos.current && { transform: `translate(${pos.current.left}px, ${pos.current.top}px)` }}
104+
style={pos.current ? { transform: `translate(${pos.current.left}px, ${pos.current.top}px)` } : {}}
89105
>
90106
{children}
91107
</div>

src/common/components/view-popup/overlay-popup/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React from 'react';
22
import PreviewPopup from './preview-popup';
33
import LinkPopup from './link-popup';
44
import FootnotePopup from './footnote-popup';
5+
import ReferencePopup from './reference/reference-popup';
6+
import CitationPopup from './reference/citation-popup';
57

68

79
function OverlayPopup(props) {
@@ -14,6 +16,12 @@ function OverlayPopup(props) {
1416
else if (props.params.type === 'footnote') {
1517
return <FootnotePopup {...props}/>;
1618
}
19+
else if (props.params.type === 'citation') {
20+
return <CitationPopup {...props}/>;
21+
}
22+
else if (props.params.type === 'reference') {
23+
return <ReferencePopup {...props}/>;
24+
}
1725
}
1826

1927
export default OverlayPopup;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React, { useLayoutEffect, useRef } from 'react';
2+
import { FormattedMessage, useIntl } from 'react-intl';
3+
import ViewPopup from '../../common/view-popup';
4+
import FormattedText from './common/formated-text';
5+
6+
function ReferenceRow({ reference, onNavigate, onOpenLink }) {
7+
function handleClick() {
8+
let { position } = reference;
9+
// onNavigate({ position });
10+
}
11+
12+
return (
13+
<div className="reference-row" onClick={handleClick}><FormattedText chars={reference.chars} onOpenLink={onOpenLink}/></div>
14+
);
15+
}
16+
17+
export default function CitationPopup(props) {
18+
const intl = useIntl();
19+
const containerRef = useRef();
20+
21+
return (
22+
<ViewPopup
23+
className="citation-popup"
24+
rect={props.params.rect}
25+
uniqueRef={props.params.ref}
26+
padding={10}
27+
>
28+
<div className="inner">
29+
{props.params.references.map((reference, index) => {
30+
return <ReferenceRow key={index} reference={reference} onNavigate={props.onNavigate} onOpenLink={props.onOpenLink}/>;
31+
})}
32+
</div>
33+
</ViewPopup>
34+
);
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
3+
export default function FormattedText({ chars, onOpenLink }) {
4+
// Helper function to create JSX from text and its properties
5+
function CreateJSX({ text, bold, italic, url }) {
6+
7+
function handleLinkClick(event) {
8+
event.preventDefault();
9+
event.stopPropagation();
10+
onOpenLink(url);
11+
}
12+
13+
let jsx = <span>{text}</span>;
14+
if (url) {
15+
jsx = <a href={url} onClick={handleLinkClick}>{jsx}</a>;
16+
}
17+
if (italic) {
18+
jsx = <em>{jsx}</em>;
19+
}
20+
if (bold) {
21+
jsx = <strong>{jsx}</strong>;
22+
}
23+
24+
return jsx;
25+
}
26+
27+
// Convert the chars array to JSX by grouping and formatting
28+
const formattedText = React.useMemo(() => {
29+
return chars.reduce((acc, char, index) => {
30+
const { u: currentChar, bold: isBold, italic: isItalic, url, spaceAfter } = char;
31+
if (!char.ignorable) {
32+
// Start a new group if different style or first character
33+
if (index === 0 || acc[acc.length - 1].isBold !== isBold || acc[acc.length - 1].isItalic !== isItalic || acc[acc.length - 1].url !== url) {
34+
acc.push({ text: currentChar, isBold, isItalic, url });
35+
}
36+
else {
37+
// Append to the current group if same style
38+
acc[acc.length - 1].text += currentChar;
39+
}
40+
if (spaceAfter || char.lineBreakAfter && index !== chars.length - 1) {
41+
acc[acc.length - 1].text += ' ';
42+
}
43+
}
44+
return acc;
45+
}, []).map((group, index) => <CreateJSX key={index} text={group.text} bold={group.bold} italic={group.italic} url={group.url}/>);
46+
}, [chars]);
47+
48+
return (
49+
<div>
50+
{formattedText}
51+
</div>
52+
);
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React, { useLayoutEffect, useRef } from 'react';
2+
import { FormattedMessage, useIntl } from 'react-intl';
3+
import ViewPopup from '../../common/view-popup';
4+
import FormattedText from './common/formated-text';
5+
6+
function ReferenceRow({ reference, onNavigate, onOpenLink }) {
7+
function handleClick() {
8+
let { position } = reference;
9+
// onNavigate({ position });
10+
}
11+
12+
return (
13+
<div className="reference-row" onClick={handleClick}><FormattedText chars={reference.chars} onOpenLink={onOpenLink}/></div>
14+
);
15+
}
16+
17+
export default function ReferencePopup(props) {
18+
const intl = useIntl();
19+
const containerRef = useRef();
20+
21+
return (
22+
<ViewPopup
23+
className="reference-popup"
24+
rect={props.params.rect}
25+
uniqueRef={props.params.ref}
26+
padding={10}
27+
>
28+
{props.params.references.map((reference, index) => {
29+
return <ReferenceRow key={index} reference={reference} onNavigate={props.onNavigate} onOpenLink={props.onOpenLink}/>;
30+
})}
31+
</ViewPopup>
32+
);
33+
}

src/common/focus-manager.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export class FocusManager {
8181

8282
_handlePointerDown(event) {
8383
if ('closest' in event.target) {
84-
if (!event.target.closest('input, textarea, [contenteditable="true"], .annotation, .thumbnails-view, .outline-view, .error-bar')) {
84+
if (!event.target.closest('input, textarea, [contenteditable="true"], .annotation, .thumbnails-view, .outline-view, .error-bar, .reference-row')) {
8585
// Note: Doing event.preventDefault() also prevents :active class on Firefox
8686
event.preventDefault();
8787
}

0 commit comments

Comments
 (0)