diff options
author | yipstanley <stanley_yip@brown.edu> | 2019-06-06 14:13:55 -0400 |
---|---|---|
committer | yipstanley <stanley_yip@brown.edu> | 2019-06-06 14:13:55 -0400 |
commit | a37629f55ef279167a5ef2fec88dc548f36f4938 (patch) | |
tree | d347a1c892c32f017c5d147d1282bed2bc5e23e2 /src | |
parent | 727c8be8d51766f483a90edf07366b5840f5a4f6 (diff) |
commentss
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 38 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 147 |
2 files changed, 77 insertions, 108 deletions
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 4ee3ae098..de75c67cf 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -20,29 +20,6 @@ import { PDFViewer } from "../pdf/PDFViewer"; import { PdfField } from "../../../new_fields/URLField"; import { HeightSym, WidthSym } from "../../../new_fields/Doc"; -/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx - * This method renders PDF and puts all kinds of functionalities such as annotation, highlighting, - * area selection (I call it stickies), embedded ink node for directly annotating using a pen or - * mouse, and pagination. - * - * - * HOW TO USE: - * AREA selection: - * 1) Click on Area button. - * 2) click on any part of the PDF, and drag to get desired sized area shape - * 3) You can write on the area (hence the reason why it's called sticky) - * 4) to make another area, you need to click on area button AGAIN. - * - * HIGHLIGHT: (Buggy. No multiline/multidiv text highlighting for now...) - * 1) just click and drag on a text - * 2) click highlight - * 3) for annotation, just pull your cursor over to that text - * 4) another method: click on highlight first and then drag on your desired text - * 5) To make another highlight, you need to reclick on the button - * - * written by: Andrew Kim - */ - type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const PdfDocument = makeInterface(positionSchema, pageSchema); @@ -53,15 +30,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen @observable private _alt = false; @observable private _scrollY: number = 0; - getHeight = (): number => { - if (this.props.Document) { - let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; - console.log(doc); - return NumCast(doc.height); - } - return 0; - } - loaded = (nw: number, nh: number) => { if (this.props.Document) { let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document; @@ -80,11 +48,13 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen render() { trace(); + // uses mozilla pdf as default const pdfUrl = Cast(this.props.Document.data, PdfField, new PdfField(window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf")); - console.log(pdfUrl); let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); return ( - <div onScroll={this.onScroll} style={{ overflowY: "scroll", overflowX: "hidden", height: `${NumCast(this.props.Document.nativeHeight ? this.props.Document.nativeHeight : 300)}px` }} onWheel={(e: React.WheelEvent) => e.stopPropagation()} className={classname}> + <div onScroll={this.onScroll} + style={{ overflowY: "scroll", overflowX: "hidden", height: `${NumCast(this.props.Document.nativeHeight ? this.props.Document.nativeHeight : 300)}px` }} + onWheel={(e: React.WheelEvent) => e.stopPropagation()} className={classname}> <PDFViewer url={pdfUrl.url.href} loaded={this.loaded} scrollY={this._scrollY} parent={this} /> </div> ); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index a76527618..d5a0a7aa1 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,22 +1,11 @@ import { observer } from "mobx-react"; import React = require("react"); import { observable, action, runInAction, computed, IReactionDisposer, reaction } from "mobx"; -import { RouteStore } from "../../../server/RouteStore"; import * as Pdfjs from "pdfjs-dist"; -import * as htmlToImage from "html-to-image"; -import { Opt, WidthSym } from "../../../new_fields/Doc"; +import { Opt } from "../../../new_fields/Doc"; import "./PDFViewer.scss"; import "pdfjs-dist/web/pdf_viewer.css"; -import { number } from "prop-types"; -import { JSXElement } from "babel-types"; import { PDFBox } from "../nodes/PDFBox"; -import { NumCast, FieldValue, Cast } from "../../../new_fields/Types"; -import { SearchBox } from "../SearchBox"; -import { Utils } from "../../../Utils"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { DocServer } from "../../DocServer"; -import { ImageField, PdfField } from "../../../new_fields/URLField"; -var path = require("path"); interface IPDFViewerProps { url: string; @@ -25,6 +14,9 @@ interface IPDFViewerProps { parent: PDFBox; } +/** + * Wrapper that loads the PDF and cascades the pdf down + */ @observer export class PDFViewer extends React.Component<IPDFViewerProps> { @observable _pdf: Opt<Pdfjs.PDFDocumentProxy>; @@ -32,7 +24,6 @@ export class PDFViewer extends React.Component<IPDFViewerProps> { @action componentDidMount() { - // const pdfUrl = window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"; const pdfUrl = this.props.url; let promise = Pdfjs.getDocument(pdfUrl).promise; @@ -59,30 +50,35 @@ interface IViewerProps { url: string; } +/** + * Handles rendering and virtualization of the pdf + */ @observer 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 _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 _renderAsSvg = false; private _pageBuffer: number = 1; private _reactionDisposer?: IReactionDisposer; - private _widthReactionDisposer?: IReactionDisposer; - private _width: number = 0; componentDidMount = () => { let wasSelected = this.props.parent.props.isSelected(); + // reaction for when document gets (de)selected this._reactionDisposer = reaction( () => [this.props.parent.props.isSelected(), this.startIndex], () => { + // if deselected, render images in place of pdf if (wasSelected && !this.props.parent.props.isSelected()) { this.saveThumbnail(); } + // if selected, render pdf else if (!wasSelected && this.props.parent.props.isSelected()) { this.renderPages(this.startIndex, this.endIndex, true); } @@ -91,19 +87,7 @@ class Viewer extends React.Component<IViewerProps> { { fireImmediately: true } ); - // this._widthReactionDisposer = reaction( - // () => [this._docWidth], - // () => { - // if (this._width !== this._docWidth) { - // this._width = this._docWidth; - // this.renderPages(this.startIndex, this.endIndex, true); - // console.log(this._width); - // } - // }, - // { fireImmediately: true } - // ) - - let numPages = this.props.pdf ? this.props.pdf.numPages : 0; + // On load, render pdf setTimeout(() => this.renderPages(this.startIndex, this.endIndex, true), 1000); } @@ -115,13 +99,15 @@ class Viewer extends React.Component<IViewerProps> { @action saveThumbnail = () => { + // file address of the pdf const address: string = this.props.url; - console.log(address); 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 thisAddress = `${address.substring(0, address.length - ".pdf".length)}-${i + 1}.PNG`; let nWidth = this._pageSizes[i].width; let nHeight = this._pageSizes[i].height; + // replace page with image this._visibleElements[i] = <img key={thisAddress} style={{ width: `${nWidth}px`, height: `${nHeight}px` }} src={thisAddress} />; } } @@ -143,10 +129,16 @@ class Viewer extends React.Component<IViewerProps> { 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 this.renderPages(this.startIndex, this.endIndex); } } + /** + * @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; @@ -154,6 +146,7 @@ class Viewer extends React.Component<IViewerProps> { return; } + // 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 => ( <Page @@ -170,10 +163,12 @@ class Viewer extends React.Component<IViewerProps> { this._isPage.push(...arr); } + // if nothing changed, return if (startIndex === this._startIndex && endIndex === this._endIndex && !forceRender) { return; } + // 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]) { @@ -185,6 +180,7 @@ class Viewer extends React.Component<IViewerProps> { } } + // 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] || forceRender) { this._visibleElements[i] = ( @@ -207,6 +203,7 @@ class Viewer extends React.Component<IViewerProps> { return; } + // 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; @@ -221,6 +218,10 @@ class Viewer extends React.Component<IViewerProps> { 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) { @@ -244,22 +245,8 @@ class Viewer extends React.Component<IViewerProps> { } render() { - console.log(`START: ${this.startIndex}`); - console.log(`END: ${this.endIndex}`) - let numPages = this.props.pdf ? this.props.pdf.numPages : 0; return ( <div className="viewer"> - {/* {Array.from(Array(numPages).keys()).map((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} - {...this.props} - /> - ))} */} {this._visibleElements} </div> ); @@ -276,22 +263,23 @@ interface IPageProps { @observer class Page extends React.Component<IPageProps> { - @observable _state: string = "N/A"; - @observable _width: number = 0; - @observable _height: number = 0; - @observable _page: Opt<Pdfjs.PDFPageProxy>; - canvas: React.RefObject<HTMLCanvasElement>; - textLayer: React.RefObject<HTMLDivElement>; - @observable _currPage: number = this.props.page + 1; + @observable private _state: string = "N/A"; + @observable private _width: number = 0; + @observable private _height: number = 0; + @observable private _page: Opt<Pdfjs.PDFPageProxy>; + @observable private _currPage: number = this.props.page + 1; + + private _canvas: React.RefObject<HTMLCanvasElement>; + private _currentAnnotations: HTMLDivElement[] = []; + private _textLayer: React.RefObject<HTMLDivElement>; constructor(props: IPageProps) { super(props); - this.canvas = React.createRef(); - this.textLayer = React.createRef(); + this._canvas = React.createRef(); + this._textLayer = React.createRef(); } componentDidMount() { - console.log(this.props.pdf); if (this.props.pdf) { this.update(this.props.pdf); } @@ -327,8 +315,8 @@ class Page extends React.Component<IPageProps> { private renderPage = (page: Pdfjs.PDFPageProxy) => { let scale = 1; let viewport = page.getViewport(scale); - let canvas = this.canvas.current; - let textLayer = this.textLayer.current; + let canvas = this._canvas.current; + let textLayer = this._textLayer.current; if (canvas && textLayer) { let ctx = canvas.getContext("2d"); canvas.width = viewport.width; @@ -337,7 +325,9 @@ class Page extends React.Component<IPageProps> { this._height = viewport.height; this.props.pageLoaded(this._currPage, viewport); if (ctx) { + // renders the page onto the canvas context page.render({ canvasContext: ctx, viewport: viewport }) + // renders text onto the text container page.getTextContent().then((res: Pdfjs.TextContent) => { //@ts-ignore Pdfjs.renderTextLayer({ @@ -345,7 +335,6 @@ class Page extends React.Component<IPageProps> { container: textLayer, viewport: viewport }); - // textLayer._render(); }); this._page = page; @@ -358,6 +347,11 @@ class Page extends React.Component<IPageProps> { e.stopPropagation(); document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); + if (!e.ctrlKey) { + for (let anno of this._currentAnnotations) { + anno.remove(); + } + } } } @@ -367,30 +361,35 @@ class Page extends React.Component<IPageProps> { } } + startAnnotation = (e: DragEvent) => { + console.log("drag starting"); + } + + pointerDownCancel = (e: PointerEvent) => { + e.stopPropagation(); + } + onPointerUp = (e: PointerEvent) => { let sel = window.getSelection(); + // if selecting over a range of things if (sel && sel.type === "Range") { - // console.log(sel.getRangeAt(0)); - let commonContainer = sel.getRangeAt(0).commonAncestorContainer; - let startContainer = sel.getRangeAt(0).startContainer; - let endContainer = sel.getRangeAt(0).endContainer; let clientRects = sel.getRangeAt(0).getClientRects(); - console.log(sel.getRangeAt(0)); - let annoBoxes = []; - if (this.textLayer.current) { - // let transform = Utils.GetScreenTransform(this.textLayer.current); - console.log(transform); + if (this._textLayer.current) { + let boundingRect = this._textLayer.current.getBoundingClientRect(); for (let i = 0; i < clientRects.length; i++) { let rect = clientRects.item(i); if (rect) { let annoBox = document.createElement("div"); annoBox.className = "pdfViewer-annotationBox"; - annoBox.style.top = rect.top.toString(); - annoBox.style.left = rect.left.toString(); - annoBox.style.width = rect.width.toString(); - annoBox.style.height = rect.height.toString(); - // annoBox.style.transform = `scale(${1 / transform.scale}) translate(-${transform.translateX * transform.scale}px, -${transform.translateY * transform.scale}px)`; - this.textLayer.current.appendChild(annoBox); + // transforms the positions from screen onto the pdf div + annoBox.style.top = ((rect.top - boundingRect.top) * (this._textLayer.current.offsetHeight / boundingRect.height)).toString(); + annoBox.style.left = ((rect.left - boundingRect.left) * (this._textLayer.current.offsetWidth / boundingRect.width)).toString(); + annoBox.style.width = (rect.width * this._textLayer.current.offsetWidth / boundingRect.width).toString(); + annoBox.style.height = (rect.height * this._textLayer.current.offsetHeight / boundingRect.height).toString(); + annoBox.ondragstart = this.startAnnotation; + annoBox.onpointerdown = this.pointerDownCancel; + this._textLayer.current.appendChild(annoBox); + this._currentAnnotations.push(annoBox); } } } @@ -403,9 +402,9 @@ class Page extends React.Component<IPageProps> { return ( <div onPointerDown={this.onPointerDown} className={this.props.name} style={{ "width": this._width, "height": this._height }}> <div className="canvasContainer"> - <canvas ref={this.canvas} /> + <canvas ref={this._canvas} /> </div> - <div className="textlayer" ref={this.textLayer} style={{ "position": "relative", "top": `-${this._height}px`, "height": `${this._height}px` }} /> + <div className="textlayer" ref={this._textLayer} style={{ "position": "relative", "top": `-${this._height}px`, "height": `${this._height}px` }} /> </div> ); } |