Added an "InteractiveAnnotation" class to homogenize the annotations' structure (highlighting) and user interactions (for now, used for text and link annotations). Text annotations: The appearance (AP) has priority over the icon (Name). The popup extends horizontally (up to a limit) as well as vertically. Reduced the title's font size. The annotation's color (C) is used to color the popup's background. On top of the mouseover show/hide behavior, a click on the icon will lock the annotation open (for mobile purposes). It can be closed with another click on either the icon or the popup. An annotation printing is conditioned by its "print" bit Unsupported annotations are not displayed at all.
854 lines
25 KiB
JavaScript
854 lines
25 KiB
JavaScript
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
|
/* 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.
|
|
*/
|
|
/* globals Util, isDict, isName, stringToPDFString, warn, Dict, Stream,
|
|
stringToBytes, PDFJS, isWorker, assert, NotImplementedException,
|
|
Promise, isArray, ObjectLoader, isValidUrl, OperatorList, OPS,
|
|
LegacyPromise */
|
|
|
|
'use strict';
|
|
|
|
var HIGHLIGHT_OFFSET = 4; // px
|
|
var SUPPORTED_TYPES = ['Link', 'Text', 'Widget'];
|
|
|
|
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) {
|
|
if (params.data) {
|
|
this.data = params.data;
|
|
return;
|
|
}
|
|
|
|
var dict = params.dict;
|
|
var data = this.data = {};
|
|
|
|
data.subtype = dict.get('Subtype').name;
|
|
var rect = dict.get('Rect');
|
|
data.rect = Util.normalizeRect(rect);
|
|
data.annotationFlags = dict.get('F');
|
|
|
|
var color = dict.get('C');
|
|
if (isArray(color) && color.length === 3) {
|
|
// TODO(mack): currently only supporting rgb; need support different
|
|
// colorspaces
|
|
data.color = color;
|
|
} else {
|
|
data.color = [0, 0, 0];
|
|
}
|
|
|
|
// Some types of annotations have border style dict which has more
|
|
// info than the border array
|
|
if (dict.has('BS')) {
|
|
var borderStyle = dict.get('BS');
|
|
data.borderWidth = borderStyle.has('W') ? borderStyle.get('W') : 1;
|
|
} else {
|
|
var borderArray = dict.get('Border') || [0, 0, 1];
|
|
data.borderWidth = borderArray[2] || 0;
|
|
|
|
// TODO: implement proper support for annotations with line dash patterns.
|
|
var dashArray = borderArray[3];
|
|
if (data.borderWidth > 0 && dashArray && isArray(dashArray)) {
|
|
var dashArrayLength = dashArray.length;
|
|
if (dashArrayLength > 0) {
|
|
// According to the PDF specification: the elements in a dashArray
|
|
// shall be numbers that are nonnegative and not all equal to zero.
|
|
var isInvalid = false;
|
|
var numPositive = 0;
|
|
for (var i = 0; i < dashArrayLength; i++) {
|
|
if (!(+dashArray[i] >= 0)) {
|
|
isInvalid = true;
|
|
break;
|
|
} else if (dashArray[i] > 0) {
|
|
numPositive++;
|
|
}
|
|
}
|
|
if (isInvalid || numPositive === 0) {
|
|
data.borderWidth = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.appearance = getDefaultAppearance(dict);
|
|
data.hasAppearance = !!this.appearance;
|
|
}
|
|
|
|
Annotation.prototype = {
|
|
|
|
getData: function Annotation_getData() {
|
|
return this.data;
|
|
},
|
|
|
|
hasHtml: function Annotation_hasHtml() {
|
|
return false;
|
|
},
|
|
|
|
getHtmlElement: function Annotation_getHtmlElement(commonObjs) {
|
|
throw new NotImplementedException(
|
|
'getHtmlElement() should be implemented in subclass');
|
|
},
|
|
|
|
// TODO(mack): Remove this, it's not really that helpful.
|
|
getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect,
|
|
borderWidth) {
|
|
assert(!isWorker,
|
|
'getEmptyContainer() should be called from main thread');
|
|
|
|
var bWidth = borderWidth || 0;
|
|
|
|
rect = rect || this.data.rect;
|
|
var element = document.createElement(tagName);
|
|
element.style.borderWidth = bWidth + 'px';
|
|
var width = rect[2] - rect[0] - 2 * bWidth;
|
|
var height = rect[3] - rect[1] - 2 * bWidth;
|
|
element.style.width = width + 'px';
|
|
element.style.height = height + 'px';
|
|
return element;
|
|
},
|
|
|
|
isInvisible: function Annotation_isInvisible() {
|
|
var data = this.data;
|
|
if (data && SUPPORTED_TYPES.indexOf(data.subtype) !== -1) {
|
|
return false;
|
|
} else {
|
|
return !!(data &&
|
|
data.annotationFlags && // Default: not invisible
|
|
data.annotationFlags & 0x1); // Invisible
|
|
}
|
|
},
|
|
|
|
isViewable: function Annotation_isViewable() {
|
|
var data = this.data;
|
|
return !!(!this.isInvisible() &&
|
|
data &&
|
|
(!data.annotationFlags ||
|
|
!(data.annotationFlags & 0x22)) && // Hidden or NoView
|
|
data.rect); // rectangle is nessessary
|
|
},
|
|
|
|
isPrintable: function Annotation_isPrintable() {
|
|
var data = this.data;
|
|
return !!(!this.isInvisible() &&
|
|
data &&
|
|
data.annotationFlags && // Default: not printable
|
|
data.annotationFlags & 0x4 && // Print
|
|
data.rect); // rectangle is nessessary
|
|
},
|
|
|
|
loadResources: function(keys) {
|
|
var promise = new LegacyPromise();
|
|
this.appearance.dict.getAsync('Resources').then(function(resources) {
|
|
if (!resources) {
|
|
promise.resolve();
|
|
return;
|
|
}
|
|
var objectLoader = new ObjectLoader(resources.map,
|
|
keys,
|
|
resources.xref);
|
|
objectLoader.load().then(function() {
|
|
promise.resolve(resources);
|
|
});
|
|
}.bind(this));
|
|
|
|
return promise;
|
|
},
|
|
|
|
getOperatorList: function Annotation_getOperatorList(evaluator) {
|
|
|
|
var promise = new LegacyPromise();
|
|
|
|
if (!this.appearance) {
|
|
promise.resolve(new OperatorList());
|
|
return promise;
|
|
}
|
|
|
|
var data = this.data;
|
|
|
|
var appearanceDict = this.appearance.dict;
|
|
var resourcesPromise = this.loadResources([
|
|
'ExtGState',
|
|
'ColorSpace',
|
|
'Pattern',
|
|
'Shading',
|
|
'XObject',
|
|
'Font'
|
|
// ProcSet
|
|
// Properties
|
|
]);
|
|
var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1];
|
|
var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0];
|
|
var transform = getTransformMatrix(data.rect, bbox, matrix);
|
|
|
|
var border = data.border;
|
|
|
|
resourcesPromise.then(function(resources) {
|
|
var opList = new OperatorList();
|
|
opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
|
|
evaluator.getOperatorList(this.appearance, resources, opList);
|
|
opList.addOp(OPS.endAnnotation, []);
|
|
promise.resolve(opList);
|
|
|
|
this.appearance.reset();
|
|
}.bind(this));
|
|
|
|
return promise;
|
|
}
|
|
};
|
|
|
|
Annotation.getConstructor =
|
|
function Annotation_getConstructor(subtype, fieldType) {
|
|
|
|
if (!subtype) {
|
|
return;
|
|
}
|
|
|
|
// TODO(mack): Implement FreeText annotations
|
|
if (subtype === 'Link') {
|
|
return LinkAnnotation;
|
|
} else if (subtype === 'Text') {
|
|
return TextAnnotation;
|
|
} else if (subtype === 'Widget') {
|
|
if (!fieldType) {
|
|
return;
|
|
}
|
|
|
|
if (fieldType === 'Tx') {
|
|
return TextWidgetAnnotation;
|
|
} else {
|
|
return WidgetAnnotation;
|
|
}
|
|
} else {
|
|
return Annotation;
|
|
}
|
|
};
|
|
|
|
// TODO(mack): Support loading annotation from data
|
|
Annotation.fromData = function Annotation_fromData(data) {
|
|
var subtype = data.subtype;
|
|
var fieldType = data.fieldType;
|
|
var Constructor = Annotation.getConstructor(subtype, fieldType);
|
|
if (Constructor) {
|
|
return new Constructor({ data: data });
|
|
}
|
|
};
|
|
|
|
Annotation.fromRef = function Annotation_fromRef(xref, ref) {
|
|
|
|
var dict = xref.fetchIfRef(ref);
|
|
if (!isDict(dict)) {
|
|
return;
|
|
}
|
|
|
|
var subtype = dict.get('Subtype');
|
|
subtype = isName(subtype) ? subtype.name : '';
|
|
if (!subtype) {
|
|
return;
|
|
}
|
|
|
|
var fieldType = Util.getInheritableProperty(dict, 'FT');
|
|
fieldType = isName(fieldType) ? fieldType.name : '';
|
|
|
|
var Constructor = Annotation.getConstructor(subtype, fieldType);
|
|
if (!Constructor) {
|
|
return;
|
|
}
|
|
|
|
var params = {
|
|
dict: dict,
|
|
ref: ref,
|
|
};
|
|
|
|
var annotation = new Constructor(params);
|
|
|
|
if (annotation.isViewable() || annotation.isPrintable()) {
|
|
return annotation;
|
|
} else {
|
|
warn('unimplemented annotation type: ' + subtype);
|
|
}
|
|
};
|
|
|
|
Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
|
|
annotations, opList, pdfManager, partialEvaluator, intent) {
|
|
|
|
function reject(e) {
|
|
annotationsReadyPromise.reject(e);
|
|
}
|
|
|
|
var annotationsReadyPromise = new LegacyPromise();
|
|
|
|
var annotationPromises = [];
|
|
for (var i = 0, n = annotations.length; i < n; ++i) {
|
|
if (intent === 'display' && annotations[i].isViewable() ||
|
|
intent === 'print' && annotations[i].isPrintable()) {
|
|
annotationPromises.push(
|
|
annotations[i].getOperatorList(partialEvaluator));
|
|
}
|
|
}
|
|
Promise.all(annotationPromises).then(function(datas) {
|
|
opList.addOp(OPS.beginAnnotations, []);
|
|
for (var i = 0, n = datas.length; i < n; ++i) {
|
|
var annotOpList = datas[i];
|
|
opList.addOpList(annotOpList);
|
|
}
|
|
opList.addOp(OPS.endAnnotations, []);
|
|
annotationsReadyPromise.resolve();
|
|
}, reject);
|
|
|
|
return annotationsReadyPromise;
|
|
};
|
|
|
|
return Annotation;
|
|
})();
|
|
PDFJS.Annotation = Annotation;
|
|
|
|
|
|
var WidgetAnnotation = (function WidgetAnnotationClosure() {
|
|
|
|
function WidgetAnnotation(params) {
|
|
Annotation.call(this, params);
|
|
|
|
if (params.data) {
|
|
return;
|
|
}
|
|
|
|
var dict = params.dict;
|
|
var data = this.data;
|
|
|
|
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 : '';
|
|
data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0;
|
|
this.fieldResources = Util.getInheritableProperty(dict, 'DR') || new Dict();
|
|
|
|
// 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));
|
|
} else {
|
|
// 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)
|
|
break;
|
|
}
|
|
fieldName.unshift('`' + j);
|
|
}
|
|
namedItem = parent;
|
|
ref = parentRef;
|
|
}
|
|
data.fullName = fieldName.join('.');
|
|
}
|
|
|
|
var parent = Annotation.prototype;
|
|
Util.inherit(WidgetAnnotation, Annotation, {
|
|
isViewable: function WidgetAnnotation_isViewable() {
|
|
if (this.data.fieldType === 'Sig') {
|
|
warn('unimplemented annotation type: Widget signature');
|
|
return false;
|
|
}
|
|
|
|
return parent.isViewable.call(this);
|
|
}
|
|
});
|
|
|
|
return WidgetAnnotation;
|
|
})();
|
|
|
|
var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
|
|
function TextWidgetAnnotation(params) {
|
|
WidgetAnnotation.call(this, params);
|
|
|
|
if (params.data) {
|
|
return;
|
|
}
|
|
|
|
this.data.textAlignment = Util.getInheritableProperty(params.dict, 'Q');
|
|
}
|
|
|
|
// TODO(mack): This dupes some of the logic in CanvasGraphics.setFont()
|
|
function setTextStyles(element, item, fontObj) {
|
|
|
|
var style = element.style;
|
|
style.fontSize = item.fontSize + 'px';
|
|
style.direction = item.fontDirection < 0 ? 'rtl': 'ltr';
|
|
|
|
if (!fontObj) {
|
|
return;
|
|
}
|
|
|
|
style.fontWeight = fontObj.black ?
|
|
(fontObj.bold ? 'bolder' : 'bold') :
|
|
(fontObj.bold ? 'bold' : 'normal');
|
|
style.fontStyle = fontObj.italic ? 'italic' : 'normal';
|
|
|
|
var fontName = fontObj.loadedName;
|
|
var fontFamily = fontName ? '"' + fontName + '", ' : '';
|
|
// Use a reasonable default font if the font doesn't specify a fallback
|
|
var fallbackName = fontObj.fallbackName || 'Helvetica, sans-serif';
|
|
style.fontFamily = fontFamily + fallbackName;
|
|
}
|
|
|
|
|
|
var parent = WidgetAnnotation.prototype;
|
|
Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
|
|
hasHtml: function TextWidgetAnnotation_hasHtml() {
|
|
return !this.data.hasAppearance && !!this.data.fieldValue;
|
|
},
|
|
|
|
getHtmlElement: function TextWidgetAnnotation_getHtmlElement(commonObjs) {
|
|
assert(!isWorker, 'getHtmlElement() shall be called from main thread');
|
|
|
|
var item = this.data;
|
|
|
|
var element = this.getEmptyContainer('div');
|
|
element.style.display = 'table';
|
|
|
|
var content = document.createElement('div');
|
|
content.textContent = item.fieldValue;
|
|
var textAlignment = item.textAlignment;
|
|
content.style.textAlign = ['left', 'center', 'right'][textAlignment];
|
|
content.style.verticalAlign = 'middle';
|
|
content.style.display = 'table-cell';
|
|
|
|
var fontObj = item.fontRefName ?
|
|
commonObjs.getData(item.fontRefName) : null;
|
|
var cssRules = setTextStyles(content, item, fontObj);
|
|
|
|
element.appendChild(content);
|
|
|
|
return element;
|
|
},
|
|
|
|
getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
|
|
if (this.appearance) {
|
|
return Annotation.prototype.getOperatorList.call(this, evaluator);
|
|
}
|
|
|
|
var promise = new LegacyPromise();
|
|
var opList = new OperatorList();
|
|
var data = this.data;
|
|
|
|
// Even if there is an appearance stream, ignore it. This is the
|
|
// behaviour used by Adobe Reader.
|
|
|
|
var defaultAppearance = data.defaultAppearance;
|
|
if (!defaultAppearance) {
|
|
promise.resolve(opList);
|
|
return promise;
|
|
}
|
|
|
|
// Include any font resources found in the default appearance
|
|
|
|
var stream = new Stream(stringToBytes(defaultAppearance));
|
|
evaluator.getOperatorList(stream, this.fieldResources, opList);
|
|
var appearanceFnArray = opList.fnArray;
|
|
var appearanceArgsArray = opList.argsArray;
|
|
var fnArray = [];
|
|
var argsArray = [];
|
|
|
|
// TODO(mack): Add support for stroke color
|
|
data.rgb = [0, 0, 0];
|
|
// TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY!
|
|
for (var i = 0, n = fnArray.length; i < n; ++i) {
|
|
var fnId = appearanceFnArray[i];
|
|
var args = appearanceArgsArray[i];
|
|
|
|
if (fnId === OPS.setFont) {
|
|
data.fontRefName = args[0];
|
|
var size = args[1];
|
|
if (size < 0) {
|
|
data.fontDirection = -1;
|
|
data.fontSize = -size;
|
|
} else {
|
|
data.fontDirection = 1;
|
|
data.fontSize = size;
|
|
}
|
|
} else if (fnId === OPS.setFillRGBColor) {
|
|
data.rgb = args;
|
|
} else if (fnId === OPS.setFillGray) {
|
|
var rgbValue = args[0] * 255;
|
|
data.rgb = [rgbValue, rgbValue, rgbValue];
|
|
}
|
|
}
|
|
promise.resolve(opList);
|
|
return promise;
|
|
}
|
|
});
|
|
|
|
return TextWidgetAnnotation;
|
|
})();
|
|
|
|
var InteractiveAnnotation = (function InteractiveAnnotationClosure() {
|
|
function InteractiveAnnotation(params) {
|
|
Annotation.call(this, params);
|
|
}
|
|
|
|
Util.inherit(InteractiveAnnotation, Annotation, {
|
|
hasHtml: function InteractiveAnnotation_hasHtml() {
|
|
return true;
|
|
},
|
|
|
|
highlight: function InteractiveAnnotation_highlight() {
|
|
if (this.highlightElement &&
|
|
this.highlightElement.hasAttribute('hidden')) {
|
|
this.highlightElement.removeAttribute('hidden');
|
|
}
|
|
},
|
|
|
|
unhighlight: function InteractiveAnnotation_unhighlight() {
|
|
if (this.highlightElement &&
|
|
!this.highlightElement.hasAttribute('hidden')) {
|
|
this.highlightElement.setAttribute('hidden', true);
|
|
}
|
|
},
|
|
|
|
initContainer: function InteractiveAnnotation_initContainer() {
|
|
|
|
var item = this.data;
|
|
var rect = item.rect;
|
|
|
|
var container = this.getEmptyContainer('section', rect, item.borderWidth);
|
|
container.style.backgroundColor = item.color;
|
|
|
|
var color = item.color;
|
|
var rgb = [];
|
|
for (var i = 0; i < 3; ++i) {
|
|
rgb[i] = Math.round(color[i] * 255);
|
|
}
|
|
item.colorCssRgb = Util.makeCssRgb(rgb);
|
|
|
|
var highlight = document.createElement('div');
|
|
highlight.className = 'annotationHighlight';
|
|
highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px';
|
|
highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px';
|
|
highlight.setAttribute('hidden', true);
|
|
|
|
this.highlightElement = highlight;
|
|
container.appendChild(this.highlightElement);
|
|
|
|
return container;
|
|
}
|
|
});
|
|
|
|
return InteractiveAnnotation;
|
|
})();
|
|
|
|
var TextAnnotation = (function TextAnnotationClosure() {
|
|
function TextAnnotation(params) {
|
|
InteractiveAnnotation.call(this, params);
|
|
|
|
if (params.data) {
|
|
return;
|
|
}
|
|
|
|
var dict = params.dict;
|
|
var data = this.data;
|
|
|
|
var content = dict.get('Contents');
|
|
var title = dict.get('T');
|
|
data.content = stringToPDFString(content || '');
|
|
data.title = stringToPDFString(title || '');
|
|
|
|
if (data.hasAppearance) {
|
|
data.name = 'NoIcon';
|
|
} else {
|
|
data.name = dict.has('Name') ? dict.get('Name').name : 'Note';
|
|
}
|
|
|
|
if (dict.has('C')) {
|
|
data.hasBgColor = true;
|
|
}
|
|
}
|
|
|
|
var ANNOT_MIN_SIZE = 10;
|
|
|
|
Util.inherit(TextAnnotation, InteractiveAnnotation, {
|
|
|
|
getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) {
|
|
assert(!isWorker, 'getHtmlElement() shall be called from main thread');
|
|
|
|
var item = this.data;
|
|
var rect = item.rect;
|
|
|
|
// sanity check because of OOo-generated PDFs
|
|
if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
|
|
rect[3] = rect[1] + ANNOT_MIN_SIZE;
|
|
}
|
|
if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
|
|
rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
|
|
}
|
|
|
|
var container = this.initContainer();
|
|
container.className = 'annotText';
|
|
|
|
var image = document.createElement('img');
|
|
image.style.height = container.style.height;
|
|
image.style.width = container.style.width;
|
|
var iconName = item.name;
|
|
image.src = PDFJS.imageResourcesPath + 'annotation-' +
|
|
iconName.toLowerCase() + '.svg';
|
|
image.alt = '[{{type}} Annotation]';
|
|
image.dataset.l10nId = 'text_annotation_type';
|
|
image.dataset.l10nArgs = JSON.stringify({type: iconName});
|
|
|
|
var contentWrapper = document.createElement('div');
|
|
contentWrapper.className = 'annotTextContentWrapper';
|
|
contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px';
|
|
contentWrapper.style.top = '-10px';
|
|
|
|
var content = document.createElement('div');
|
|
content.className = 'annotTextContent';
|
|
content.setAttribute('hidden', true);
|
|
if (item.hasBgColor) {
|
|
var color = item.color;
|
|
var rgb = [];
|
|
for (var i = 0; i < 3; ++i) {
|
|
// Enlighten the color (70%)
|
|
var c = Math.round(color[i] * 255);
|
|
rgb[i] = Math.round((255 - c) * 0.7) + c;
|
|
}
|
|
content.style.backgroundColor = Util.makeCssRgb(rgb);
|
|
}
|
|
|
|
var title = document.createElement('h1');
|
|
var text = document.createElement('p');
|
|
title.textContent = item.title;
|
|
|
|
if (!item.content && !item.title) {
|
|
content.setAttribute('hidden', true);
|
|
} else {
|
|
var e = document.createElement('span');
|
|
var lines = item.content.split(/(?:\r\n?|\n)/);
|
|
for (var i = 0, ii = lines.length; i < ii; ++i) {
|
|
var line = lines[i];
|
|
e.appendChild(document.createTextNode(line));
|
|
if (i < (ii - 1))
|
|
e.appendChild(document.createElement('br'));
|
|
}
|
|
text.appendChild(e);
|
|
|
|
var pinned = false;
|
|
|
|
var showAnnotation = function showAnnotation(pin) {
|
|
if (pin) {
|
|
pinned = true;
|
|
}
|
|
if (content.hasAttribute('hidden')) {
|
|
container.style.zIndex += 1;
|
|
content.removeAttribute('hidden');
|
|
}
|
|
};
|
|
|
|
var hideAnnotation = function hideAnnotation(unpin) {
|
|
if (unpin) {
|
|
pinned = false;
|
|
}
|
|
if (!content.hasAttribute('hidden') && !pinned) {
|
|
container.style.zIndex -= 1;
|
|
content.setAttribute('hidden', true);
|
|
}
|
|
};
|
|
|
|
var toggleAnnotation = function toggleAnnotation() {
|
|
if (pinned) {
|
|
hideAnnotation(true);
|
|
} else {
|
|
showAnnotation(true);
|
|
}
|
|
};
|
|
|
|
var self = this;
|
|
image.addEventListener('click', function image_clickHandler() {
|
|
toggleAnnotation();
|
|
}, false);
|
|
image.addEventListener('mouseover', function image_mouseOverHandler() {
|
|
showAnnotation();
|
|
}, false);
|
|
image.addEventListener('mouseout', function image_mouseOutHandler() {
|
|
hideAnnotation();
|
|
}, false);
|
|
|
|
content.addEventListener('click', function content_clickHandler() {
|
|
hideAnnotation(true);
|
|
}, false);
|
|
}
|
|
|
|
content.appendChild(title);
|
|
content.appendChild(text);
|
|
contentWrapper.appendChild(content);
|
|
container.appendChild(image);
|
|
container.appendChild(contentWrapper);
|
|
|
|
return container;
|
|
}
|
|
});
|
|
|
|
return TextAnnotation;
|
|
})();
|
|
|
|
var LinkAnnotation = (function LinkAnnotationClosure() {
|
|
function LinkAnnotation(params) {
|
|
InteractiveAnnotation.call(this, params);
|
|
|
|
if (params.data) {
|
|
return;
|
|
}
|
|
|
|
var dict = params.dict;
|
|
var data = this.data;
|
|
|
|
var action = dict.get('A');
|
|
if (action) {
|
|
var linkType = action.get('S').name;
|
|
if (linkType === 'URI') {
|
|
var 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.
|
|
if (!isValidUrl(url, false)) {
|
|
url = '';
|
|
}
|
|
data.url = url;
|
|
} else if (linkType === 'GoTo') {
|
|
data.dest = action.get('D');
|
|
} else if (linkType === 'GoToR') {
|
|
var urlDict = action.get('F');
|
|
if (isDict(urlDict)) {
|
|
// We assume that the 'url' is a Filspec dictionary
|
|
// and fetch the url without checking any further
|
|
url = urlDict.get('F') || '';
|
|
}
|
|
|
|
// TODO: pdf reference says that GoToR
|
|
// can also have 'NewWindow' attribute
|
|
if (!isValidUrl(url, false)) {
|
|
url = '';
|
|
}
|
|
data.url = url;
|
|
data.dest = action.get('D');
|
|
} else if (linkType === 'Named') {
|
|
data.action = action.get('N').name;
|
|
} else {
|
|
warn('unrecognized link type: ' + linkType);
|
|
}
|
|
} else if (dict.has('Dest')) {
|
|
// simple destination link
|
|
var dest = dict.get('Dest');
|
|
data.dest = isName(dest) ? dest.name : dest;
|
|
}
|
|
}
|
|
|
|
// Lets URLs beginning with 'www.' default to using the 'http://' protocol.
|
|
function addDefaultProtocolToUrl(url) {
|
|
if (url && url.indexOf('www.') === 0) {
|
|
return ('http://' + url);
|
|
}
|
|
return url;
|
|
}
|
|
|
|
Util.inherit(LinkAnnotation, InteractiveAnnotation, {
|
|
hasOperatorList: function LinkAnnotation_hasOperatorList() {
|
|
return false;
|
|
},
|
|
|
|
getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) {
|
|
|
|
var container = this.initContainer();
|
|
container.className = 'annotLink';
|
|
|
|
var item = this.data;
|
|
var rect = item.rect;
|
|
|
|
container.style.borderColor = item.colorCssRgb;
|
|
container.style.borderStyle = 'solid';
|
|
|
|
var link = document.createElement('a');
|
|
link.href = link.title = this.data.url || '';
|
|
|
|
container.appendChild(link);
|
|
|
|
return container;
|
|
}
|
|
});
|
|
|
|
return LinkAnnotation;
|
|
})();
|