Browse Source

Merge pull request #6988 from timvandermeij/fileattachment-annotation

Implement support for FileAttachment annotations
Jonas Jenwald 9 years ago
parent
commit
41efb92d3a
  1. 2
      extensions/b2g/viewer.js
  2. 2
      extensions/firefox/content/PdfStreamConverter.jsm
  3. 30
      src/core/annotation.js
  4. 1
      src/core/obj.js
  5. 79
      src/display/annotation_layer.js
  6. 12
      src/shared/util.js
  7. 1
      test/pdfs/.gitignore
  8. BIN
      test/pdfs/annotation-fileattachment.pdf
  9. 7
      test/test_manifest.json
  10. 47
      test/unit/annotation_layer_spec.js
  11. 17
      test/unit/util_spec.js
  12. 3
      web/annotation_layer_builder.css
  13. 5
      web/annotation_layer_builder.js
  14. 4
      web/pdf_attachment_view.js
  15. 3
      web/pdf_page_view.js
  16. 5
      web/pdf_viewer.component.js
  17. 6
      web/pdf_viewer.js
  18. 9
      web/ui_utils.js
  19. 23
      web/viewer.js

2
extensions/b2g/viewer.js

@ -88,7 +88,7 @@ var PDFViewerApplication = { @@ -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) {

2
extensions/firefox/content/PdfStreamConverter.jsm

@ -343,7 +343,7 @@ ChromeActions.prototype = { @@ -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;

30
src/core/annotation.js

@ -50,6 +50,7 @@ var isName = corePrimitives.isName; @@ -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 */ { @@ -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 */ { @@ -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() { @@ -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;

1
src/core/obj.js

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

79
src/display/annotation_layer.js

@ -32,6 +32,7 @@ var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType; @@ -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; @@ -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 = @@ -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() { @@ -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 = ( @@ -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() { @@ -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) {

12
src/shared/util.js

@ -284,6 +284,17 @@ var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = { @@ -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; @@ -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;

1
test/pdfs/.gitignore vendored

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

BIN
test/pdfs/annotation-fileattachment.pdf

Binary file not shown.

7
test/test_manifest.json

@ -2773,6 +2773,13 @@ @@ -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",

47
test/unit/annotation_layer_spec.js

@ -1,9 +1,25 @@ @@ -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() { @@ -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'));
});
});
});

17
test/unit/util_spec.js

@ -1,10 +1,25 @@ @@ -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() {

3
web/annotation_layer_builder.css

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

5
web/annotation_layer_builder.js

@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
* @property {HTMLDivElement} pageDiv
* @property {PDFPage} pdfPage
* @property {IPDFLinkService} linkService
* @property {DownloadManager} downloadManager
*/
/**
@ -35,6 +36,7 @@ var AnnotationLayerBuilder = (function AnnotationLayerBuilderClosure() { @@ -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() { @@ -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) {

4
web/pdf_attachment_view.js

@ -12,7 +12,7 @@ @@ -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() { @@ -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');

3
web/pdf_page_view.js

@ -13,8 +13,7 @@ @@ -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';

5
web/pdf_viewer.component.js

@ -15,7 +15,7 @@ @@ -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') { @@ -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') { @@ -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);

6
web/pdf_viewer.js

@ -40,6 +40,8 @@ var DEFAULT_CACHE_SIZE = 10; @@ -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() { @@ -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() { @@ -757,7 +760,8 @@ var PDFViewer = (function pdfViewer() {
return new AnnotationLayerBuilder({
pageDiv: pageDiv,
pdfPage: pdfPage,
linkService: this.linkService
linkService: this.linkService,
downloadManager: this.downloadManager
});
},

9
web/ui_utils.js

@ -23,15 +23,6 @@ var MAX_AUTO_SCALE = 1.25; @@ -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)

23
web/viewer.js

@ -12,15 +12,15 @@ @@ -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 = { @@ -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 = { @@ -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

Loading…
Cancel
Save