From 6a33dfd13a20afb1bc1ed5dc9f96740d4e9a2312 Mon Sep 17 00:00:00 2001 From: Tim van der Meij Date: Sun, 14 Feb 2016 20:44:00 +0100 Subject: [PATCH] Implement support for FileAttachment annotations --- .../firefox/content/PdfStreamConverter.jsm | 2 +- src/core/annotation.js | 30 +++++++ src/core/obj.js | 1 + src/display/annotation_layer.js | 78 ++++++++++++++++++- web/annotation_layer_builder.css | 3 +- web/annotation_layer_builder.js | 5 +- web/pdf_page_view.js | 3 +- web/pdf_viewer.component.js | 5 +- web/pdf_viewer.js | 6 +- web/viewer.js | 3 +- 10 files changed, 127 insertions(+), 9 deletions(-) diff --git a/extensions/firefox/content/PdfStreamConverter.jsm b/extensions/firefox/content/PdfStreamConverter.jsm index d97344cf5..b3f7a9113 100644 --- a/extensions/firefox/content/PdfStreamConverter.jsm +++ b/extensions/firefox/content/PdfStreamConverter.jsm @@ -337,7 +337,7 @@ ChromeActions.prototype = { try { // contentDisposition/contentDispositionFilename is readonly before FF18 channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT; - if (self.contentDispositionFilename) { + if (self.contentDispositionFilename && !data.isAttachment) { channel.contentDispositionFilename = self.contentDispositionFilename; } else { channel.contentDispositionFilename = filename; diff --git a/src/core/annotation.js b/src/core/annotation.js index 46a59c916..a060094b0 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -50,6 +50,7 @@ var isName = corePrimitives.isName; var Stream = coreStream.Stream; var ColorSpace = coreColorSpace.ColorSpace; var ObjectLoader = coreObj.ObjectLoader; +var FileSpec = coreObj.FileSpec; var OperatorList = coreEvaluator.OperatorList; /** @@ -75,6 +76,7 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ { // Return the right annotation object based on the subtype and field type. var parameters = { + xref: xref, dict: dict, ref: ref }; @@ -108,6 +110,9 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ { case 'StrikeOut': return new StrikeOutAnnotation(parameters); + case 'FileAttachment': + return new FileAttachmentAnnotation(parameters); + default: warn('Unimplemented annotation type "' + subtype + '", ' + 'falling back to base annotation'); @@ -852,6 +857,31 @@ var StrikeOutAnnotation = (function StrikeOutAnnotationClosure() { return StrikeOutAnnotation; })(); +var FileAttachmentAnnotation = (function FileAttachmentAnnotationClosure() { + function FileAttachmentAnnotation(parameters) { + Annotation.call(this, parameters); + + var dict = parameters.dict; + var file = new FileSpec(dict.get('FS'), parameters.xref); + + this.data.annotationType = AnnotationType.FILEATTACHMENT; + this.data.file = file.serializable; + + if (!dict.has('C')) { + // Fall back to the default background color. + this.data.color = null; + } + + this.data.hasPopup = dict.has('Popup'); + this.data.title = stringToPDFString(dict.get('T') || ''); + this.data.contents = stringToPDFString(dict.get('Contents') || ''); + } + + Util.inherit(FileAttachmentAnnotation, Annotation, {}); + + return FileAttachmentAnnotation; +})(); + exports.Annotation = Annotation; exports.AnnotationBorderStyle = AnnotationBorderStyle; exports.AnnotationFactory = AnnotationFactory; diff --git a/src/core/obj.js b/src/core/obj.js index 37102c9b5..c7735ed87 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -1591,4 +1591,5 @@ var ObjectLoader = (function() { exports.Catalog = Catalog; exports.ObjectLoader = ObjectLoader; exports.XRef = XRef; +exports.FileSpec = FileSpec; })); diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 722cf17ea..7eef28360 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -42,6 +42,7 @@ var CustomStyle = displayDOMUtils.CustomStyle; * @property {PDFPage} page * @property {PageViewport} viewport * @property {IPDFLinkService} linkService + * @property {DownloadManager} downloadManager */ /** @@ -83,6 +84,9 @@ AnnotationElementFactory.prototype = case AnnotationType.STRIKEOUT: return new StrikeOutAnnotationElement(parameters); + case AnnotationType.FILEATTACHMENT: + return new FileAttachmentAnnotationElement(parameters); + default: return new AnnotationElement(parameters); } @@ -101,6 +105,7 @@ var AnnotationElement = (function AnnotationElementClosure() { this.page = parameters.page; this.viewport = parameters.viewport; this.linkService = parameters.linkService; + this.downloadManager = parameters.downloadManager; if (isRenderable) { this.container = this._createContainer(); @@ -717,6 +722,76 @@ var StrikeOutAnnotationElement = ( return StrikeOutAnnotationElement; })(); +/** + * @class + * @alias FileAttachmentAnnotationElement + */ +var FileAttachmentAnnotationElement = ( + function FileAttachmentAnnotationElementClosure() { + function FileAttachmentAnnotationElement(parameters) { + AnnotationElement.call(this, parameters, true); + + this.filename = parameters.data.file.filename; + this.content = parameters.data.file.content; + } + + Util.inherit(FileAttachmentAnnotationElement, AnnotationElement, { + /** + * Render the file attachment annotation's HTML element in the empty + * container. + * + * @public + * @memberof FileAttachmentAnnotationElement + * @returns {HTMLSectionElement} + */ + render: function FileAttachmentAnnotationElement_render() { + this.container.className = 'fileAttachmentAnnotation'; + + var trigger = document.createElement('div'); + trigger.style.height = this.container.style.height; + trigger.style.width = this.container.style.width; + trigger.addEventListener('dblclick', this._download.bind(this)); + + if (!this.data.hasPopup && (this.data.title || this.data.contents)) { + var popupElement = new PopupElement({ + container: this.container, + trigger: trigger, + color: this.data.color, + title: this.data.title, + contents: this.data.contents, + hideWrapper: true + }); + var popup = popupElement.render(); + + // Position the popup next to the FileAttachment annotation's + // container. + popup.style.left = this.container.style.width; + + this.container.appendChild(popup); + } + + this.container.appendChild(trigger); + return this.container; + }, + + /** + * Download the file attachment associated with this annotation. + * + * @private + * @memberof FileAttachmentAnnotationElement + */ + _download: function FileAttachmentAnnotationElement_download() { + if (!this.downloadManager) { + warn('Download cannot be started due to unavailable download manager'); + return; + } + this.downloadManager.downloadData(this.content, this.filename, ''); + } + }); + + return FileAttachmentAnnotationElement; +})(); + /** * @typedef {Object} AnnotationLayerParameters * @property {PageViewport} viewport @@ -753,7 +828,8 @@ var AnnotationLayer = (function AnnotationLayerClosure() { layer: parameters.div, page: parameters.page, viewport: parameters.viewport, - linkService: parameters.linkService + linkService: parameters.linkService, + downloadManager: parameters.downloadManager }; var element = annotationElementFactory.create(properties); if (element.isRenderable) { diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index 3c6b4e43d..03494430a 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -72,6 +72,7 @@ .annotationLayer .highlightAnnotation, .annotationLayer .underlineAnnotation, .annotationLayer .squigglyAnnotation, -.annotationLayer .strikeoutAnnotation { +.annotationLayer .strikeoutAnnotation, +.annotationLayer .fileAttachmentAnnotation { cursor: pointer; } diff --git a/web/annotation_layer_builder.js b/web/annotation_layer_builder.js index 3f5a32900..6d985b8cc 100644 --- a/web/annotation_layer_builder.js +++ b/web/annotation_layer_builder.js @@ -21,6 +21,7 @@ * @property {HTMLDivElement} pageDiv * @property {PDFPage} pdfPage * @property {IPDFLinkService} linkService + * @property {DownloadManager} downloadManager */ /** @@ -35,6 +36,7 @@ var AnnotationLayerBuilder = (function AnnotationLayerBuilderClosure() { this.pageDiv = options.pageDiv; this.pdfPage = options.pdfPage; this.linkService = options.linkService; + this.downloadManager = options.downloadManager; this.div = null; } @@ -59,7 +61,8 @@ var AnnotationLayerBuilder = (function AnnotationLayerBuilderClosure() { div: self.div, annotations: annotations, page: self.pdfPage, - linkService: self.linkService + linkService: self.linkService, + downloadManager: self.downloadManager }; if (self.div) { diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 539113a40..f03a13512 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -13,8 +13,7 @@ * limitations under the License. */ /* globals RenderingStates, PDFJS, DEFAULT_SCALE, CSS_UNITS, getOutputScale, - TextLayerBuilder, AnnotationLayerBuilder, Promise, - approximateFraction, roundToDivide */ + TextLayerBuilder, Promise, approximateFraction, roundToDivide */ 'use strict'; diff --git a/web/pdf_viewer.component.js b/web/pdf_viewer.component.js index 99b450ee8..9980b7ebf 100644 --- a/web/pdf_viewer.component.js +++ b/web/pdf_viewer.component.js @@ -15,7 +15,8 @@ /*jshint globalstrict: false */ /* globals PDFJS, PDFViewer, PDFPageView, TextLayerBuilder, PDFLinkService, DefaultTextLayerFactory, AnnotationLayerBuilder, PDFHistory, - DefaultAnnotationLayerFactory, getFileName, ProgressBar */ + DefaultAnnotationLayerFactory, getFileName, DownloadManager, + ProgressBar */ // Initializing PDFJS global object (if still undefined) if (typeof PDFJS === 'undefined') { @@ -29,6 +30,7 @@ if (typeof PDFJS === 'undefined') { //#include pdf_link_service.js //#include pdf_viewer.js //#include pdf_history.js +//#include download_manager.js PDFJS.PDFViewer = PDFViewer; PDFJS.PDFPageView = PDFPageView; @@ -40,5 +42,6 @@ if (typeof PDFJS === 'undefined') { PDFJS.PDFHistory = PDFHistory; PDFJS.getFileName = getFileName; + PDFJS.DownloadManager = DownloadManager; PDFJS.ProgressBar = ProgressBar; }).call((typeof window === 'undefined') ? this : window); diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 2584a0287..baf28551d 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -40,6 +40,8 @@ var DEFAULT_CACHE_SIZE = 10; * @property {HTMLDivElement} container - The container for the viewer element. * @property {HTMLDivElement} viewer - (optional) The viewer element. * @property {IPDFLinkService} linkService - The navigation/linking service. + * @property {DownloadManager} downloadManager - (optional) The download + * manager component. * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering * queue object. * @property {boolean} removePageBorders - (optional) Removes the border shadow @@ -92,6 +94,7 @@ var PDFViewer = (function pdfViewer() { this.container = options.container; this.viewer = options.viewer || options.container.firstElementChild; this.linkService = options.linkService || new SimpleLinkService(); + this.downloadManager = options.downloadManager || null; this.removePageBorders = options.removePageBorders || false; this.defaultRenderingQueue = !options.renderingQueue; @@ -757,7 +760,8 @@ var PDFViewer = (function pdfViewer() { return new AnnotationLayerBuilder({ pageDiv: pageDiv, pdfPage: pdfPage, - linkService: this.linkService + linkService: this.linkService, + downloadManager: this.downloadManager }); }, diff --git a/web/viewer.js b/web/viewer.js index dc748025f..8e4441f2e 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -134,7 +134,8 @@ var PDFViewerApplication = { container: container, viewer: viewer, renderingQueue: pdfRenderingQueue, - linkService: pdfLinkService + linkService: pdfLinkService, + downloadManager: new DownloadManager() }); pdfRenderingQueue.setViewer(this.pdfViewer); pdfLinkService.setViewer(this.pdfViewer);