Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5e1902c
chore: add pdfjs-dist typings
fukudasjp Oct 15, 2021
824c2d7
fix: control PDF rendering tasks properly
fukudasjp Nov 9, 2021
c5e09cb
feat: add pdf text layer support
fukudasjp Oct 20, 2021
9f0dcd5
fix: apply review comments
fukudasjp Nov 16, 2021
cc06446
refactor: extract text rendering hook
fukudasjp Nov 18, 2021
de5731f
refactor: extract async func call hook
fukudasjp Nov 19, 2021
f64fa07
fix: revise how to import css from pdfjs
fukudasjp Nov 24, 2021
85e3538
Merge remote-tracking branch 'upstream/master' into feat/redner-pdf-text
fukudasjp Nov 24, 2021
3c08df9
fix: install @types/pdfjs-dist to yarn2
fukudasjp Nov 24, 2021
c722221
feat: add script to update style
fukudasjp Nov 24, 2021
69ff042
refactor: revise script for importing css
fukudasjp Nov 24, 2021
17feb71
fix: use postcss to manupulate pdfjs-web css
fukudasjp Dec 2, 2021
158b8a2
fix: add comment to the style update script
fukudasjp Dec 2, 2021
0705d2e
refactor: move useAsyncFunctionCall to utils
fukudasjp Dec 2, 2021
9e9cad5
fix: name of package script
fukudasjp Dec 2, 2021
6ba0703
Merge remote-tracking branch 'upstream/master' into feat/redner-pdf-text
fukudasjp Dec 2, 2021
052336a
fix: apply CI comment
fukudasjp Dec 2, 2021
e081c92
fix: fix broken logic
fukudasjp Dec 2, 2021
68d895d
fix: remove unused code
fukudasjp Dec 2, 2021
8c82fae
fix: apply review comments around pdfjs css
fukudasjp Dec 3, 2021
2c28bd9
fix: pdfjs typings version
fukudasjp Dec 3, 2021
032842c
fix: apply review comments
fukudasjp Dec 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/discovery-react-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"react-virtualized": "9.21.1"
},
"devDependencies": {
"@types/pdfjs-dist": "^2.10.378",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that this package is now deprecated, showing:

This is a stub types definition. pdfjs-dist provides its own type definitions, so you do not need this installed.

"cross-env": "^7.0.3",
"css-loader": "^3.4.2",
"madge": "^5.0.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withKnobs, radios, number } from '@storybook/addon-knobs';
import { withKnobs, radios, number, boolean } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import PdfViewer from './PdfViewer';
import { document as doc } from 'components/DocumentPreview/__fixtures__/Art Effects.pdf';

Expand Down Expand Up @@ -32,6 +33,19 @@ storiesOf('DocumentPreview/components/PdfViewer', module)

const zoom = radios(zoomKnob.label, zoomKnob.options, zoomKnob.defaultValue);
const scale = parseFloat(zoom);
const showTextLayer = boolean('Show text layer', false);

return <PdfViewer file={atob(doc)} page={page} scale={scale} setLoading={(): void => {}} />;
const setLoadingAction = action('setLoading');
const setRenderedTextAction = action('setRenderedText');

return (
<PdfViewer
file={atob(doc)}
page={page}
scale={scale}
showTextLayer={showTextLayer}
setLoading={setLoadingAction}
setRenderedText={setRenderedTextAction}
/>
);
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import React, { SFC, useEffect, useRef, useState, useMemo } from 'react';
import PdfjsLib from 'pdfjs-dist';
import React, { FC, useEffect, useRef, useMemo, useCallback } from 'react';
import cx from 'classnames';
import PdfjsLib, {
PDFDocumentProxy,
PDFPageProxy,
PDFPageViewport,
PDFRenderTask
} from 'pdfjs-dist';
import PdfjsWorkerAsText from 'pdfjs-dist/build/pdf.worker.min.js';
import { settings } from 'carbon-components';
import PdfViewerTextLayer, { PdfRenderedText } from './PdfViewerTextLayer';
import useAsyncFunctionCall from './useAsyncFunctionCall';

setupPdfjs();

interface Props {
className?: string;

/**
* PDF file data as base64-encoded string
*/
Expand All @@ -21,6 +31,16 @@ interface Props {
*/
scale: number;

/**
* Render text layer
*/
showTextLayer?: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for making this an option? Why not always show the text layer when rendering a PDF?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we reuse this component to render thumbnail image or tile.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried that this would take up a lot of resources, if we need to create many "thumbnails". We'll have to test this out and make sure we don't overwhelm the browser.

SDU Annotator has a server-side process to create the thumbnails. I don't believe we will be able to make use of that, but a similar approach may be needed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Let's think about it when we implement thumbnails. With that, our usage is only to display a (full-page) document and I drop the option.


/**
* Text layer class name. Only applicable when showTextLayer is true
*/
textLayerClassName?: string;

/**
* Callback invoked with page count, once `file` has been parsed
*/
Expand All @@ -33,88 +53,91 @@ interface Props {
* Callback which is invoked with whether to enable/disable toolbar controls
*/
setHideToolbarControls?: (disabled: boolean) => void;
/**
* Callback for text layer info
*/
setRenderedText?: (info: PdfRenderedText | null) => any;
}

const PdfViewer: SFC<Props> = ({
const PdfViewer: FC<Props> = ({
className,
file,
page,
scale,
showTextLayer,
textLayerClassName,
setPageCount,
setLoading,
setHideToolbarControls
setHideToolbarControls,
setRenderedText,
children
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null);

// In order to prevent unnecessary re-loading, loaded file and page are stored in state
const [loadedFile, setLoadedFile] = useState<any>(null);
const [loadedPage, setLoadedPage] = useState<any>(null);

useEffect(() => {
let didCancel = false;

async function loadPdf(): Promise<void> {
if (file) {
const newPdf = await _loadPdf(file);
if (!didCancel) {
setLoadedFile(newPdf);
if (setPageCount) {
setPageCount(newPdf.numPages);
}
}
}
}
loadPdf();

return (): void => {
didCancel = true;
};
}, [file, setPageCount]);

useEffect(() => {
let didCancel = false;

async function loadPage(): Promise<void> {
if (loadedFile && page > 0) {
const newPage = await _loadPage(loadedFile, page);
if (!didCancel) {
setLoadedPage(newPage);
}
}
}
loadPage();

return (): void => {
didCancel = true;
};
}, [loadedFile, page]);
const loadedFile = useAsyncFunctionCall(
useCallback(async () => (file ? await _loadPdf(file) : null), [file])
);
const loadedPage = useAsyncFunctionCall(
useCallback(
async () => (loadedFile && page > 0 ? await _loadPage(loadedFile, page) : null),
[loadedFile, page]
)
);

const [viewport, canvasInfo] = useMemo(() => {
const viewport = loadedPage?.getViewport({ scale });
const canvasInfo = viewport ? getCanvasInfo(viewport) : undefined;
return [viewport, canvasInfo];
}, [loadedPage, scale]);

// render page
useAsyncFunctionCall(
useCallback(
async (abortSignal: AbortSignal) => {
if (loadedPage && !(loadedPage as any).then && viewport && canvasInfo) {
const task = _renderPage(loadedPage, canvasRef.current!, viewport, canvasInfo);
abortSignal.addEventListener('abort', () => task?.cancel());
await task?.promise;

setLoading(false);
}
},
[canvasInfo, loadedPage, setLoading, viewport]
)
);

useEffect(() => {
if (loadedPage && !loadedPage.then && viewport && canvasInfo) {
_renderPage(loadedPage, canvasRef.current!, viewport, canvasInfo);
setLoading(false);
if (setPageCount && loadedFile) {
setPageCount(loadedFile.numPages);
}
}, [loadedPage, viewport, canvasInfo, setLoading]);
}, [loadedFile, setPageCount]);

useEffect(() => {
if (setHideToolbarControls) {
setHideToolbarControls(false);
}
}, [setHideToolbarControls]);

const classNameBase = `${settings.prefix}--document-preview-pdf-viewer`;
return (
<canvas
ref={canvasRef}
className={`${settings.prefix}--document-preview-pdf-viewer`}
style={{ width: `${canvasInfo?.width ?? 0}px`, height: `${canvasInfo?.height ?? 0}px` }}
width={canvasInfo?.canvasWidth}
height={canvasInfo?.canvasHeight}
/>
<div className={cx(classNameBase, className)}>
<canvas
ref={canvasRef}
className={`${classNameBase}--canvas`}
style={{ width: `${canvasInfo?.width ?? 0}px`, height: `${canvasInfo?.height ?? 0}px` }}
width={canvasInfo?.canvasWidth}
height={canvasInfo?.canvasHeight}
/>
{showTextLayer && (
<PdfViewerTextLayer
className={cx(`${classNameBase}--text`, textLayerClassName)}
loadedPage={loadedPage}
scale={scale}
setRenderedText={setRenderedText}
/>
)}
{children}
</div>
);
};

Expand All @@ -123,32 +146,35 @@ PdfViewer.defaultProps = {
scale: 1
};

function _loadPdf(data: string): Promise<any> {
function _loadPdf(data: string): Promise<PDFDocumentProxy> {
return PdfjsLib.getDocument({ data }).promise;
}

function _loadPage(file: any, page: number): Promise<any> {
function _loadPage(file: PDFDocumentProxy, page: number) {
return file.getPage(page);
}

function _renderPage(
pdfPage: any,
pdfPage: PDFPageProxy,
canvas: HTMLCanvasElement,
viewport: any,
viewport: PDFPageViewport,
canvasInfo: CanvasInfo
): void {
): PDFRenderTask | null {
const canvasContext = canvas.getContext('2d');
canvasContext?.resetTransform();
canvasContext?.scale(canvasInfo.canvasScale, canvasInfo.canvasScale);
pdfPage.render({ canvasContext, viewport });
if (canvasContext) {
canvasContext.resetTransform();
canvasContext.scale(canvasInfo.canvasScale, canvasInfo.canvasScale);
return pdfPage.render({ canvasContext, viewport });
}
return null;
}

// set up web worker for use by PDF.js library
// @see https://stackoverflow.com/a/6454685/908343
function setupPdfjs(): void {
if (typeof Worker !== 'undefined') {
const blob = new Blob([PdfjsWorkerAsText], { type: 'text/javascript' });
const pdfjsWorker = new Worker(URL.createObjectURL(blob));
const pdfjsWorker = new Worker(URL.createObjectURL(blob)) as any;
PdfjsLib.GlobalWorkerOptions.workerPort = pdfjsWorker;
} else {
PdfjsLib.GlobalWorkerOptions.workerSrc = PdfjsWorkerAsText;
Expand Down
Loading