aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2019-06-18 02:19:07 -0400
committerBob Zeleznik <zzzman@gmail.com>2019-06-18 02:19:07 -0400
commit62c781c0c79ac395c5e117d208a90485ff1ba599 (patch)
tree0198c16e5299470d21a4f3d0fc0720b2ab975cce /src
parent4dc8c03562a0473becb895824740da487e16e771 (diff)
faster loading of PDFs
Diffstat (limited to 'src')
-rw-r--r--src/client/views/nodes/PDFBox.tsx1
-rw-r--r--src/client/views/pdf/PDFViewer.tsx340
2 files changed, 116 insertions, 225 deletions
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 655c12ab3..3aaa5749d 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -69,7 +69,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
loaded = (nw: number, nh: number, np: number) => {
if (this.props.Document) {
let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
- console.log("pages = " + np);
doc.numPages = np;
if (doc.nativeWidth && doc.nativeHeight) return;
let oldaspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index d74a16f3f..5149e48fd 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -43,16 +43,7 @@ export class PDFViewer extends React.Component<IPDFViewerProps> {
@action
componentDidMount() {
- const pdfUrl = this.props.url;
- console.log("pdf starting to load")
- let promise = Pdfjs.getDocument(pdfUrl).promise;
-
- promise.then((pdf: Pdfjs.PDFDocumentProxy) => {
- runInAction(() => {
- console.log("pdf url received");
- this._pdf = pdf;
- });
- });
+ Pdfjs.getDocument(this.props.url).promise.then(pdf => runInAction(() => this._pdf = pdf));
}
render() {
@@ -83,12 +74,8 @@ class Viewer extends React.Component<IViewerProps> {
// _visibleElements is the array of JSX elements that gets rendered
@observable.shallow private _visibleElements: JSX.Element[] = [];
// _isPage is an array that tells us whether or not an index is rendered as a page or as a placeholder
- @observable private _isPage: boolean[] = [];
+ @observable private _isPage: string[] = [];
@observable private _pageSizes: { width: number, height: number }[] = [];
- @observable private _startIndex: number = 0;
- @observable private _endIndex: number = 1;
- @observable private _loaded: boolean = false;
- @observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
@observable private _annotations: Doc[] = [];
@observable private _pointerEvents: "all" | "none" = "all";
@observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
@@ -97,7 +84,6 @@ class Viewer extends React.Component<IViewerProps> {
private _annotationLayer: React.RefObject<HTMLDivElement>;
private _reactionDisposer?: IReactionDisposer;
private _annotationReactionDisposer?: IReactionDisposer;
- private _pagesLoaded: number = 0;
private _dropDisposer?: DragManager.DragDropDisposer;
constructor(props: IViewerProps) {
@@ -106,77 +92,62 @@ class Viewer extends React.Component<IViewerProps> {
this._annotationLayer = React.createRef();
}
+ componentDidUpdate = (prevProps: IViewerProps) => {
+ if (this.scrollY !== prevProps.scrollY && this._visibleElements.length) {
+ this.renderPages(this.startIndex, this.endIndex, false);
+ }
+ }
+
@action
componentDidMount = () => {
let wasSelected = this.props.parent.props.active();
- // reaction for when document gets (de)selected
this._reactionDisposer = reaction(
- () => [this.props.parent.props.active(), this.startIndex],
- () => {
- // if deselected, render images in place of pdf
- if (wasSelected && !this.props.parent.props.active()) {
- this.saveThumbnail();
- }
- // if selected, render pdf
- else if (!wasSelected && this.props.parent.props.active()) {
- this.renderPages(this.startIndex, this.endIndex, true);
- }
+ () => [this.props.parent.props.active(), this.startIndex, this.props.pdf],
+ async () => {
+ await this.initialLoad();
wasSelected = this.props.parent.props.active();
- this._pointerEvents = wasSelected ? "none" : "all";
- },
- { fireImmediately: true }
- );
+ runInAction(() => this._pointerEvents = wasSelected ? "none" : "all");
+ this.renderPages(this.startIndex, this.endIndex, false);
+ }, { fireImmediately: true });
- if (this.props.parent.Document) {
- this._annotationReactionDisposer = reaction(
- () => DocListCast(this.props.parent.Document.annotations),
- () => {
- let annotations = DocListCast(this.props.parent.Document.annotations);
- if (annotations && annotations.length) {
- this.renderAnnotations(annotations, true);
- }
- },
- { fireImmediately: true }
- );
- }
+ this._annotationReactionDisposer = reaction(
+ () => this.props.parent.Document && DocListCast(this.props.parent.Document.annotations),
+ (annotations: Doc[]) =>
+ annotations && annotations.length && this.renderAnnotations(annotations, true),
+ { fireImmediately: true });
+ }
- setTimeout(() => {
- // this.renderPages(this.startIndex, this.endIndex, true);
- this.initialLoad();
- }, 1000);
+ componentWillUnmount = () => {
+ this._reactionDisposer && this._reactionDisposer();
+ this._annotationReactionDisposer && this._annotationReactionDisposer();
}
@action
- initialLoad = () => {
- let pdf = this.props.pdf;
- if (pdf) {
- this._pageSizes = Array<{ width: number, height: number }>(pdf.numPages);
- let rendered = 0;
- for (let i = 0; i < pdf.numPages; i++) {
- pdf.getPage(i + 1).then(
- (page: Pdfjs.PDFPageProxy) => {
- runInAction(() => {
- this._pageSizes[i] = { width: page.view[2] * scale, height: page.view[3] * scale };
- });
- console.log(`page ${i} size retreieved`);
- rendered++;
- if (rendered === pdf!.numPages - 1) {
- this.saveThumbnail();
- }
- }
- );
+ initialLoad = async () => {
+ if (this.props.pdf && this._pageSizes.length === 0) {
+ let pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages);
+ for (let i = 0; i < this.props.pdf.numPages; i++) {
+ await this.props.pdf.getPage(i + 1).then(page => runInAction(() => {
+ pageSizes[i] = { width: page.view[2] * scale, height: page.view[3] * scale };
+ if (i === 0) this.props.loaded(pageSizes[i].width, pageSizes[i].height, this.props.pdf!.numPages);
+ }));
}
+ runInAction(() => {
+ this._pageSizes = pageSizes;
+ let divs = Array.from(Array(this._pageSizes.length).keys()).map(i => (
+ <div key={`pdfviewer-placeholder-${i}`} className="pdfviewer-placeholder"
+ style={{ width: this._pageSizes[i] ? this._pageSizes[i].width : 0, height: this._pageSizes[i] ? this._pageSizes[i].height : 0 }} />
+ ));
+ this._isPage = Array.from(Array(this._pageSizes.length).map(p => "none"));
+ this._visibleElements = new Array<JSX.Element>(...divs);
+ })
}
}
private mainCont = (div: HTMLDivElement | null) => {
- if (this._dropDisposer) {
- this._dropDisposer();
- }
+ this._dropDisposer && this._dropDisposer();
if (div) {
- this._dropDisposer = DragManager.MakeDropTarget(div, {
- handlers: { drop: this.drop.bind(this) }
- });
+ this._dropDisposer = div && DragManager.MakeDropTarget(div, { handlers: { drop: this.drop.bind(this) } });
}
}
@@ -222,154 +193,102 @@ class Viewer extends React.Component<IViewerProps> {
e.stopPropagation();
}
}
-
- componentWillUnmount = () => {
- if (this._reactionDisposer) {
- this._reactionDisposer();
- }
- if (this._annotationReactionDisposer) {
- this._annotationReactionDisposer();
- }
- }
-
+ /**
+ * Called by the Page class when it gets rendered, initializes the lists and
+ * puts a placeholder with all of the correct page sizes when all of the pages have been loaded.
+ */
@action
- saveThumbnail = async () => {
- // file address of the pdf
- const address: string = this.props.url;
- for (let i = 0; i < this._visibleElements.length; i++) {
- if (this._isPage[i]) {
- // change the address to be the file address of the PNG version of each page
- let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${i + 1}.PNG`)));
- let thisAddress = res.path;
- let nWidth = parseInt(res.width);
- let nHeight = parseInt(res.height);
- // replace page with image
- runInAction(() =>
- this._visibleElements[i] = <img key={thisAddress} style={{ width: `${nWidth * scale}px`, height: `${nHeight * scale}px` }} src={thisAddress} />);
- }
- }
- }
-
- @computed get scrollY(): number {
- return this.props.scrollY;
- }
-
- @computed get startIndex(): number {
- return Math.max(0, this.getIndex(this.scrollY) - this._pageBuffer);
- }
-
- @computed get endIndex(): number {
- let width = this._pageSizes.map(i => i ? i.width : 0);
- return Math.min(this.props.pdf ? this.props.pdf.numPages - 1 : 0, this.getIndex(this.scrollY + Math.max(...width)) + this._pageBuffer);
- }
-
- componentDidUpdate = (prevProps: IViewerProps) => {
- if (this.scrollY !== prevProps.scrollY || this._pdf !== this.props.pdf) {
- this._pdf = this.props.pdf;
- // render pages if the scorll position changes
- console.log(`START: ${this.startIndex}, END: ${this.endIndex}`);
- this.renderPages(this.startIndex, this.endIndex);
- }
+ pageLoaded = (index: number, page: Pdfjs.PDFPageViewport): void => {
+ this.props.pdf && this.props.loaded && this.props.loaded(page.width, page.height, this.props.pdf.numPages);
}
-
@action
- private renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => {
- if (removeOldAnnotations) {
- this._annotations = annotations;
- }
- else {
- this._annotations.push(...annotations);
- this._annotations = new Array<Doc>(...this._annotations);
+ getPlaceholderPage = (page: number) => {
+ if (this._isPage[page] !== "none") {
+ this._isPage[page] = "none";
+ this._visibleElements[page] = (
+ <div key={`pdfviewer-placeholder-${page}`} className="pdfviewer-placeholder" style={{ width: this._pageSizes[page] ? this._pageSizes[page].width : 0, height: this._pageSizes[page] ? this._pageSizes[page].height : 0 }} />
+ );
}
}
-
- /**
- * @param startIndex: where to start rendering pages
- * @param endIndex: where to end rendering pages
- * @param forceRender: (optional), force pdfs to re-render, even if the page already exists
- */
@action
- renderPages = (startIndex: number, endIndex: number, forceRender: boolean = false) => {
- let numPages = this.props.pdf ? this.props.pdf.numPages : 0;
- if (!this.props.pdf) {
- return;
- }
-
- if (this._pageSizes.length !== numPages) {
- this._pageSizes = new Array(numPages).map(i => ({ width: 0, height: 0 }));
- }
-
- // this is only for an initial render to get all of the pages rendered
- if (this._visibleElements.length !== numPages) {
- let divs = Array.from(Array(numPages).keys()).map(i => i < 5 ? (
+ getRenderedPage = (page: number) => {
+ if (this._isPage[page] !== "page") {
+ this._isPage[page] = "page";
+ this._visibleElements[page] = (
<Page
pdf={this.props.pdf}
- page={i}
- numPages={numPages}
- key={`${this.props.pdf ? this.props.pdf.fingerprint + `-page${i + 1}` : "undefined"}`}
- name={`${this.props.pdf ? this.props.pdf.fingerprint + `-page${i + 1}` : "undefined"}`}
+ page={page}
+ numPages={this.props.pdf!.numPages}
+ key={`${this.props.pdf ? this.props.pdf.fingerprint + `-page${page + 1}` : "undefined"}`}
+ name={`${this.props.pdf ? this.props.pdf.fingerprint + `-page${page + 1}` : "undefined"}`}
pageLoaded={this.pageLoaded}
parent={this.props.parent}
- renderAnnotations={this.renderAnnotations}
makePin={this.createPinAnnotation}
+ renderAnnotations={this.renderAnnotations}
createAnnotation={this.createAnnotation}
sendAnnotations={this.receiveAnnotations}
makeAnnotationDocuments={this.makeAnnotationDocument}
receiveAnnotations={this.sendAnnotations}
{...this.props} />
- ) :
- (<div key={`pdfviewer-placeholder-${i}`} className="pdfviewer-placeholder" style={{ width: this._pageSizes[i] ? this._pageSizes[i].width : 612 * scale, height: this._pageSizes[i] ? this._pageSizes[i].height : 792 * scale }} />)
);
- let arr = Array.from(Array(numPages).keys()).map(i => i < 5);
- this._visibleElements.push(...divs);
- this._isPage.push(...arr);
}
+ }
- // if nothing changed, return
- if (startIndex === this._startIndex && endIndex === this._endIndex && !forceRender) {
- return;
+ // change the address to be the file address of the PNG version of each page
+ // file address of the pdf
+ @action
+ getPageImage = async (page: number) => {
+ let handleError = () => this.getRenderedPage(page);
+ if (this._isPage[page] != "image") {
+ this._isPage[page] = "image";
+ const address = this.props.url;
+ let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`)));
+ runInAction(() => this._visibleElements[page] = <img key={res.path} src={res.path} onError={handleError}
+ style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />);
}
+ }
- // unrender pages outside of the pdf by replacing them with empty stand-in divs
- for (let i = 0; i < numPages; i++) {
- if (i < startIndex || i > endIndex) {
- if (this._isPage[i]) {
- this._visibleElements[i] = (
- <div key={`pdfviewer-placeholder-${i}`} className="pdfviewer-placeholder" style={{ width: this._pageSizes[i] ? this._pageSizes[i].width : 0, height: this._pageSizes[i] ? this._pageSizes[i].height : 0 }} />
- );
+ @computed get scrollY(): number { return this.props.scrollY; }
+
+ @computed get startIndex(): number { return Math.max(0, this.getPageFromScroll(this.scrollY) - this._pageBuffer); }
+
+ @computed get endIndex(): number {
+ let width = this._pageSizes.map(i => i ? i.width : 0);
+ return Math.min(this.props.pdf ? this.props.pdf.numPages - 1 : 0, this.getPageFromScroll(this.scrollY + Math.max(...width)) + this._pageBuffer);
+ }
+
+ /**
+ * @param startIndex: where to start rendering pages
+ * @param endIndex: where to end rendering pages
+ * @param forceRender: (optional), force pdfs to re-render, even if the page already exists
+ */
+ @action
+ renderPages = (startIndex: number, endIndex: number, forceRender: boolean = false) => {
+ if (this.props.pdf) {
+ // unrender pages outside of the pdf by replacing them with empty stand-in divs
+ for (let i = 0; i < this.props.pdf.numPages; i++) {
+ if (i < startIndex || i > endIndex) {
+ this.getPlaceholderPage(i);
+ } else {
+ if (this.props.parent.props.active()) {
+ this.getRenderedPage(i);
+ } else {
+ this.getPageImage(i);
+ }
}
- this._isPage[i] = false;
}
}
+ }
- // render pages for any indices that don't already have pages (force rerender will make these render regardless)
- for (let i = startIndex; i <= endIndex; i++) {
- if (!this._isPage[i] || (this._isPage[i] && forceRender)) {
- this._visibleElements[i] = (
- <Page
- pdf={this.props.pdf}
- page={i}
- numPages={numPages}
- key={`${this.props.pdf ? this.props.pdf.fingerprint + `-page${i + 1}` : "undefined"}`}
- name={`${this.props.pdf ? this.props.pdf.fingerprint + `-page${i + 1}` : "undefined"}`}
- pageLoaded={this.pageLoaded}
- parent={this.props.parent}
- makePin={this.createPinAnnotation}
- renderAnnotations={this.renderAnnotations}
- createAnnotation={this.createAnnotation}
- sendAnnotations={this.receiveAnnotations}
- makeAnnotationDocuments={this.makeAnnotationDocument}
- receiveAnnotations={this.sendAnnotations}
- {...this.props} />
- );
- this._isPage[i] = true;
- }
+ @action
+ private renderAnnotations = (annotations: Doc[], removeOldAnnotations: boolean): void => {
+ if (removeOldAnnotations) {
+ this._annotations = annotations;
+ }
+ else {
+ this._annotations.push(...annotations);
+ this._annotations = new Array<Doc>(...this._annotations);
}
-
- this._startIndex = startIndex;
- this._endIndex = endIndex;
-
- return;
}
@action
@@ -392,7 +311,7 @@ class Viewer extends React.Component<IViewerProps> {
let pinAnno = new Doc();
pinAnno.x = x;
- pinAnno.y = y + this.getPageHeight(page);
+ pinAnno.y = y + this.getScrollFromPage(page);
pinAnno.width = pinAnno.height = PinRadius;
pinAnno.page = page;
pinAnno.target = targetDoc;
@@ -411,9 +330,7 @@ class Viewer extends React.Component<IViewerProps> {
}
// get the page index that the vertical offset passed in is on
- getIndex = (vOffset: number) => {
- // if (this._loaded) {
- let numPages = this.props.pdf ? this.props.pdf.numPages : 0;
+ getPageFromScroll = (vOffset: number) => {
let index = 0;
let currOffset = vOffset;
while (index < this._pageSizes.length && currOffset - (this._pageSizes[index] ? this._pageSizes[index].height : 792 * scale) > 0) {
@@ -421,34 +338,10 @@ class Viewer extends React.Component<IViewerProps> {
index++;
}
return index;
- // }
- return 0;
}
- /**
- * Called by the Page class when it gets rendered, initializes the lists and
- * puts a placeholder with all of the correct page sizes when all of the pages have been loaded.
- */
- @action
- pageLoaded = (index: number, page: Pdfjs.PDFPageViewport): void => {
- if (this._loaded) {
- return;
- }
- let numPages = this.props.pdf ? this.props.pdf.numPages : 0;
- this.props.loaded(page.width, page.height, numPages);
- this._pageSizes[index - 1] = { width: page.width, height: page.height };
- this._pagesLoaded++;
- if (this._pagesLoaded === numPages) {
- this._loaded = true;
- let divs = Array.from(Array(numPages).keys()).map(i => (
- <div key={`pdfviewer-placeholder-${i}`} className="pdfviewer-placeholder" style={{ width: this._pageSizes[i] ? this._pageSizes[i].width : 0, height: this._pageSizes[i] ? this._pageSizes[i].height : 0 }} />
- ));
- this._visibleElements = new Array<JSX.Element>(...divs);
- this.renderPages(this.startIndex, this.endIndex, true);
- }
- }
- getPageHeight = (index: number): number => {
+ getScrollFromPage = (index: number): number => {
let counter = 0;
if (this.props.pdf && index < this.props.pdf.numPages) {
for (let i = 0; i < index; i++) {
@@ -463,7 +356,7 @@ class Viewer extends React.Component<IViewerProps> {
createAnnotation = (div: HTMLDivElement, page: number) => {
if (this._annotationLayer.current) {
if (div.style.top) {
- div.style.top = (parseInt(div.style.top) + this.getPageHeight(page)).toString();
+ div.style.top = (parseInt(div.style.top) + this.getScrollFromPage(page)).toString();
}
this._annotationLayer.current.append(div);
let savedPage = this._savedAnnotations.getValue(page);
@@ -494,7 +387,6 @@ class Viewer extends React.Component<IViewerProps> {
}
render() {
- trace();
return (
<div ref={this.mainCont} style={{ pointerEvents: "all" }}>
<div className="viewer">