pdf.js/src/core/annotation.js

1012 lines
31 KiB
JavaScript
Raw Normal View History

2013-03-21 17:04:44 +09:00
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define('pdfjs/core/annotation', ['exports', 'pdfjs/shared/util',
'pdfjs/core/primitives', 'pdfjs/core/stream', 'pdfjs/core/colorspace',
'pdfjs/core/obj', 'pdfjs/core/evaluator'], factory);
} else if (typeof exports !== 'undefined') {
factory(exports, require('../shared/util.js'), require('./primitives.js'),
require('./stream.js'), require('./colorspace.js'), require('./obj.js'),
require('./evaluator.js'));
} else {
factory((root.pdfjsCoreAnnotation = {}), root.pdfjsSharedUtil,
root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreColorSpace,
root.pdfjsCoreObj, root.pdfjsCoreEvaluator);
}
}(this, function (exports, sharedUtil, corePrimitives, coreStream,
coreColorSpace, coreObj, coreEvaluator) {
var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
var AnnotationFieldFlag = sharedUtil.AnnotationFieldFlag;
var AnnotationFlag = sharedUtil.AnnotationFlag;
var AnnotationType = sharedUtil.AnnotationType;
var OPS = sharedUtil.OPS;
var Util = sharedUtil.Util;
var isBool = sharedUtil.isBool;
var isString = sharedUtil.isString;
var isArray = sharedUtil.isArray;
var isInt = sharedUtil.isInt;
var isValidUrl = sharedUtil.isValidUrl;
var stringToBytes = sharedUtil.stringToBytes;
var stringToPDFString = sharedUtil.stringToPDFString;
var stringToUTF8String = sharedUtil.stringToUTF8String;
var warn = sharedUtil.warn;
var Dict = corePrimitives.Dict;
var isDict = corePrimitives.isDict;
var isName = corePrimitives.isName;
var isRef = corePrimitives.isRef;
var Stream = coreStream.Stream;
var ColorSpace = coreColorSpace.ColorSpace;
var ObjectLoader = coreObj.ObjectLoader;
var FileSpec = coreObj.FileSpec;
var OperatorList = coreEvaluator.OperatorList;
/**
* @class
* @alias AnnotationFactory
*/
function AnnotationFactory() {}
AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
/**
* @param {XRef} xref
* @param {Object} ref
* @param {string} uniquePrefix
* @param {Object} idCounters
* @returns {Annotation}
*/
create: function AnnotationFactory_create(xref, ref,
uniquePrefix, idCounters) {
var dict = xref.fetchIfRef(ref);
if (!isDict(dict)) {
return;
}
var id = isRef(ref) ? ref.toString() :
'annot_' + (uniquePrefix || '') + (++idCounters.obj);
// Determine the annotation's subtype.
var subtype = dict.get('Subtype');
subtype = isName(subtype) ? subtype.name : null;
// Return the right annotation object based on the subtype and field type.
var parameters = {
xref: xref,
dict: dict,
ref: isRef(ref) ? ref : null,
subtype: subtype,
id: id,
};
switch (subtype) {
case 'Link':
return new LinkAnnotation(parameters);
case 'Text':
return new TextAnnotation(parameters);
case 'Widget':
var fieldType = Util.getInheritableProperty(dict, 'FT');
fieldType = isName(fieldType) ? fieldType.name : null;
switch (fieldType) {
case 'Tx':
return new TextWidgetAnnotation(parameters);
}
warn('Unimplemented widget field type "' + fieldType + '", ' +
'falling back to base field type.');
return new WidgetAnnotation(parameters);
case 'Popup':
return new PopupAnnotation(parameters);
case 'Highlight':
return new HighlightAnnotation(parameters);
case 'Underline':
return new UnderlineAnnotation(parameters);
case 'Squiggly':
return new SquigglyAnnotation(parameters);
case 'StrikeOut':
return new StrikeOutAnnotation(parameters);
case 'FileAttachment':
return new FileAttachmentAnnotation(parameters);
default:
if (!subtype) {
warn('Annotation is missing the required /Subtype.');
} else {
warn('Unimplemented annotation type "' + subtype + '", ' +
'falling back to base annotation.');
}
return new Annotation(parameters);
}
}
};
2013-03-21 17:04:44 +09:00
var Annotation = (function AnnotationClosure() {
// 12.5.5: Algorithm: Appearance streams
function getTransformMatrix(rect, bbox, matrix) {
var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
var minX = bounds[0];
var minY = bounds[1];
var maxX = bounds[2];
var maxY = bounds[3];
if (minX === maxX || minY === maxY) {
// From real-life file, bbox was [0, 0, 0, 0]. In this case,
// just apply the transform for rect
return [1, 0, 0, 1, rect[0], rect[1]];
}
var xRatio = (rect[2] - rect[0]) / (maxX - minX);
var yRatio = (rect[3] - rect[1]) / (maxY - minY);
return [
xRatio,
0,
0,
yRatio,
rect[0] - minX * xRatio,
rect[1] - minY * yRatio
];
}
function getDefaultAppearance(dict) {
var appearanceState = dict.get('AP');
if (!isDict(appearanceState)) {
return;
}
var appearance;
var appearances = appearanceState.get('N');
if (isDict(appearances)) {
var as = dict.get('AS');
if (as && appearances.has(as.name)) {
appearance = appearances.get(as.name);
}
} else {
appearance = appearances;
}
return appearance;
}
function Annotation(params) {
var dict = params.dict;
this.setFlags(dict.get('F'));
this.setRectangle(dict.getArray('Rect'));
this.setColor(dict.getArray('C'));
this.setBorderStyle(dict);
2013-03-21 17:04:44 +09:00
this.appearance = getDefaultAppearance(dict);
// Expose public properties using a data object.
this.data = {};
this.data.id = params.id;
this.data.subtype = params.subtype;
this.data.annotationFlags = this.flags;
this.data.rect = this.rectangle;
this.data.color = this.color;
this.data.borderStyle = this.borderStyle;
this.data.hasAppearance = !!this.appearance;
2013-03-21 17:04:44 +09:00
}
Annotation.prototype = {
/**
* @private
*/
_hasFlag: function Annotation_hasFlag(flags, flag) {
return !!(flags & flag);
},
/**
* @private
*/
_isViewable: function Annotation_isViewable(flags) {
return !this._hasFlag(flags, AnnotationFlag.INVISIBLE) &&
!this._hasFlag(flags, AnnotationFlag.HIDDEN) &&
!this._hasFlag(flags, AnnotationFlag.NOVIEW);
},
/**
* @private
*/
_isPrintable: function AnnotationFlag_isPrintable(flags) {
return this._hasFlag(flags, AnnotationFlag.PRINT) &&
!this._hasFlag(flags, AnnotationFlag.INVISIBLE) &&
!this._hasFlag(flags, AnnotationFlag.HIDDEN);
},
/**
* @return {boolean}
*/
get viewable() {
if (this.flags === 0) {
return true;
}
return this._isViewable(this.flags);
},
/**
* @return {boolean}
*/
get printable() {
if (this.flags === 0) {
return false;
}
return this._isPrintable(this.flags);
},
/**
* Set the flags.
*
* @public
* @memberof Annotation
* @param {number} flags - Unsigned 32-bit integer specifying annotation
* characteristics
* @see {@link shared/util.js}
*/
setFlags: function Annotation_setFlags(flags) {
this.flags = (isInt(flags) && flags > 0) ? flags : 0;
},
/**
* Check if a provided flag is set.
*
* @public
* @memberof Annotation
* @param {number} flag - Hexadecimal representation for an annotation
* characteristic
* @return {boolean}
* @see {@link shared/util.js}
*/
hasFlag: function Annotation_hasFlag(flag) {
return this._hasFlag(this.flags, flag);
},
/**
* Set the rectangle.
*
* @public
* @memberof Annotation
* @param {Array} rectangle - The rectangle array with exactly four entries
*/
setRectangle: function Annotation_setRectangle(rectangle) {
if (isArray(rectangle) && rectangle.length === 4) {
this.rectangle = Util.normalizeRect(rectangle);
} else {
this.rectangle = [0, 0, 0, 0];
}
},
/**
* Set the color and take care of color space conversion.
*
* @public
* @memberof Annotation
* @param {Array} color - The color array containing either 0
* (transparent), 1 (grayscale), 3 (RGB) or
* 4 (CMYK) elements
*/
setColor: function Annotation_setColor(color) {
var rgbColor = new Uint8Array(3); // Black in RGB color space (default)
if (!isArray(color)) {
this.color = rgbColor;
return;
}
switch (color.length) {
case 0: // Transparent, which we indicate with a null value
this.color = null;
break;
case 1: // Convert grayscale to RGB
ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0);
this.color = rgbColor;
break;
case 3: // Convert RGB percentages to RGB
ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0);
this.color = rgbColor;
break;
case 4: // Convert CMYK to RGB
ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0);
this.color = rgbColor;
break;
default:
this.color = rgbColor;
break;
}
},
/**
* Set the border style (as AnnotationBorderStyle object).
*
* @public
* @memberof Annotation
* @param {Dict} borderStyle - The border style dictionary
*/
setBorderStyle: function Annotation_setBorderStyle(borderStyle) {
this.borderStyle = new AnnotationBorderStyle();
if (!isDict(borderStyle)) {
return;
}
if (borderStyle.has('BS')) {
var dict = borderStyle.get('BS');
var dictType = dict.get('Type');
if (!dictType || isName(dictType, 'Border')) {
this.borderStyle.setWidth(dict.get('W'));
this.borderStyle.setStyle(dict.get('S'));
this.borderStyle.setDashArray(dict.getArray('D'));
}
} else if (borderStyle.has('Border')) {
var array = borderStyle.getArray('Border');
if (isArray(array) && array.length >= 3) {
this.borderStyle.setHorizontalCornerRadius(array[0]);
this.borderStyle.setVerticalCornerRadius(array[1]);
this.borderStyle.setWidth(array[2]);
if (array.length === 4) { // Dash array available
this.borderStyle.setDashArray(array[3]);
}
}
} else {
// There are no border entries in the dictionary. According to the
// specification, we should draw a solid border of width 1 in that
// case, but Adobe Reader did not implement that part of the
// specification and instead draws no border at all, so we do the same.
// See also https://github.com/mozilla/pdf.js/issues/6179.
this.borderStyle.setWidth(0);
}
},
2013-03-21 17:04:44 +09:00
/**
* Prepare the annotation for working with a popup in the display layer.
*
* @private
* @memberof Annotation
* @param {Dict} dict - The annotation's data dictionary
*/
_preparePopup: function Annotation_preparePopup(dict) {
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') || '');
},
loadResources: function Annotation_loadResources(keys) {
return new Promise(function (resolve, reject) {
this.appearance.dict.getAsync('Resources').then(function (resources) {
if (!resources) {
resolve();
return;
}
var objectLoader = new ObjectLoader(resources.map,
keys,
resources.xref);
objectLoader.load().then(function() {
resolve(resources);
}, reject);
}, reject);
}.bind(this));
},
getOperatorList: function Annotation_getOperatorList(evaluator, task,
renderForms) {
2013-03-21 17:04:44 +09:00
if (!this.appearance) {
2014-05-10 10:21:15 +09:00
return Promise.resolve(new OperatorList());
2013-03-21 17:04:44 +09:00
}
var data = this.data;
var appearanceDict = this.appearance.dict;
var resourcesPromise = this.loadResources([
'ExtGState',
'ColorSpace',
'Pattern',
'Shading',
'XObject',
'Font'
// ProcSet
// Properties
]);
var bbox = appearanceDict.getArray('BBox') || [0, 0, 1, 1];
var matrix = appearanceDict.getArray('Matrix') || [1, 0, 0, 1, 0 ,0];
2013-03-21 17:04:44 +09:00
var transform = getTransformMatrix(data.rect, bbox, matrix);
2014-05-10 10:21:15 +09:00
var self = this;
return resourcesPromise.then(function(resources) {
var opList = new OperatorList();
opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
2015-10-21 10:50:32 +09:00
return evaluator.getOperatorList(self.appearance, task,
resources, opList).
2014-05-10 10:21:15 +09:00
then(function () {
opList.addOp(OPS.endAnnotation, []);
self.appearance.reset();
return opList;
});
});
2013-03-21 17:04:44 +09:00
}
};
Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
annotations, opList, partialEvaluator, task, intent, renderForms) {
var annotationPromises = [];
for (var i = 0, n = annotations.length; i < n; ++i) {
if ((intent === 'display' && annotations[i].viewable) ||
(intent === 'print' && annotations[i].printable)) {
annotationPromises.push(
annotations[i].getOperatorList(partialEvaluator, task, renderForms));
}
}
return Promise.all(annotationPromises).then(function(operatorLists) {
opList.addOp(OPS.beginAnnotations, []);
for (var i = 0, n = operatorLists.length; i < n; ++i) {
opList.addOpList(operatorLists[i]);
}
opList.addOp(OPS.endAnnotations, []);
});
};
2013-03-21 17:04:44 +09:00
return Annotation;
})();
/**
* Contains all data regarding an annotation's border style.
*
* @class
*/
var AnnotationBorderStyle = (function AnnotationBorderStyleClosure() {
/**
* @constructor
* @private
*/
function AnnotationBorderStyle() {
this.width = 1;
this.style = AnnotationBorderStyleType.SOLID;
this.dashArray = [3];
this.horizontalCornerRadius = 0;
this.verticalCornerRadius = 0;
}
AnnotationBorderStyle.prototype = {
/**
* Set the width.
*
* @public
* @memberof AnnotationBorderStyle
* @param {integer} width - The width
*/
setWidth: function AnnotationBorderStyle_setWidth(width) {
if (width === (width | 0)) {
this.width = width;
}
},
/**
* Set the style.
*
* @public
* @memberof AnnotationBorderStyle
* @param {Object} style - The style object
* @see {@link shared/util.js}
*/
setStyle: function AnnotationBorderStyle_setStyle(style) {
if (!style) {
return;
}
switch (style.name) {
case 'S':
this.style = AnnotationBorderStyleType.SOLID;
break;
case 'D':
this.style = AnnotationBorderStyleType.DASHED;
break;
case 'B':
this.style = AnnotationBorderStyleType.BEVELED;
break;
case 'I':
this.style = AnnotationBorderStyleType.INSET;
break;
case 'U':
this.style = AnnotationBorderStyleType.UNDERLINE;
break;
default:
break;
}
},
/**
* Set the dash array.
*
* @public
* @memberof AnnotationBorderStyle
* @param {Array} dashArray - The dash array with at least one element
*/
setDashArray: function AnnotationBorderStyle_setDashArray(dashArray) {
// We validate the dash array, but we do not use it because CSS does not
// allow us to change spacing of dashes. For more information, visit
// http://www.w3.org/TR/css3-background/#the-border-style.
if (isArray(dashArray) && dashArray.length > 0) {
// According to the PDF specification: the elements in a dashArray
// shall be numbers that are nonnegative and not all equal to zero.
var isValid = true;
var allZeros = true;
for (var i = 0, len = dashArray.length; i < len; i++) {
var element = dashArray[i];
var validNumber = (+element >= 0);
if (!validNumber) {
isValid = false;
break;
} else if (element > 0) {
allZeros = false;
}
}
if (isValid && !allZeros) {
this.dashArray = dashArray;
} else {
this.width = 0; // Adobe behavior when the array is invalid.
}
} else if (dashArray) {
this.width = 0; // Adobe behavior when the array is invalid.
}
},
/**
* Set the horizontal corner radius (from a Border dictionary).
*
* @public
* @memberof AnnotationBorderStyle
* @param {integer} radius - The horizontal corner radius
*/
setHorizontalCornerRadius:
function AnnotationBorderStyle_setHorizontalCornerRadius(radius) {
if (radius === (radius | 0)) {
this.horizontalCornerRadius = radius;
}
},
/**
* Set the vertical corner radius (from a Border dictionary).
*
* @public
* @memberof AnnotationBorderStyle
* @param {integer} radius - The vertical corner radius
*/
setVerticalCornerRadius:
function AnnotationBorderStyle_setVerticalCornerRadius(radius) {
if (radius === (radius | 0)) {
this.verticalCornerRadius = radius;
}
}
};
return AnnotationBorderStyle;
})();
2013-03-21 17:04:44 +09:00
var WidgetAnnotation = (function WidgetAnnotationClosure() {
function WidgetAnnotation(params) {
Annotation.call(this, params);
var dict = params.dict;
var data = this.data;
data.annotationType = AnnotationType.WIDGET;
2013-03-21 17:04:44 +09:00
data.fieldValue = stringToPDFString(
Util.getInheritableProperty(dict, 'V') || '');
data.alternativeText = stringToPDFString(dict.get('TU') || '');
data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
var fieldType = Util.getInheritableProperty(dict, 'FT');
data.fieldType = isName(fieldType) ? fieldType.name : null;
this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty;
2013-03-21 17:04:44 +09:00
data.fieldFlags = Util.getInheritableProperty(dict, 'Ff');
if (!isInt(data.fieldFlags) || data.fieldFlags < 0) {
data.fieldFlags = 0;
}
// Hide signatures because we cannot validate them.
if (data.fieldType === 'Sig') {
this.setFlags(AnnotationFlag.HIDDEN);
}
2013-03-21 17:04:44 +09:00
// Building the full field name by collecting the field and
// its ancestors 'T' data and joining them using '.'.
var fieldName = [];
var namedItem = dict;
var ref = params.ref;
while (namedItem) {
var parent = namedItem.get('Parent');
var parentRef = namedItem.getRaw('Parent');
var name = namedItem.get('T');
if (name) {
fieldName.unshift(stringToPDFString(name));
2014-10-17 01:08:24 +09:00
} else if (parent && ref) {
2013-03-21 17:04:44 +09:00
// The field name is absent, that means more than one field
// with the same name may exist. Replacing the empty name
// with the '`' plus index in the parent's 'Kids' array.
// This is not in the PDF spec but necessary to id the
// the input controls.
var kids = parent.get('Kids');
var j, jj;
for (j = 0, jj = kids.length; j < jj; j++) {
var kidRef = kids[j];
if (kidRef.num === ref.num && kidRef.gen === ref.gen) {
2013-03-21 17:04:44 +09:00
break;
}
2013-03-21 17:04:44 +09:00
}
fieldName.unshift('`' + j);
}
namedItem = parent;
ref = parentRef;
}
data.fullName = fieldName.join('.');
}
Util.inherit(WidgetAnnotation, Annotation, {
/**
* Check if a provided field flag is set.
*
* @public
* @memberof WidgetAnnotation
* @param {number} flag - Hexadecimal representation for an annotation
* field characteristic
* @return {boolean}
* @see {@link shared/util.js}
*/
hasFieldFlag: function WidgetAnnotation_hasFieldFlag(flag) {
return !!(this.data.fieldFlags & flag);
},
});
2013-03-21 17:04:44 +09:00
return WidgetAnnotation;
})();
2013-03-26 07:32:47 +09:00
var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
function TextWidgetAnnotation(params) {
WidgetAnnotation.call(this, params);
// Determine the alignment of text in the field.
var alignment = Util.getInheritableProperty(params.dict, 'Q');
if (!isInt(alignment) || alignment < 0 || alignment > 2) {
alignment = null;
}
this.data.textAlignment = alignment;
// Determine the maximum length of text in the field.
var maximumLength = Util.getInheritableProperty(params.dict, 'MaxLen');
if (!isInt(maximumLength) || maximumLength < 0) {
maximumLength = null;
}
this.data.maxLen = maximumLength;
// Process field flags for the display layer.
this.data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE);
this.data.comb = this.hasFieldFlag(AnnotationFieldFlag.COMB) &&
!this.hasFieldFlag(AnnotationFieldFlag.MULTILINE) &&
!this.hasFieldFlag(AnnotationFieldFlag.PASSWORD) &&
!this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) &&
this.data.maxLen !== null;
2013-03-26 07:32:47 +09:00
}
Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
getOperatorList:
function TextWidgetAnnotation_getOperatorList(evaluator, task,
renderForms) {
var operatorList = new OperatorList();
// Do not render form elements on the canvas when interactive forms are
// enabled. The display layer is responsible for rendering them instead.
if (renderForms) {
return Promise.resolve(operatorList);
}
if (this.appearance) {
return Annotation.prototype.getOperatorList.call(this, evaluator, task,
renderForms);
}
2013-05-29 10:16:21 +09:00
2013-03-26 07:32:47 +09:00
// Even if there is an appearance stream, ignore it. This is the
// behaviour used by Adobe Reader.
if (!this.data.defaultAppearance) {
return Promise.resolve(operatorList);
2013-03-26 07:32:47 +09:00
}
var stream = new Stream(stringToBytes(this.data.defaultAppearance));
return evaluator.getOperatorList(stream, task, this.fieldResources,
operatorList).
2014-05-10 10:21:15 +09:00
then(function () {
return operatorList;
2014-05-10 10:21:15 +09:00
});
2013-03-26 07:32:47 +09:00
}
});
return TextWidgetAnnotation;
})();
2013-03-21 17:04:44 +09:00
var TextAnnotation = (function TextAnnotationClosure() {
var DEFAULT_ICON_SIZE = 22; // px
2013-03-21 17:04:44 +09:00
function TextAnnotation(parameters) {
Annotation.call(this, parameters);
2013-03-21 17:04:44 +09:00
this.data.annotationType = AnnotationType.TEXT;
2013-03-21 17:04:44 +09:00
if (this.data.hasAppearance) {
this.data.name = 'NoIcon';
} else {
this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE;
this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE;
this.data.name = parameters.dict.has('Name') ?
parameters.dict.get('Name').name : 'Note';
}
this._preparePopup(parameters.dict);
}
2013-03-21 17:04:44 +09:00
Util.inherit(TextAnnotation, Annotation, {});
2013-03-21 17:04:44 +09:00
return TextAnnotation;
})();
var LinkAnnotation = (function LinkAnnotationClosure() {
function LinkAnnotation(params) {
Annotation.call(this, params);
2013-03-21 17:04:44 +09:00
var dict = params.dict;
var data = this.data;
data.annotationType = AnnotationType.LINK;
2013-03-21 17:04:44 +09:00
var action = dict.get('A'), url, dest;
if (action && isDict(action)) {
2013-03-21 17:04:44 +09:00
var linkType = action.get('S').name;
switch (linkType) {
case 'URI':
url = action.get('URI');
if (isName(url)) {
// Some bad PDFs do not put parentheses around relative URLs.
url = '/' + url.name;
} else if (url) {
url = addDefaultProtocolToUrl(url);
}
// TODO: pdf spec mentions urls can be relative to a Base
// entry in the dictionary.
break;
2013-03-21 17:04:44 +09:00
case 'GoTo':
dest = action.get('D');
break;
case 'GoToR':
var urlDict = action.get('F');
if (isDict(urlDict)) {
// We assume that we found a FileSpec dictionary
// and fetch the URL without checking any further.
url = urlDict.get('F') || null;
} else if (isString(urlDict)) {
url = urlDict;
}
// NOTE: the destination is relative to the *remote* document.
var remoteDest = action.get('D');
if (remoteDest) {
if (isName(remoteDest)) {
remoteDest = remoteDest.name;
}
[api-minor] Let `LinkAnnotation`/`PDFLinkService_getDestinationHash` return a stringified version of the destination array for explicit destinations Currently for explicit destinations, compared to named destinations, we manually try to build a hash that often times is a quite poor representation of the *actual* destination. (Currently this only, kind of, works for `\XYZ` destinations.) For PDF files using explicit destinations, this can make it difficult/impossible to obtain a link to a specific section of the document through the URL. Note that in practice most PDF files, especially newer ones, use named destinations and these are thus unnaffected by this patch. This patch also fixes an existing issue in `PDFLinkService_getDestinationHash`, where a named destination consisting of only a number would not be handled correctly. With the added, and already existing, type checks in place for destinations, I really don't think that this patch exposes any "sensitive" internal destination code not already accessible through normal hash parameters. *Please note:* Just trying to improve the algorithm that generates the hash is unfortunately not possible in general, since there are a number of cases where it will simply never work well. - First of all, note that `getDestinationHash` currently relies on the `_pagesRefCache`, hence it's possible that the hash returned is empty during e.g. ranged/streamed loading of a PDF file. - Second of all, the currently computed hash is actually dependent on the document rotation. With named destinations, the fetched internal destination array is rotational invariant (as it should be), but this will not hold in general for the hash. We can easily avoid this issue by using a stringified destination array. - Third of all, note that according to the PDF specification[1], `GoToR` destinations may actually contain explicit destination arrays. Since we cannot really construct a hash in `annotation.js`, we currently have no good way to support those. Even though this case seems *very* rare in practice (I've not actually seen such a PDF file), it's in the specification, and this patch allows us to support that for "free". --- [1] http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf#G11.1951685
2016-05-15 19:12:47 +09:00
if (isString(url)) {
var baseUrl = url.split('#')[0];
[api-minor] Let `LinkAnnotation`/`PDFLinkService_getDestinationHash` return a stringified version of the destination array for explicit destinations Currently for explicit destinations, compared to named destinations, we manually try to build a hash that often times is a quite poor representation of the *actual* destination. (Currently this only, kind of, works for `\XYZ` destinations.) For PDF files using explicit destinations, this can make it difficult/impossible to obtain a link to a specific section of the document through the URL. Note that in practice most PDF files, especially newer ones, use named destinations and these are thus unnaffected by this patch. This patch also fixes an existing issue in `PDFLinkService_getDestinationHash`, where a named destination consisting of only a number would not be handled correctly. With the added, and already existing, type checks in place for destinations, I really don't think that this patch exposes any "sensitive" internal destination code not already accessible through normal hash parameters. *Please note:* Just trying to improve the algorithm that generates the hash is unfortunately not possible in general, since there are a number of cases where it will simply never work well. - First of all, note that `getDestinationHash` currently relies on the `_pagesRefCache`, hence it's possible that the hash returned is empty during e.g. ranged/streamed loading of a PDF file. - Second of all, the currently computed hash is actually dependent on the document rotation. With named destinations, the fetched internal destination array is rotational invariant (as it should be), but this will not hold in general for the hash. We can easily avoid this issue by using a stringified destination array. - Third of all, note that according to the PDF specification[1], `GoToR` destinations may actually contain explicit destination arrays. Since we cannot really construct a hash in `annotation.js`, we currently have no good way to support those. Even though this case seems *very* rare in practice (I've not actually seen such a PDF file), it's in the specification, and this patch allows us to support that for "free". --- [1] http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf#G11.1951685
2016-05-15 19:12:47 +09:00
if (isString(remoteDest)) {
// In practice, a named destination may contain only a number.
// If that happens, use the '#nameddest=' form to avoid the link
// redirecting to a page, instead of the correct destination.
url = baseUrl + '#' +
(/^\d+$/.test(remoteDest) ? 'nameddest=' : '') + remoteDest;
} else if (isArray(remoteDest)) {
url = baseUrl + '#' + JSON.stringify(remoteDest);
}
}
}
// The 'NewWindow' property, equal to `LinkTarget.BLANK`.
var newWindow = action.get('NewWindow');
if (isBool(newWindow)) {
data.newWindow = newWindow;
}
break;
case 'Named':
data.action = action.get('N').name;
break;
default:
warn('unrecognized link type: ' + linkType);
}
} else if (dict.has('Dest')) { // Simple destination link.
dest = dict.get('Dest');
}
if (url) {
if (isValidUrl(url, /* allowRelative = */ false)) {
data.url = tryConvertUrlEncoding(url);
2013-03-21 17:04:44 +09:00
}
}
if (dest) {
2013-03-21 17:04:44 +09:00
data.dest = isName(dest) ? dest.name : dest;
}
}
// Lets URLs beginning with 'www.' default to using the 'http://' protocol.
function addDefaultProtocolToUrl(url) {
if (isString(url) && url.indexOf('www.') === 0) {
return ('http://' + url);
}
return url;
}
2013-03-21 17:04:44 +09:00
function tryConvertUrlEncoding(url) {
// According to ISO 32000-1:2008, section 12.6.4.7, URIs should be encoded
// in 7-bit ASCII. Some bad PDFs use UTF-8 encoding, see Bugzilla 1122280.
try {
return stringToUTF8String(url);
} catch (e) {
return url;
}
}
Util.inherit(LinkAnnotation, Annotation, {});
2013-03-21 17:04:44 +09:00
return LinkAnnotation;
})();
var PopupAnnotation = (function PopupAnnotationClosure() {
function PopupAnnotation(parameters) {
Annotation.call(this, parameters);
this.data.annotationType = AnnotationType.POPUP;
var dict = parameters.dict;
var parentItem = dict.get('Parent');
if (!parentItem) {
warn('Popup annotation has a missing or invalid parent annotation.');
return;
}
this.data.parentId = dict.getRaw('Parent').toString();
this.data.title = stringToPDFString(parentItem.get('T') || '');
this.data.contents = stringToPDFString(parentItem.get('Contents') || '');
if (!parentItem.has('C')) {
// Fall back to the default background color.
this.data.color = null;
} else {
this.setColor(parentItem.getArray('C'));
this.data.color = this.color;
}
// If the Popup annotation is not viewable, but the parent annotation is,
// that is most likely a bug. Fallback to inherit the flags from the parent
// annotation (this is consistent with the behaviour in Adobe Reader).
if (!this.viewable) {
var parentFlags = parentItem.get('F');
if (this._isViewable(parentFlags)) {
this.setFlags(parentFlags);
}
}
}
Util.inherit(PopupAnnotation, Annotation, {});
return PopupAnnotation;
})();
var HighlightAnnotation = (function HighlightAnnotationClosure() {
function HighlightAnnotation(parameters) {
Annotation.call(this, parameters);
this.data.annotationType = AnnotationType.HIGHLIGHT;
this._preparePopup(parameters.dict);
// PDF viewers completely ignore any border styles.
this.data.borderStyle.setWidth(0);
}
Util.inherit(HighlightAnnotation, Annotation, {});
return HighlightAnnotation;
})();
var UnderlineAnnotation = (function UnderlineAnnotationClosure() {
function UnderlineAnnotation(parameters) {
Annotation.call(this, parameters);
this.data.annotationType = AnnotationType.UNDERLINE;
this._preparePopup(parameters.dict);
// PDF viewers completely ignore any border styles.
this.data.borderStyle.setWidth(0);
}
Util.inherit(UnderlineAnnotation, Annotation, {});
return UnderlineAnnotation;
})();
var SquigglyAnnotation = (function SquigglyAnnotationClosure() {
function SquigglyAnnotation(parameters) {
Annotation.call(this, parameters);
this.data.annotationType = AnnotationType.SQUIGGLY;
this._preparePopup(parameters.dict);
// PDF viewers completely ignore any border styles.
this.data.borderStyle.setWidth(0);
}
Util.inherit(SquigglyAnnotation, Annotation, {});
return SquigglyAnnotation;
})();
var StrikeOutAnnotation = (function StrikeOutAnnotationClosure() {
function StrikeOutAnnotation(parameters) {
Annotation.call(this, parameters);
this.data.annotationType = AnnotationType.STRIKEOUT;
this._preparePopup(parameters.dict);
// PDF viewers completely ignore any border styles.
this.data.borderStyle.setWidth(0);
}
Util.inherit(StrikeOutAnnotation, Annotation, {});
return StrikeOutAnnotation;
})();
var FileAttachmentAnnotation = (function FileAttachmentAnnotationClosure() {
function FileAttachmentAnnotation(parameters) {
Annotation.call(this, parameters);
var file = new FileSpec(parameters.dict.get('FS'), parameters.xref);
this.data.annotationType = AnnotationType.FILEATTACHMENT;
this.data.file = file.serializable;
this._preparePopup(parameters.dict);
}
Util.inherit(FileAttachmentAnnotation, Annotation, {});
return FileAttachmentAnnotation;
})();
exports.Annotation = Annotation;
exports.AnnotationBorderStyle = AnnotationBorderStyle;
exports.AnnotationFactory = AnnotationFactory;
}));