diff --git a/src/components/PdfViewer/xdpdf/CMapReaderFactory.js b/src/components/PdfViewer/xdpdf/CMapReaderFactory.js new file mode 100644 index 00000000..1db891d7 --- /dev/null +++ b/src/components/PdfViewer/xdpdf/CMapReaderFactory.js @@ -0,0 +1,21 @@ +import { CMapCompressionType } from 'pdfjs-dist/es5/build/pdf.js' + +// see https://github.com/mozilla/pdf.js/blob/628e70fbb5dea3b9066aa5c34cca70aaafef8db2/src/display/dom_utils.js#L64 + +export default function() { + + this.fetch = function(query) { + + return import('./buffer-loader!pdfjs-dist/cmaps/'+query.name+'.bcmap' /* webpackChunkName: "noprefetch-[request]" */) + .then(function(bcmap) { + + // delete require.cache[require.resolve('./buffer-loader!pdfjs-dist/cmaps/'+query.name+'.bcmap')]; + + return { + cMapData: bcmap.default, + compressionType: CMapCompressionType.BINARY, + }; + + }); + } +}; diff --git a/src/components/PdfViewer/xdpdf/annotationLayer.css b/src/components/PdfViewer/xdpdf/annotationLayer.css new file mode 100644 index 00000000..e1922f18 --- /dev/null +++ b/src/components/PdfViewer/xdpdf/annotationLayer.css @@ -0,0 +1,144 @@ +/* see https://github.com/mozilla/pdf.js/blob/55a853b6678cf3d05681ffbb521e5228e607b5d2/test/annotation_layer_test.css */ + +.annotationLayer { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} + +.annotationLayer section { + position: absolute; +} + +.annotationLayer .linkAnnotation > a { + position: absolute; + font-size: 1em; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.annotationLayer .linkAnnotation > a /* -ms-a */ { + background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0 repeat; +} + +.annotationLayer .linkAnnotation > a:hover { + opacity: 0.2; + background: #ff0; + box-shadow: 0px 2px 10px #ff0; +} + +.annotationLayer .textAnnotation img { + position: absolute; + cursor: pointer; +} + +.annotationLayer .textWidgetAnnotation input, +.annotationLayer .textWidgetAnnotation textarea, +.annotationLayer .choiceWidgetAnnotation select, +.annotationLayer .buttonWidgetAnnotation.checkBox input, +.annotationLayer .buttonWidgetAnnotation.radioButton input { + background-color: rgba(0, 54, 255, 0.13); + border: 1px solid transparent; + box-sizing: border-box; + font-size: 9px; + height: 100%; + padding: 0 3px; + vertical-align: top; + width: 100%; +} + +.annotationLayer .textWidgetAnnotation textarea { + font: message-box; + font-size: 9px; + resize: none; +} + +.annotationLayer .textWidgetAnnotation input[disabled], +.annotationLayer .textWidgetAnnotation textarea[disabled], +.annotationLayer .choiceWidgetAnnotation select[disabled], +.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled], +.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] { + background: none; + border: 1px solid transparent; + cursor: not-allowed; +} + +.annotationLayer .textWidgetAnnotation input:hover, +.annotationLayer .textWidgetAnnotation textarea:hover, +.annotationLayer .choiceWidgetAnnotation select:hover, +.annotationLayer .buttonWidgetAnnotation.checkBox input:hover, +.annotationLayer .buttonWidgetAnnotation.radioButton input:hover { + border: 1px solid #000; +} + +.annotationLayer .textWidgetAnnotation input:focus, +.annotationLayer .textWidgetAnnotation textarea:focus, +.annotationLayer .choiceWidgetAnnotation select:focus { + background: none; + border: 1px solid transparent; +} + +.annotationLayer .textWidgetAnnotation input.comb { + font-family: monospace; + padding-left: 2px; + padding-right: 0; +} + +.annotationLayer .textWidgetAnnotation input.comb:focus { + /* + * Letter spacing is placed on the right side of each character. Hence, the + * letter spacing of the last character may be placed outside the visible + * area, causing horizontal scrolling. We avoid this by extending the width + * when the element has focus and revert this when it loses focus. + */ + width: 115%; +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input, +.annotationLayer .buttonWidgetAnnotation.radioButton input { + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + appearance: none; +} + +.annotationLayer .popupWrapper { + position: absolute; + width: 20em; +} + +.annotationLayer .popup { + position: absolute; + z-index: 200; + max-width: 20em; + background-color: #FFFF99; + box-shadow: 0px 2px 5px #333; + border-radius: 2px; + padding: 0.6em; + margin-left: 5px; + cursor: pointer; + word-wrap: break-word; +} + +.annotationLayer .popup h1 { + font-size: 1em; + border-bottom: 1px solid #000000; + padding-bottom: 0.2em; +} + +.annotationLayer .popup p { + padding-top: 0.2em; +} + +.annotationLayer .highlightAnnotation, +.annotationLayer .underlineAnnotation, +.annotationLayer .squigglyAnnotation, +.annotationLayer .strikeoutAnnotation, +.annotationLayer .lineAnnotation svg line, +.annotationLayer .fileAttachmentAnnotation { + cursor: pointer; +} diff --git a/src/components/PdfViewer/xdpdf/buffer-loader.js b/src/components/PdfViewer/xdpdf/buffer-loader.js new file mode 100644 index 00000000..464c5a35 --- /dev/null +++ b/src/components/PdfViewer/xdpdf/buffer-loader.js @@ -0,0 +1,16 @@ +var loaderUtils = require('loader-utils'); + +module.exports = function(content) { + + var options = loaderUtils.getOptions(this); + + var data; + if ( content instanceof Buffer ) + data = content; + else + data = Buffer.from(content); + + return 'export default Buffer.from("'+data.toString('base64')+'", "base64")'; +} + +module.exports.raw = true; diff --git a/src/components/PdfViewer/xdpdf/componentFactory.js b/src/components/PdfViewer/xdpdf/componentFactory.js new file mode 100644 index 00000000..7ea49106 --- /dev/null +++ b/src/components/PdfViewer/xdpdf/componentFactory.js @@ -0,0 +1,111 @@ +import resizeSensor from 'vue-resize-sensor' + +export default function(pdfjsWrapper) { + + var createLoadingTask = pdfjsWrapper.createLoadingTask; + var PDFJSWrapper = pdfjsWrapper.PDFJSWrapper; + + return { + createLoadingTask: createLoadingTask, + render: function(h) { + return h('span', { + attrs: { + style: 'position: relative; display: block' + } + }, [ + h('canvas', { + attrs: { + style: 'display: inline-block; width: 100%; height: 100%; vertical-align: top', + }, + ref:'canvas' + }), + h('span', { + style: 'display: inline-block; width: 100%; height: 100%', + class: 'annotationLayer', + ref:'annotationLayer' + }), + h(resizeSensor, { + props: { + initial: true + }, + on: { + resize: this.resize + }, + }) + ]) + }, + props: { + src: { + type: [String, Object, Uint8Array], + default: '', + }, + page: { + type: Number, + default: 1, + }, + rotate: { + type: Number, + }, + }, + watch: { + src: function() { + + this.pdf.loadDocument(this.src); + }, + page: function() { + + this.pdf.loadPage(this.page, this.rotate); + }, + rotate: function() { + this.pdf.renderPage(this.rotate); + }, + }, + methods: { + resize: function(size) { + + // check if the element is attached to the dom tree || resizeSensor being destroyed + if ( this.$el.parentNode === null || (size.width === 0 && size.height === 0) ) + return; + + // on IE10- canvas height must be set + this.$refs.canvas.style.height = this.$refs.canvas.offsetWidth * (this.$refs.canvas.height / this.$refs.canvas.width) + 'px'; + // update the page when the resolution is too poor + var resolutionScale = this.pdf.getResolutionScale(); + + if ( resolutionScale < 0.85 || resolutionScale > 1.15 ) + this.pdf.renderPage(this.rotate); + + // this.$refs.annotationLayer.style.transform = 'scale('+resolutionScale+')'; + }, + print: function(dpi, pageList) { + + this.pdf.printPage(dpi, pageList); + } + }, + + // doc: mounted hook is not called during server-side rendering. + mounted: function() { + + this.pdf = new PDFJSWrapper(this.$refs.canvas, this.$refs.annotationLayer, this.$emit.bind(this)); + + this.$on('loaded', function() { + + this.pdf.loadPage(this.page, this.rotate); + }); + + this.$on('page-size', function(width, height) { + + this.$refs.canvas.style.height = this.$refs.canvas.offsetWidth * (height / width) + 'px'; + }); + + this.pdf.loadDocument(this.src); + }, + + // doc: destroyed hook is not called during server-side rendering. + destroyed: function() { + + this.pdf.destroy(); + } + } + +} diff --git a/src/components/PdfViewer/xdpdf/pdfjsWrapper.js b/src/components/PdfViewer/xdpdf/pdfjsWrapper.js new file mode 100644 index 00000000..6c8a8901 --- /dev/null +++ b/src/components/PdfViewer/xdpdf/pdfjsWrapper.js @@ -0,0 +1,389 @@ +import { PDFLinkService } from 'pdfjs-dist/es5/web/pdf_viewer'; + +var pendingOperation = Promise.resolve(); + +export default function(PDFJS) { + + function isPDFDocumentLoadingTask(obj) { + + return typeof(obj) === 'object' && obj !== null && obj.__PDFDocumentLoadingTask === true; + // or: return obj.constructor.name === 'PDFDocumentLoadingTask'; + } + + function createLoadingTask(src, options) { + + var source; + if ( typeof(src) === 'string' ) + source = { url: src }; + else if ( src instanceof Uint8Array ) + source = { data: src }; + else if ( typeof(src) === 'object' && src !== null ) + source = Object.assign({}, src); + else + throw new TypeError('invalid src type'); + + // source.verbosity = PDFJS.VerbosityLevel.INFOS; + // source.pdfBug = true; + // source.stopAtErrors = true; + + if ( options && options.withCredentials ) + source.withCredentials = options.withCredentials; + + var loadingTask = PDFJS.getDocument(source); + loadingTask.__PDFDocumentLoadingTask = true; // since PDFDocumentLoadingTask is not public + + if ( options && options.onPassword ) + loadingTask.onPassword = options.onPassword; + + if ( options && options.onProgress ) + loadingTask.onProgress = options.onProgress; + + return loadingTask; + } + + + function PDFJSWrapper(canvasElt, annotationLayerElt, emitEvent) { + + var pdfDoc = null; + var pdfPage = null; + var pdfRender = null; + var canceling = false; + + canvasElt.getContext('2d').save(); + + function clearCanvas() { + + canvasElt.getContext('2d').clearRect(0, 0, canvasElt.width, canvasElt.height); + } + + function clearAnnotations() { + + while ( annotationLayerElt.firstChild ) + annotationLayerElt.removeChild(annotationLayerElt.firstChild); + } + + this.destroy = function() { + + if ( pdfDoc === null ) + return; + + // Aborts all network requests and destroys worker. + pendingOperation = pdfDoc.destroy(); + pdfDoc = null; + } + + this.getResolutionScale = function() { + + return canvasElt.offsetWidth / canvasElt.width; + } + + this.printPage = function(dpi, pageNumberOnly) { + + if ( pdfPage === null ) + return; + + // 1in == 72pt + // 1in == 96px + var PRINT_RESOLUTION = dpi === undefined ? 300 : dpi; + var PRINT_UNITS = PRINT_RESOLUTION / 72.0; + var CSS_UNITS = 96.0 / 72.0; + + var printContainerElement = document.createElement('div'); + printContainerElement.setAttribute('id', 'print-container') + + + function removePrintContainer() { + printContainerElement.parentNode.removeChild(printContainerElement); + + } + + new Promise(function(resolve, reject) { + + printContainerElement.frameBorder = '0'; + printContainerElement.scrolling = 'no'; + printContainerElement.width = '0px;' + printContainerElement.height = '0px;' + printContainerElement.style.cssText = 'position: absolute; top: 0; left: 0'; + + window.document.body.appendChild(printContainerElement); + resolve(window) + }) + .then(function(win) { + + win.document.title = ''; + + return pdfDoc.getPage(1) + .then(function(page) { + + var viewport = page.getViewport({ scale: 1 }); + printContainerElement.appendChild(win.document.createElement('style')).textContent = + '@supports ((size:A4) and (size:1pt 1pt)) {' + + '@page { margin: 1pt; size: ' + ((viewport.width * PRINT_UNITS) / CSS_UNITS) + 'pt ' + ((viewport.height * PRINT_UNITS) / CSS_UNITS) + 'pt; }' + + '}' + + '#print-canvas { display: none }' + + '@media print {' + + 'body { margin: 0 }' + + '#print-canvas { page-break-before: avoid; page-break-after: always; page-break-inside: avoid; display: block }' + + 'body > *:not(#print-container) { display: none; }' + + '}'+ + + '@media screen {' + + 'body { margin: 0 }' + + + '}' + return win; + }) + }) + .then(function(win) { + + var allPages = []; + + for ( var pageNumber = 1; pageNumber <= pdfDoc.numPages; ++pageNumber ) { + + if ( pageNumberOnly !== undefined && pageNumberOnly.indexOf(pageNumber) === -1 ) + continue; + + allPages.push( + pdfDoc.getPage(pageNumber) + .then(function(page) { + + var viewport = page.getViewport({ scale: 1 }); + + var printCanvasElt = printContainerElement.appendChild(win.document.createElement('canvas')); + printCanvasElt.setAttribute('id', 'print-canvas') + printCanvasElt.width = (viewport.width * PRINT_UNITS); + printCanvasElt.height = (viewport.height * PRINT_UNITS); + + return page.render({ + canvasContext: printCanvasElt.getContext('2d'), + transform: [ // Additional transform, applied just before viewport transform. + PRINT_UNITS, 0, 0, + PRINT_UNITS, 0, 0 + ], + viewport: viewport, + intent: 'print' + }).promise; + }) + ); + } + + Promise.all(allPages) + .then(function() { + + win.focus(); // Required for IE + if (win.document.queryCommandSupported('print')) { + win.document.execCommand('print', false, null); + } else { + win.print(); + } + removePrintContainer(); + }) + .catch(function(err) { + removePrintContainer(); + emitEvent('error', err); + }) + }) + } + + this.renderPage = function(rotate) { + if ( pdfRender !== null ) { + + if ( canceling ) + return; + canceling = true; + pdfRender.cancel(); + return; + } + + if ( pdfPage === null ) + return; + + var pageRotate = (pdfPage.rotate === undefined ? 0 : pdfPage.rotate) + (rotate === undefined ? 0 : rotate); + + var scale = canvasElt.offsetWidth / pdfPage.getViewport({ scale: 1 }).width * (window.devicePixelRatio || 1); + var viewport = pdfPage.getViewport({ scale: scale, rotation:pageRotate }); + + emitEvent('page-size', viewport.width, viewport.height, scale); + + canvasElt.width = viewport.width; + canvasElt.height = viewport.height; + + pdfRender = pdfPage.render({ + canvasContext: canvasElt.getContext('2d'), + viewport: viewport + }); + + annotationLayerElt.style.visibility = 'hidden'; + clearAnnotations(); + + var viewer = { + scrollPageIntoView: function(params) { + emitEvent('link-clicked', params.pageNumber) + }, + }; + + var linkService = new PDFLinkService(); + linkService.setDocument(pdfDoc); + linkService.setViewer(viewer); + + pendingOperation = pendingOperation.then(function() { + + var getAnnotationsOperation = + pdfPage.getAnnotations({ intent: 'display' }) + .then(function(annotations) { + + PDFJS.AnnotationLayer.render({ + viewport: viewport.clone({ dontFlip: true }), + div: annotationLayerElt, + annotations: annotations, + page: pdfPage, + linkService: linkService, + renderInteractiveForms: false + }); + }); + + var pdfRenderOperation = + pdfRender.promise + .then(function() { + + annotationLayerElt.style.visibility = ''; + canceling = false; + pdfRender = null; + }) + .catch(function(err) { + + pdfRender = null; + if ( err instanceof PDFJS.RenderingCancelledException ) { + + canceling = false; + this.renderPage(rotate); + return; + } + emitEvent('error', err); + }.bind(this)) + + return Promise.all([getAnnotationsOperation, pdfRenderOperation]); + }.bind(this)); + } + + + this.forEachPage = function(pageCallback) { + + var numPages = pdfDoc.numPages; + + (function next(pageNum) { + + pdfDoc.getPage(pageNum) + .then(pageCallback) + .then(function() { + + if ( ++pageNum <= numPages ) + next(pageNum); + }) + })(1); + } + + + this.loadPage = function(pageNumber, rotate) { + + pdfPage = null; + + if ( pdfDoc === null ) + return; + + pendingOperation = pendingOperation.then(function() { + + return pdfDoc.getPage(pageNumber); + }) + .then(function(page) { + + pdfPage = page; + this.renderPage(rotate); + emitEvent('page-loaded', page.pageNumber); + }.bind(this)) + .catch(function(err) { + + clearCanvas(); + clearAnnotations(); + emitEvent('error', err); + }); + } + + this.loadDocument = function(src) { + + pdfDoc = null; + pdfPage = null; + + // console.log("==loadDocument==src=>",src) + emitEvent('num-pages', undefined); + + if ( !src ) { + + canvasElt.removeAttribute('width'); + canvasElt.removeAttribute('height'); + clearAnnotations(); + return; + } + + // wait for pending operation ends + pendingOperation = pendingOperation.then(function() { + + var loadingTask; + // console.log("==loadDocument==src2=>",src) + if ( isPDFDocumentLoadingTask(src) ) { + // console.log("==loadDocument==src3=>",src) + if (src&& src.destroyed ) { + + emitEvent('error', new Error('loadingTask has been destroyed')); + return + } + + loadingTask = src; + } else { + // console.log("==loadDocument==src4=>",src) + loadingTask = createLoadingTask(src, { + onPassword: function(updatePassword, reason) { + + var reasonStr; + switch (reason) { + case PDFJS.PasswordResponses.NEED_PASSWORD: + reasonStr = 'NEED_PASSWORD'; + break; + case PDFJS.PasswordResponses.INCORRECT_PASSWORD: + reasonStr = 'INCORRECT_PASSWORD'; + break; + } + emitEvent('password', updatePassword, reasonStr); + }, + onProgress: function(status) { + + var ratio = status.loaded / status.total; + emitEvent('progress', Math.min(ratio, 1)); + } + }); + } + + return loadingTask.promise; + }) + .then(function(pdf) { + // console.log("==loadDocument==pdf1=>",pdf) + pdfDoc = pdf; + emitEvent('num-pages', pdf.numPages); + emitEvent('loaded'); + }) + .catch(function(err) { + console.log("==loadDocument==err=>",err) + clearCanvas(); + clearAnnotations(); + emitEvent('error', err); + }) + } + + annotationLayerElt.style.transformOrigin = '0 0'; + } + + return { + createLoadingTask: createLoadingTask, + PDFJSWrapper: PDFJSWrapper, + } +} diff --git a/src/components/PdfViewer/xdpdf/vuePdfNoSss.vue b/src/components/PdfViewer/xdpdf/vuePdfNoSss.vue new file mode 100644 index 00000000..2ef83703 --- /dev/null +++ b/src/components/PdfViewer/xdpdf/vuePdfNoSss.vue @@ -0,0 +1,24 @@ + + \ No newline at end of file diff --git a/src/components/PdfViewer/xdpdf/vuePdfNoSssNoWorker.vue b/src/components/PdfViewer/xdpdf/vuePdfNoSssNoWorker.vue new file mode 100644 index 00000000..591b82b5 --- /dev/null +++ b/src/components/PdfViewer/xdpdf/vuePdfNoSssNoWorker.vue @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/src/components/PdfViewer/xdpdf/vuePdfSss.vue b/src/components/PdfViewer/xdpdf/vuePdfSss.vue new file mode 100644 index 00000000..c507b898 --- /dev/null +++ b/src/components/PdfViewer/xdpdf/vuePdfSss.vue @@ -0,0 +1,23 @@ + + \ No newline at end of file