Merge pull request #6988 from timvandermeij/fileattachment-annotation

Implement support for FileAttachment annotations
This commit is contained in:
Jonas Jenwald 2016-02-24 12:58:06 +01:00
commit 41efb92d3a
19 changed files with 222 additions and 34 deletions

View File

@ -88,7 +88,7 @@ var PDFViewerApplication = {
setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
this.url = url;
var title = PDFJS.getFileName(url) || url;
var title = PDFJS.getFilenameFromUrl(url) || url;
try {
title = decodeURIComponent(title);
} catch (e) {

View File

@ -343,7 +343,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;

View File

@ -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;

View File

@ -1601,4 +1601,5 @@ var ObjectLoader = (function() {
exports.Catalog = Catalog;
exports.ObjectLoader = ObjectLoader;
exports.XRef = XRef;
exports.FileSpec = FileSpec;
}));

View File

@ -32,6 +32,7 @@ var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
var AnnotationType = sharedUtil.AnnotationType;
var Util = sharedUtil.Util;
var addLinkAttributes = sharedUtil.addLinkAttributes;
var getFilenameFromUrl = sharedUtil.getFilenameFromUrl;
var warn = sharedUtil.warn;
var CustomStyle = displayDOMUtils.CustomStyle;
@ -42,6 +43,7 @@ var CustomStyle = displayDOMUtils.CustomStyle;
* @property {PDFPage} page
* @property {PageViewport} viewport
* @property {IPDFLinkService} linkService
* @property {DownloadManager} downloadManager
*/
/**
@ -83,6 +85,9 @@ AnnotationElementFactory.prototype =
case AnnotationType.STRIKEOUT:
return new StrikeOutAnnotationElement(parameters);
case AnnotationType.FILEATTACHMENT:
return new FileAttachmentAnnotationElement(parameters);
default:
return new AnnotationElement(parameters);
}
@ -101,6 +106,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();
@ -721,6 +727,76 @@ var StrikeOutAnnotationElement = (
return StrikeOutAnnotationElement;
})();
/**
* @class
* @alias FileAttachmentAnnotationElement
*/
var FileAttachmentAnnotationElement = (
function FileAttachmentAnnotationElementClosure() {
function FileAttachmentAnnotationElement(parameters) {
AnnotationElement.call(this, parameters, true);
this.filename = getFilenameFromUrl(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
@ -757,7 +833,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) {

View File

@ -284,6 +284,17 @@ var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = {
font: 'font'
};
// Gets the file name from a given URL.
function getFilenameFromUrl(url) {
var anchor = url.indexOf('#');
var query = url.indexOf('?');
var end = Math.min(
anchor > 0 ? anchor : url.length,
query > 0 ? query : url.length);
return url.substring(url.lastIndexOf('/', end) + 1, end);
}
PDFJS.getFilenameFromUrl = getFilenameFromUrl;
// Combines two URLs. The baseUrl shall be absolute URL. If the url is an
// absolute URL, it will be returned as is.
function combineUrl(baseUrl, url) {
@ -2367,6 +2378,7 @@ exports.combineUrl = combineUrl;
exports.createPromiseCapability = createPromiseCapability;
exports.deprecated = deprecated;
exports.error = error;
exports.getFilenameFromUrl = getFilenameFromUrl;
exports.getLookupTableFactory = getLookupTableFactory;
exports.info = info;
exports.isArray = isArray;

View File

@ -217,3 +217,4 @@
!annotation-strikeout.pdf
!annotation-squiggly.pdf
!annotation-highlight.pdf
!annotation-fileattachment.pdf

Binary file not shown.

View File

@ -2773,6 +2773,13 @@
"type": "eq",
"annotations": true
},
{ "id": "annotation-fileattachment",
"file": "pdfs/annotation-fileattachment.pdf",
"md5": "d20ecee4b53c81b2dd44c8715a1b4a83",
"rounds": 1,
"type": "eq",
"annotations": true
},
{ "id": "issue6108",
"file": "pdfs/issue6108.pdf",
"md5": "8961cb55149495989a80bf0487e0f076",

View File

@ -1,9 +1,25 @@
/* globals expect, it, describe, Dict, Name, Annotation, AnnotationBorderStyle,
AnnotationBorderStyleType, AnnotationFlag */
AnnotationBorderStyleType, AnnotationFlag, PDFJS, combineUrl,
waitsFor, beforeEach, afterEach, stringToBytes */
'use strict';
describe('Annotation layer', function() {
function waitsForPromiseResolved(promise, successCallback) {
var resolved = false;
promise.then(function(val) {
resolved = true;
successCallback(val);
},
function(error) {
// Shouldn't get here.
expect(error).toEqual('the promise should not have been rejected');
});
waitsFor(function() {
return resolved;
}, 20000);
}
describe('Annotation', function() {
it('should set and get flags', function() {
var dict = new Dict();
@ -172,4 +188,33 @@ describe('Annotation layer', function() {
expect(borderStyle.verticalCornerRadius).toEqual(0);
});
});
describe('FileAttachmentAnnotation', function() {
var loadingTask;
var annotations;
beforeEach(function() {
var pdfUrl = combineUrl(window.location.href,
'../pdfs/annotation-fileattachment.pdf');
loadingTask = PDFJS.getDocument(pdfUrl);
waitsForPromiseResolved(loadingTask.promise, function(pdfDocument) {
waitsForPromiseResolved(pdfDocument.getPage(1), function(pdfPage) {
waitsForPromiseResolved(pdfPage.getAnnotations(),
function (pdfAnnotations) {
annotations = pdfAnnotations;
});
});
});
});
afterEach(function() {
loadingTask.destroy();
});
it('should correctly parse a file attachment', function() {
var annotation = annotations[0];
expect(annotation.file.filename).toEqual('Test.txt');
expect(annotation.file.content).toEqual(stringToBytes('Test attachment'));
});
});
});

View File

@ -1,10 +1,25 @@
/* globals expect, it, describe, combineUrl, Dict, isDict, Name, PDFJS,
stringToPDFString, isExternalLinkTargetSet, LinkTarget,
removeNullCharacters */
removeNullCharacters, getFilenameFromUrl */
'use strict';
describe('util', function() {
describe('getFilenameFromUrl', function() {
it('should get the filename from an absolute URL', function() {
var url = 'http://server.org/filename.pdf';
var result = getFilenameFromUrl(url);
var expected = 'filename.pdf';
expect(result).toEqual(expected);
});
it('should get the filename from a relative URL', function() {
var url = '../../filename.pdf';
var result = getFilenameFromUrl(url);
var expected = 'filename.pdf';
expect(result).toEqual(expected);
});
});
describe('combineUrl', function() {
it('absolute url with protocol stays as is', function() {

View File

@ -72,6 +72,7 @@
.annotationLayer .highlightAnnotation,
.annotationLayer .underlineAnnotation,
.annotationLayer .squigglyAnnotation,
.annotationLayer .strikeoutAnnotation {
.annotationLayer .strikeoutAnnotation,
.annotationLayer .fileAttachmentAnnotation {
cursor: pointer;
}

View File

@ -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) {

View File

@ -12,7 +12,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals getFileName, PDFJS */
/* globals PDFJS */
'use strict';
@ -84,7 +84,7 @@ var PDFAttachmentView = (function PDFAttachmentViewClosure() {
for (var i = 0; i < attachmentsCount; i++) {
var item = attachments[names[i]];
var filename = getFileName(item.filename);
var filename = PDFJS.getFilenameFromUrl(item.filename);
var div = document.createElement('div');
div.className = 'attachmentsItem';
var button = document.createElement('button');

View File

@ -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';

View File

@ -15,7 +15,7 @@
/*jshint globalstrict: false */
/* globals PDFJS, PDFViewer, PDFPageView, TextLayerBuilder, PDFLinkService,
DefaultTextLayerFactory, AnnotationLayerBuilder, PDFHistory,
DefaultAnnotationLayerFactory, getFileName, ProgressBar */
DefaultAnnotationLayerFactory, DownloadManager, ProgressBar */
// Initializing PDFJS global object (if still undefined)
if (typeof PDFJS === 'undefined') {
@ -29,6 +29,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;
@ -39,6 +40,6 @@ if (typeof PDFJS === 'undefined') {
PDFJS.DefaultAnnotationLayerFactory = DefaultAnnotationLayerFactory;
PDFJS.PDFHistory = PDFHistory;
PDFJS.getFileName = getFileName;
PDFJS.DownloadManager = DownloadManager;
PDFJS.ProgressBar = ProgressBar;
}).call((typeof window === 'undefined') ? this : window);

View File

@ -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
});
},

View File

@ -23,15 +23,6 @@ var MAX_AUTO_SCALE = 1.25;
var SCROLLBAR_PADDING = 40;
var VERTICAL_PADDING = 5;
function getFileName(url) {
var anchor = url.indexOf('#');
var query = url.indexOf('?');
var end = Math.min(
anchor > 0 ? anchor : url.length,
query > 0 ? query : url.length);
return url.substring(url.lastIndexOf('/', end) + 1, end);
}
/**
* Returns scale factor for the canvas. It makes sense for the HiDPI displays.
* @return {Object} The object with horizontal (sx) and vertical (sy)

View File

@ -12,15 +12,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals PDFJS, PDFBug, FirefoxCom, Stats, ProgressBar,
DownloadManager, getFileName, getPDFFileNameFromURL,
PDFHistory, Preferences, SidebarView, ViewHistory, Stats,
PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar,
PasswordPrompt, PDFPresentationMode, PDFDocumentProperties, HandTool,
Promise, PDFLinkService, PDFOutlineView, PDFAttachmentView,
OverlayManager, PDFFindController, PDFFindBar, PDFViewer,
PDFRenderingQueue, PresentationModeState, parseQueryString,
RenderingStates, UNKNOWN_SCALE, DEFAULT_SCALE_VALUE,
/* globals PDFJS, PDFBug, FirefoxCom, Stats, ProgressBar, DownloadManager,
getPDFFileNameFromURL, PDFHistory, Preferences, SidebarView,
ViewHistory, Stats, PDFThumbnailViewer, URL, noContextMenuHandler,
SecondaryToolbar, PasswordPrompt, PDFPresentationMode,
PDFDocumentProperties, HandTool, Promise, PDFLinkService,
PDFOutlineView, PDFAttachmentView, OverlayManager,
PDFFindController, PDFFindBar, PDFViewer, PDFRenderingQueue,
PresentationModeState, parseQueryString, RenderingStates,
UNKNOWN_SCALE, DEFAULT_SCALE_VALUE,
IGNORE_CURRENT_POSITION_ON_ZOOM: true */
'use strict';
@ -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);
@ -485,7 +486,7 @@ var PDFViewerApplication = {
setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
this.url = url;
try {
this.setTitle(decodeURIComponent(getFileName(url)) || url);
this.setTitle(decodeURIComponent(PDFJS.getFilenameFromUrl(url)) || url);
} catch (e) {
// decodeURIComponent may throw URIError,
// fall back to using the unprocessed url in that case