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.
|
|
|
|
*/
|
2019-01-06 03:03:53 +09:00
|
|
|
/* eslint no-var: error */
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2017-04-02 23:14:30 +09:00
|
|
|
import {
|
|
|
|
AnnotationBorderStyleType, AnnotationFieldFlag, AnnotationFlag,
|
2019-02-24 00:14:31 +09:00
|
|
|
AnnotationType, OPS, stringToBytes, stringToPDFString, Util, warn
|
2017-04-02 23:14:30 +09:00
|
|
|
} from '../shared/util';
|
|
|
|
import { Catalog, FileSpec, ObjectLoader } from './obj';
|
|
|
|
import { Dict, isDict, isName, isRef, isStream } from './primitives';
|
|
|
|
import { ColorSpace } from './colorspace';
|
2019-02-24 00:14:31 +09:00
|
|
|
import { getInheritableProperty } from './core_utils';
|
2017-10-31 03:29:58 +09:00
|
|
|
import { OperatorList } from './operator_list';
|
2017-04-02 23:14:30 +09:00
|
|
|
import { Stream } from './stream';
|
2015-11-22 01:32:47 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class AnnotationFactory {
|
2015-07-26 23:47:28 +09:00
|
|
|
/**
|
2018-03-21 09:43:40 +09:00
|
|
|
* Create an `Annotation` object of the correct type for the given reference
|
|
|
|
* to an annotation dictionary. This yields a promise that is resolved when
|
|
|
|
* the `Annotation` object is constructed.
|
|
|
|
*
|
2015-07-26 23:47:28 +09:00
|
|
|
* @param {XRef} xref
|
|
|
|
* @param {Object} ref
|
2016-10-01 19:05:07 +09:00
|
|
|
* @param {PDFManager} pdfManager
|
2017-01-09 00:51:30 +09:00
|
|
|
* @param {Object} idFactory
|
2018-03-21 09:43:40 +09:00
|
|
|
* @return {Promise} A promise that is resolved with an {Annotation} instance.
|
2015-07-26 23:47:28 +09:00
|
|
|
*/
|
2017-08-27 07:30:00 +09:00
|
|
|
static create(xref, ref, pdfManager, idFactory) {
|
2018-03-21 09:43:40 +09:00
|
|
|
return pdfManager.ensure(this, '_create',
|
|
|
|
[xref, ref, pdfManager, idFactory]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
static _create(xref, ref, pdfManager, idFactory) {
|
2017-08-27 06:49:11 +09:00
|
|
|
let dict = xref.fetchIfRef(ref);
|
2015-07-26 23:47:28 +09:00
|
|
|
if (!isDict(dict)) {
|
|
|
|
return;
|
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
let id = isRef(ref) ? ref.toString() : 'annot_' + idFactory.createObjId();
|
2015-07-26 23:47:28 +09:00
|
|
|
|
|
|
|
// Determine the annotation's subtype.
|
2017-08-27 06:49:11 +09:00
|
|
|
let subtype = dict.get('Subtype');
|
2016-07-24 21:32:48 +09:00
|
|
|
subtype = isName(subtype) ? subtype.name : null;
|
2015-07-26 23:47:28 +09:00
|
|
|
|
|
|
|
// Return the right annotation object based on the subtype and field type.
|
2017-08-27 06:49:11 +09:00
|
|
|
let parameters = {
|
2017-04-27 19:58:44 +09:00
|
|
|
xref,
|
|
|
|
dict,
|
2016-08-26 23:01:25 +09:00
|
|
|
ref: isRef(ref) ? ref : null,
|
2017-04-27 19:58:44 +09:00
|
|
|
subtype,
|
|
|
|
id,
|
|
|
|
pdfManager,
|
2015-07-26 23:47:28 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
switch (subtype) {
|
|
|
|
case 'Link':
|
|
|
|
return new LinkAnnotation(parameters);
|
|
|
|
|
|
|
|
case 'Text':
|
|
|
|
return new TextAnnotation(parameters);
|
|
|
|
|
|
|
|
case 'Widget':
|
2018-02-19 05:42:33 +09:00
|
|
|
let fieldType = getInheritableProperty({ dict, key: 'FT', });
|
2016-09-06 06:46:52 +09:00
|
|
|
fieldType = isName(fieldType) ? fieldType.name : null;
|
|
|
|
|
|
|
|
switch (fieldType) {
|
|
|
|
case 'Tx':
|
|
|
|
return new TextWidgetAnnotation(parameters);
|
2016-11-04 21:01:42 +09:00
|
|
|
case 'Btn':
|
|
|
|
return new ButtonWidgetAnnotation(parameters);
|
2016-09-25 08:45:49 +09:00
|
|
|
case 'Ch':
|
|
|
|
return new ChoiceWidgetAnnotation(parameters);
|
2015-07-26 23:47:28 +09:00
|
|
|
}
|
2016-09-06 06:46:52 +09:00
|
|
|
warn('Unimplemented widget field type "' + fieldType + '", ' +
|
|
|
|
'falling back to base field type.');
|
2015-07-26 23:47:28 +09:00
|
|
|
return new WidgetAnnotation(parameters);
|
|
|
|
|
2015-12-23 05:31:56 +09:00
|
|
|
case 'Popup':
|
|
|
|
return new PopupAnnotation(parameters);
|
|
|
|
|
2017-04-03 03:50:17 +09:00
|
|
|
case 'Line':
|
|
|
|
return new LineAnnotation(parameters);
|
|
|
|
|
2017-07-24 07:11:27 +09:00
|
|
|
case 'Square':
|
|
|
|
return new SquareAnnotation(parameters);
|
|
|
|
|
2017-07-24 07:30:58 +09:00
|
|
|
case 'Circle':
|
|
|
|
return new CircleAnnotation(parameters);
|
|
|
|
|
2017-09-18 03:18:22 +09:00
|
|
|
case 'PolyLine':
|
|
|
|
return new PolylineAnnotation(parameters);
|
|
|
|
|
2017-09-23 23:50:49 +09:00
|
|
|
case 'Polygon':
|
|
|
|
return new PolygonAnnotation(parameters);
|
|
|
|
|
2019-04-10 06:35:32 +09:00
|
|
|
case 'Caret':
|
|
|
|
return new CaretAnnotation(parameters);
|
|
|
|
|
2018-09-30 23:29:16 +09:00
|
|
|
case 'Ink':
|
|
|
|
return new InkAnnotation(parameters);
|
|
|
|
|
2016-01-01 23:31:46 +09:00
|
|
|
case 'Highlight':
|
|
|
|
return new HighlightAnnotation(parameters);
|
|
|
|
|
2015-12-28 08:33:41 +09:00
|
|
|
case 'Underline':
|
|
|
|
return new UnderlineAnnotation(parameters);
|
|
|
|
|
2015-12-30 23:28:26 +09:00
|
|
|
case 'Squiggly':
|
|
|
|
return new SquigglyAnnotation(parameters);
|
|
|
|
|
2015-12-29 23:09:28 +09:00
|
|
|
case 'StrikeOut':
|
|
|
|
return new StrikeOutAnnotation(parameters);
|
|
|
|
|
2017-09-16 23:37:50 +09:00
|
|
|
case 'Stamp':
|
|
|
|
return new StampAnnotation(parameters);
|
|
|
|
|
2016-02-15 04:44:00 +09:00
|
|
|
case 'FileAttachment':
|
|
|
|
return new FileAttachmentAnnotation(parameters);
|
|
|
|
|
2015-07-26 23:47:28 +09:00
|
|
|
default:
|
2016-07-24 21:32:48 +09:00
|
|
|
if (!subtype) {
|
|
|
|
warn('Annotation is missing the required /Subtype.');
|
|
|
|
} else {
|
|
|
|
warn('Unimplemented annotation type "' + subtype + '", ' +
|
|
|
|
'falling back to base annotation.');
|
|
|
|
}
|
2015-07-26 23:47:28 +09:00
|
|
|
return new Annotation(parameters);
|
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
|
|
|
}
|
2014-03-07 23:48:42 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
function getTransformMatrix(rect, bbox, matrix) {
|
2013-03-21 17:04:44 +09:00
|
|
|
// 12.5.5: Algorithm: Appearance streams
|
2017-08-27 06:49:11 +09:00
|
|
|
let bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
|
|
|
|
let minX = bounds[0];
|
|
|
|
let minY = bounds[1];
|
|
|
|
let maxX = bounds[2];
|
|
|
|
let 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]];
|
2013-03-21 17:04:44 +09:00
|
|
|
}
|
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
let xRatio = (rect[2] - rect[0]) / (maxX - minX);
|
|
|
|
let yRatio = (rect[3] - rect[1]) / (maxY - minY);
|
|
|
|
return [
|
|
|
|
xRatio,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
yRatio,
|
|
|
|
rect[0] - minX * xRatio,
|
|
|
|
rect[1] - minY * yRatio
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
class Annotation {
|
|
|
|
constructor(params) {
|
|
|
|
let dict = params.dict;
|
2015-11-22 07:25:17 +09:00
|
|
|
|
|
|
|
this.setFlags(dict.get('F'));
|
2016-03-26 22:41:15 +09:00
|
|
|
this.setRectangle(dict.getArray('Rect'));
|
2016-05-06 02:16:35 +09:00
|
|
|
this.setColor(dict.getArray('C'));
|
2014-12-26 04:44:16 +09:00
|
|
|
this.setBorderStyle(dict);
|
2017-02-13 07:05:10 +09:00
|
|
|
this.setAppearance(dict);
|
2015-11-29 00:08:34 +09:00
|
|
|
|
|
|
|
// Expose public properties using a data object.
|
2017-08-27 06:49:11 +09:00
|
|
|
this.data = {
|
|
|
|
annotationFlags: this.flags,
|
|
|
|
borderStyle: this.borderStyle,
|
|
|
|
color: this.color,
|
|
|
|
hasAppearance: !!this.appearance,
|
|
|
|
id: params.id,
|
|
|
|
rect: this.rectangle,
|
|
|
|
subtype: params.subtype,
|
|
|
|
};
|
2013-03-21 17:04:44 +09:00
|
|
|
}
|
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_hasFlag(flags, flag) {
|
|
|
|
return !!(flags & flag);
|
|
|
|
}
|
2015-07-15 23:59:25 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_isViewable(flags) {
|
|
|
|
return !this._hasFlag(flags, AnnotationFlag.INVISIBLE) &&
|
|
|
|
!this._hasFlag(flags, AnnotationFlag.HIDDEN) &&
|
|
|
|
!this._hasFlag(flags, AnnotationFlag.NOVIEW);
|
|
|
|
}
|
2015-07-15 23:59:25 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_isPrintable(flags) {
|
|
|
|
return this._hasFlag(flags, AnnotationFlag.PRINT) &&
|
|
|
|
!this._hasFlag(flags, AnnotationFlag.INVISIBLE) &&
|
|
|
|
!this._hasFlag(flags, AnnotationFlag.HIDDEN);
|
|
|
|
}
|
2015-07-15 23:59:25 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
get viewable() {
|
|
|
|
if (this.flags === 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return this._isViewable(this.flags);
|
|
|
|
}
|
2015-07-15 23:59:25 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
get printable() {
|
|
|
|
if (this.flags === 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return this._isPrintable(this.flags);
|
|
|
|
}
|
2015-07-15 23:59:25 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* Set the flags.
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @memberof Annotation
|
|
|
|
* @param {number} flags - Unsigned 32-bit integer specifying annotation
|
|
|
|
* characteristics
|
|
|
|
* @see {@link shared/util.js}
|
|
|
|
*/
|
|
|
|
setFlags(flags) {
|
2017-09-01 23:52:50 +09:00
|
|
|
this.flags = (Number.isInteger(flags) && flags > 0) ? flags : 0;
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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(flag) {
|
|
|
|
return this._hasFlag(this.flags, flag);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the rectangle.
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @memberof Annotation
|
|
|
|
* @param {Array} rectangle - The rectangle array with exactly four entries
|
|
|
|
*/
|
|
|
|
setRectangle(rectangle) {
|
2017-09-02 03:27:13 +09:00
|
|
|
if (Array.isArray(rectangle) && rectangle.length === 4) {
|
2017-08-27 06:49:11 +09:00
|
|
|
this.rectangle = Util.normalizeRect(rectangle);
|
|
|
|
} else {
|
|
|
|
this.rectangle = [0, 0, 0, 0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the color and take care of color space conversion.
|
2018-06-12 00:25:40 +09:00
|
|
|
* The default value is black, in RGB color space.
|
2017-08-27 06:49:11 +09:00
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @memberof Annotation
|
|
|
|
* @param {Array} color - The color array containing either 0
|
|
|
|
* (transparent), 1 (grayscale), 3 (RGB) or
|
|
|
|
* 4 (CMYK) elements
|
|
|
|
*/
|
|
|
|
setColor(color) {
|
2018-06-12 00:25:40 +09:00
|
|
|
let rgbColor = new Uint8ClampedArray(3);
|
2017-09-02 03:27:13 +09:00
|
|
|
if (!Array.isArray(color)) {
|
2017-08-27 06:49:11 +09:00
|
|
|
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(borderStyle) {
|
|
|
|
this.borderStyle = new AnnotationBorderStyle();
|
|
|
|
if (!isDict(borderStyle)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (borderStyle.has('BS')) {
|
|
|
|
let dict = borderStyle.get('BS');
|
|
|
|
let 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')) {
|
|
|
|
let array = borderStyle.getArray('Border');
|
2017-09-02 03:27:13 +09:00
|
|
|
if (Array.isArray(array) && array.length >= 3) {
|
2017-08-27 06:49:11 +09:00
|
|
|
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]);
|
2014-12-26 04:24:28 +09:00
|
|
|
}
|
2017-02-13 07:05:10 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
} 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);
|
|
|
|
}
|
|
|
|
}
|
2017-02-13 07:05:10 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* Set the (normal) appearance.
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @memberof Annotation
|
|
|
|
* @param {Dict} dict - The annotation's data dictionary
|
|
|
|
*/
|
|
|
|
setAppearance(dict) {
|
|
|
|
this.appearance = null;
|
2017-02-13 07:05:10 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
let appearanceStates = dict.get('AP');
|
|
|
|
if (!isDict(appearanceStates)) {
|
|
|
|
return;
|
|
|
|
}
|
2016-02-23 08:21:28 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
// In case the normal appearance is a stream, then it is used directly.
|
|
|
|
let normalAppearanceState = appearanceStates.get('N');
|
|
|
|
if (isStream(normalAppearanceState)) {
|
|
|
|
this.appearance = normalAppearanceState;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!isDict(normalAppearanceState)) {
|
|
|
|
return;
|
|
|
|
}
|
2016-02-23 08:21:28 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
// In case the normal appearance is a dictionary, the `AS` entry provides
|
|
|
|
// the key of the stream in this dictionary.
|
|
|
|
let as = dict.get('AS');
|
|
|
|
if (!isName(as) || !normalAppearanceState.has(as.name)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.appearance = normalAppearanceState.get(as.name);
|
|
|
|
}
|
2017-06-13 17:22:11 +09:00
|
|
|
|
2017-08-27 06:49:11 +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(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') || '');
|
|
|
|
}
|
2013-06-05 09:57:52 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
loadResources(keys) {
|
|
|
|
return this.appearance.dict.getAsync('Resources').then((resources) => {
|
|
|
|
if (!resources) {
|
|
|
|
return;
|
2013-03-21 17:04:44 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
let objectLoader = new ObjectLoader(resources, keys, resources.xref);
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
return objectLoader.load().then(function() {
|
|
|
|
return resources;
|
[api-minor] Always allow e.g. rendering to continue even if there are errors, and add a `stopAtErrors` parameter to `getDocument` to opt-out of this behaviour (issue 6342, issue 3795, bug 1130815)
Other PDF readers, e.g. Adobe Reader and PDFium (in Chrome), will attempt to render as much of a page as possible even if there are errors present.
Currently we just bail as soon the first error is hit, which means that we'll usually not render anything in these cases and just display a blank page instead.
NOTE: This patch changes the default behaviour of the PDF.js API to always attempt to recover as much data as possible, even when encountering errors during e.g. `getOperatorList`/`getTextContent`, which thus improve our handling of corrupt PDF files and allow the default viewer to handle errors slightly more gracefully.
In the event that an API consumer wishes to use the old behaviour, where we stop parsing as soon as an error is encountered, the `stopAtErrors` parameter can be set at `getDocument`.
Fixes, inasmuch it's possible since the PDF files are corrupt, e.g. issue 6342, issue 3795, and [bug 1130815](https://bugzilla.mozilla.org/show_bug.cgi?id=1130815) (and probably others too).
2017-02-19 22:03:08 +09:00
|
|
|
});
|
2017-08-27 06:49:11 +09:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getOperatorList(evaluator, task, renderForms) {
|
|
|
|
if (!this.appearance) {
|
|
|
|
return Promise.resolve(new OperatorList());
|
|
|
|
}
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
let data = this.data;
|
|
|
|
let appearanceDict = this.appearance.dict;
|
|
|
|
let resourcesPromise = this.loadResources([
|
|
|
|
'ExtGState',
|
|
|
|
'ColorSpace',
|
|
|
|
'Pattern',
|
|
|
|
'Shading',
|
|
|
|
'XObject',
|
|
|
|
'Font',
|
|
|
|
// ProcSet
|
|
|
|
// Properties
|
|
|
|
]);
|
|
|
|
let bbox = appearanceDict.getArray('BBox') || [0, 0, 1, 1];
|
|
|
|
let matrix = appearanceDict.getArray('Matrix') || [1, 0, 0, 1, 0, 0];
|
|
|
|
let transform = getTransformMatrix(data.rect, bbox, matrix);
|
|
|
|
|
|
|
|
return resourcesPromise.then((resources) => {
|
|
|
|
let opList = new OperatorList();
|
|
|
|
opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
|
|
|
|
return evaluator.getOperatorList({
|
|
|
|
stream: this.appearance,
|
|
|
|
task,
|
|
|
|
resources,
|
|
|
|
operatorList: opList,
|
|
|
|
}).then(() => {
|
|
|
|
opList.addOp(OPS.endAnnotation, []);
|
|
|
|
this.appearance.reset();
|
|
|
|
return opList;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2014-12-26 04:11:23 +09:00
|
|
|
/**
|
|
|
|
* Contains all data regarding an annotation's border style.
|
|
|
|
*/
|
2017-08-27 06:49:11 +09:00
|
|
|
class AnnotationBorderStyle {
|
|
|
|
constructor() {
|
2014-12-26 04:11:23 +09:00
|
|
|
this.width = 1;
|
|
|
|
this.style = AnnotationBorderStyleType.SOLID;
|
|
|
|
this.dashArray = [3];
|
|
|
|
this.horizontalCornerRadius = 0;
|
|
|
|
this.verticalCornerRadius = 0;
|
|
|
|
}
|
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* Set the width.
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @memberof AnnotationBorderStyle
|
|
|
|
* @param {integer} width - The width
|
|
|
|
*/
|
|
|
|
setWidth(width) {
|
2018-12-31 20:21:28 +09:00
|
|
|
// Some corrupt PDF generators may provide the width as a `Name`,
|
|
|
|
// rather than as a number (fixes issue 10385).
|
|
|
|
if (isName(width)) {
|
2019-01-04 18:33:15 +09:00
|
|
|
this.width = 0; // This is consistent with the behaviour in Adobe Reader.
|
|
|
|
return;
|
2018-12-31 20:21:28 +09:00
|
|
|
}
|
2017-09-03 19:50:16 +09:00
|
|
|
if (Number.isInteger(width)) {
|
2017-08-27 06:49:11 +09:00
|
|
|
this.width = width;
|
|
|
|
}
|
|
|
|
}
|
2014-12-26 04:11:23 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* Set the style.
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @memberof AnnotationBorderStyle
|
2018-12-31 20:14:33 +09:00
|
|
|
* @param {Name} style - The annotation style.
|
2017-08-27 06:49:11 +09:00
|
|
|
* @see {@link shared/util.js}
|
|
|
|
*/
|
|
|
|
setStyle(style) {
|
2018-12-31 20:14:33 +09:00
|
|
|
if (!isName(style)) {
|
2017-08-27 06:49:11 +09:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (style.name) {
|
|
|
|
case 'S':
|
|
|
|
this.style = AnnotationBorderStyleType.SOLID;
|
|
|
|
break;
|
2014-12-26 04:11:23 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
case 'D':
|
|
|
|
this.style = AnnotationBorderStyleType.DASHED;
|
|
|
|
break;
|
2014-12-26 04:11:23 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
case 'B':
|
|
|
|
this.style = AnnotationBorderStyleType.BEVELED;
|
|
|
|
break;
|
2014-12-26 04:11:23 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
case 'I':
|
|
|
|
this.style = AnnotationBorderStyleType.INSET;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'U':
|
|
|
|
this.style = AnnotationBorderStyleType.UNDERLINE;
|
|
|
|
break;
|
2014-12-26 04:11:23 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the dash array.
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @memberof AnnotationBorderStyle
|
|
|
|
* @param {Array} dashArray - The dash array with at least one element
|
|
|
|
*/
|
|
|
|
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.
|
2017-09-02 03:27:13 +09:00
|
|
|
if (Array.isArray(dashArray) && dashArray.length > 0) {
|
2017-08-27 06:49:11 +09:00
|
|
|
// According to the PDF specification: the elements in `dashArray`
|
|
|
|
// shall be numbers that are nonnegative and not all equal to zero.
|
|
|
|
let isValid = true;
|
|
|
|
let allZeros = true;
|
|
|
|
for (let i = 0, len = dashArray.length; i < len; i++) {
|
|
|
|
let element = dashArray[i];
|
|
|
|
let validNumber = (+element >= 0);
|
|
|
|
if (!validNumber) {
|
|
|
|
isValid = false;
|
2014-12-26 04:11:23 +09:00
|
|
|
break;
|
2017-08-27 06:49:11 +09:00
|
|
|
} else if (element > 0) {
|
|
|
|
allZeros = false;
|
2014-12-26 04:11:23 +09:00
|
|
|
}
|
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
if (isValid && !allZeros) {
|
|
|
|
this.dashArray = dashArray;
|
|
|
|
} else {
|
|
|
|
this.width = 0; // Adobe behavior when the array is invalid.
|
2014-12-26 04:11:23 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
} else if (dashArray) {
|
|
|
|
this.width = 0; // Adobe behavior when the array is invalid.
|
|
|
|
}
|
|
|
|
}
|
2014-12-26 04:11:23 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* Set the horizontal corner radius (from a Border dictionary).
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @memberof AnnotationBorderStyle
|
|
|
|
* @param {integer} radius - The horizontal corner radius
|
|
|
|
*/
|
|
|
|
setHorizontalCornerRadius(radius) {
|
2017-09-03 19:50:16 +09:00
|
|
|
if (Number.isInteger(radius)) {
|
2017-08-27 06:49:11 +09:00
|
|
|
this.horizontalCornerRadius = radius;
|
|
|
|
}
|
|
|
|
}
|
2014-12-26 04:11:23 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* Set the vertical corner radius (from a Border dictionary).
|
|
|
|
*
|
|
|
|
* @public
|
|
|
|
* @memberof AnnotationBorderStyle
|
|
|
|
* @param {integer} radius - The vertical corner radius
|
|
|
|
*/
|
|
|
|
setVerticalCornerRadius(radius) {
|
2017-09-03 19:50:16 +09:00
|
|
|
if (Number.isInteger(radius)) {
|
2017-08-27 06:49:11 +09:00
|
|
|
this.verticalCornerRadius = radius;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class WidgetAnnotation extends Annotation {
|
|
|
|
constructor(params) {
|
|
|
|
super(params);
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
let dict = params.dict;
|
|
|
|
let data = this.data;
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2015-12-15 22:52:17 +09:00
|
|
|
data.annotationType = AnnotationType.WIDGET;
|
2016-11-02 08:07:31 +09:00
|
|
|
data.fieldName = this._constructFieldName(dict);
|
2018-02-19 05:42:33 +09:00
|
|
|
data.fieldValue = getInheritableProperty({ dict, key: 'V',
|
|
|
|
getArray: true, });
|
2013-03-21 17:04:44 +09:00
|
|
|
data.alternativeText = stringToPDFString(dict.get('TU') || '');
|
2018-02-19 05:42:33 +09:00
|
|
|
data.defaultAppearance = getInheritableProperty({ dict, key: 'DA', }) || '';
|
|
|
|
let fieldType = getInheritableProperty({ dict, key: 'FT', });
|
2016-09-06 06:46:52 +09:00
|
|
|
data.fieldType = isName(fieldType) ? fieldType.name : null;
|
2018-02-19 05:42:33 +09:00
|
|
|
this.fieldResources = getInheritableProperty({ dict, key: 'DR', }) ||
|
|
|
|
Dict.empty;
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2018-02-19 05:42:33 +09:00
|
|
|
data.fieldFlags = getInheritableProperty({ dict, key: 'Ff', });
|
2017-09-01 23:52:50 +09:00
|
|
|
if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) {
|
2016-09-14 23:32:51 +09:00
|
|
|
data.fieldFlags = 0;
|
|
|
|
}
|
|
|
|
|
2016-09-25 08:45:49 +09:00
|
|
|
data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
|
|
|
|
|
2018-12-12 19:04:41 +09:00
|
|
|
// Hide signatures because we cannot validate them, and unset the fieldValue
|
|
|
|
// since it's (most likely) a `Dict` which is non-serializable and will thus
|
|
|
|
// cause errors when sending annotations to the main-thread (issue 10347).
|
2015-11-22 07:25:17 +09:00
|
|
|
if (data.fieldType === 'Sig') {
|
2018-12-12 19:04:41 +09:00
|
|
|
data.fieldValue = null;
|
2015-11-22 07:25:17 +09:00
|
|
|
this.setFlags(AnnotationFlag.HIDDEN);
|
|
|
|
}
|
2016-11-02 08:07:31 +09:00
|
|
|
}
|
2015-11-22 07:25:17 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* Construct the (fully qualified) field name from the (partial) field
|
|
|
|
* names of the field and its ancestors.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @memberof WidgetAnnotation
|
|
|
|
* @param {Dict} dict - Complete widget annotation dictionary
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
_constructFieldName(dict) {
|
|
|
|
// Both the `Parent` and `T` fields are optional. While at least one of
|
|
|
|
// them should be provided, bad PDF generators may fail to do so.
|
|
|
|
if (!dict.has('T') && !dict.has('Parent')) {
|
|
|
|
warn('Unknown field name, falling back to empty field name.');
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no parent exists, the partial and fully qualified names are equal.
|
|
|
|
if (!dict.has('Parent')) {
|
|
|
|
return stringToPDFString(dict.get('T'));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Form the fully qualified field name by appending the partial name to
|
|
|
|
// the parent's fully qualified name, separated by a period.
|
|
|
|
let fieldName = [];
|
|
|
|
if (dict.has('T')) {
|
|
|
|
fieldName.unshift(stringToPDFString(dict.get('T')));
|
|
|
|
}
|
2016-11-02 08:07:31 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
let loopDict = dict;
|
|
|
|
while (loopDict.has('Parent')) {
|
|
|
|
loopDict = loopDict.get('Parent');
|
|
|
|
if (!isDict(loopDict)) {
|
|
|
|
// Even though it is not allowed according to the PDF specification,
|
|
|
|
// bad PDF generators may provide a `Parent` entry that is not a
|
|
|
|
// dictionary, but `null` for example (issue 8143).
|
|
|
|
break;
|
2016-11-02 08:07:31 +09:00
|
|
|
}
|
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
if (loopDict.has('T')) {
|
|
|
|
fieldName.unshift(stringToPDFString(loopDict.get('T')));
|
2016-11-02 08:07:31 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
|
|
|
return fieldName.join('.');
|
|
|
|
}
|
2016-11-02 08:07:31 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
/**
|
|
|
|
* 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(flag) {
|
|
|
|
return !!(this.data.fieldFlags & flag);
|
|
|
|
}
|
2017-08-27 07:48:02 +09:00
|
|
|
|
|
|
|
getOperatorList(evaluator, task, renderForms) {
|
|
|
|
// 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(new OperatorList());
|
|
|
|
}
|
|
|
|
return super.getOperatorList(evaluator, task, renderForms);
|
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2016-11-02 08:07:31 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class TextWidgetAnnotation extends WidgetAnnotation {
|
|
|
|
constructor(params) {
|
|
|
|
super(params);
|
2013-03-26 07:32:47 +09:00
|
|
|
|
2018-02-19 05:42:33 +09:00
|
|
|
const dict = params.dict;
|
|
|
|
|
2016-09-25 08:45:49 +09:00
|
|
|
// The field value is always a string.
|
|
|
|
this.data.fieldValue = stringToPDFString(this.data.fieldValue || '');
|
|
|
|
|
2016-09-13 21:57:11 +09:00
|
|
|
// Determine the alignment of text in the field.
|
2018-02-19 05:42:33 +09:00
|
|
|
let alignment = getInheritableProperty({ dict, key: 'Q', });
|
2017-09-01 23:52:50 +09:00
|
|
|
if (!Number.isInteger(alignment) || alignment < 0 || alignment > 2) {
|
2016-09-13 21:57:11 +09:00
|
|
|
alignment = null;
|
|
|
|
}
|
|
|
|
this.data.textAlignment = alignment;
|
|
|
|
|
|
|
|
// Determine the maximum length of text in the field.
|
2018-02-19 05:42:33 +09:00
|
|
|
let maximumLength = getInheritableProperty({ dict, key: 'MaxLen', });
|
2017-09-01 23:52:50 +09:00
|
|
|
if (!Number.isInteger(maximumLength) || maximumLength < 0) {
|
2016-09-13 21:57:11 +09:00
|
|
|
maximumLength = null;
|
|
|
|
}
|
|
|
|
this.data.maxLen = maximumLength;
|
2016-09-14 23:32:51 +09:00
|
|
|
|
|
|
|
// Process field flags for the display layer.
|
|
|
|
this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE);
|
2016-09-20 07:04:11 +09:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
getOperatorList(evaluator, task, renderForms) {
|
2017-08-27 07:48:02 +09:00
|
|
|
if (renderForms || this.appearance) {
|
|
|
|
return super.getOperatorList(evaluator, task, renderForms);
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2013-05-29 10:16:21 +09:00
|
|
|
|
2017-08-27 07:48:02 +09:00
|
|
|
let operatorList = new OperatorList();
|
2013-03-26 07:32:47 +09:00
|
|
|
|
2017-08-27 06:49:11 +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
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
let stream = new Stream(stringToBytes(this.data.defaultAppearance));
|
|
|
|
return evaluator.getOperatorList({
|
|
|
|
stream,
|
|
|
|
task,
|
|
|
|
resources: this.fieldResources,
|
|
|
|
operatorList,
|
|
|
|
}).then(function () {
|
|
|
|
return operatorList;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2013-03-26 07:32:47 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class ButtonWidgetAnnotation extends WidgetAnnotation {
|
|
|
|
constructor(params) {
|
|
|
|
super(params);
|
2016-11-04 21:01:42 +09:00
|
|
|
|
2016-12-16 07:49:46 +09:00
|
|
|
this.data.checkBox = !this.hasFieldFlag(AnnotationFieldFlag.RADIO) &&
|
|
|
|
!this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
|
2017-11-21 07:00:19 +09:00
|
|
|
this.data.radioButton = this.hasFieldFlag(AnnotationFieldFlag.RADIO) &&
|
|
|
|
!this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
|
|
|
|
this.data.pushButton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
|
|
|
|
|
2016-12-16 07:49:46 +09:00
|
|
|
if (this.data.checkBox) {
|
2018-07-07 09:51:10 +09:00
|
|
|
this._processCheckBox(params);
|
2017-11-21 07:00:19 +09:00
|
|
|
} else if (this.data.radioButton) {
|
|
|
|
this._processRadioButton(params);
|
|
|
|
} else if (this.data.pushButton) {
|
|
|
|
this._processPushButton(params);
|
|
|
|
} else {
|
|
|
|
warn('Invalid field flags for button widget annotation');
|
2016-11-04 21:01:42 +09:00
|
|
|
}
|
2017-11-21 07:00:19 +09:00
|
|
|
}
|
2016-12-16 06:58:27 +09:00
|
|
|
|
2018-07-07 09:51:10 +09:00
|
|
|
_processCheckBox(params) {
|
|
|
|
if (isName(this.data.fieldValue)) {
|
|
|
|
this.data.fieldValue = this.data.fieldValue.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
const customAppearance = params.dict.get('AP');
|
|
|
|
if (!isDict(customAppearance)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const exportValueOptionsDict = customAppearance.get('D');
|
|
|
|
if (!isDict(exportValueOptionsDict)) {
|
2017-11-21 07:00:19 +09:00
|
|
|
return;
|
|
|
|
}
|
2018-07-07 09:51:10 +09:00
|
|
|
|
|
|
|
const exportValues = exportValueOptionsDict.getKeys();
|
|
|
|
const hasCorrectOptionCount = exportValues.length === 2;
|
|
|
|
if (!hasCorrectOptionCount) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.data.exportValue = exportValues[0] === 'Off' ?
|
|
|
|
exportValues[1] : exportValues[0];
|
2017-11-21 07:00:19 +09:00
|
|
|
}
|
2016-12-16 06:58:27 +09:00
|
|
|
|
2017-11-21 07:00:19 +09:00
|
|
|
_processRadioButton(params) {
|
|
|
|
this.data.fieldValue = this.data.buttonValue = null;
|
|
|
|
|
|
|
|
// The parent field's `V` entry holds a `Name` object with the appearance
|
|
|
|
// state of whichever child field is currently in the "on" state.
|
|
|
|
let fieldParent = params.dict.get('Parent');
|
|
|
|
if (isDict(fieldParent) && fieldParent.has('V')) {
|
|
|
|
let fieldParentValue = fieldParent.get('V');
|
|
|
|
if (isName(fieldParentValue)) {
|
|
|
|
this.data.fieldValue = fieldParentValue.name;
|
2016-12-16 06:58:27 +09:00
|
|
|
}
|
2017-11-21 07:00:19 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// The button's value corresponds to its appearance state.
|
|
|
|
let appearanceStates = params.dict.get('AP');
|
|
|
|
if (!isDict(appearanceStates)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let normalAppearanceState = appearanceStates.get('N');
|
|
|
|
if (!isDict(normalAppearanceState)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let keys = normalAppearanceState.getKeys();
|
|
|
|
for (let i = 0, ii = keys.length; i < ii; i++) {
|
|
|
|
if (keys[i] !== 'Off') {
|
|
|
|
this.data.buttonValue = keys[i];
|
|
|
|
break;
|
2016-11-04 21:01:42 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-11-21 07:00:19 +09:00
|
|
|
|
|
|
|
_processPushButton(params) {
|
|
|
|
if (!params.dict.has('A')) {
|
|
|
|
warn('Push buttons without action dictionaries are not supported');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Catalog.parseDestDictionary({
|
|
|
|
destDict: params.dict,
|
|
|
|
resultObj: this.data,
|
|
|
|
docBaseUrl: params.pdfManager.docBaseUrl,
|
|
|
|
});
|
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2016-11-04 21:01:42 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class ChoiceWidgetAnnotation extends WidgetAnnotation {
|
|
|
|
constructor(params) {
|
|
|
|
super(params);
|
2016-09-25 08:45:49 +09:00
|
|
|
|
|
|
|
// Determine the options. The options array may consist of strings or
|
|
|
|
// arrays. If the array consists of arrays, then the first element of
|
|
|
|
// each array is the export value and the second element of each array is
|
|
|
|
// the display value. If the array consists of strings, then these
|
|
|
|
// represent both the export and display value. In this case, we convert
|
|
|
|
// it to an array of arrays as well for convenience in the display layer.
|
2017-02-26 07:34:26 +09:00
|
|
|
// Note that the specification does not state that the `Opt` field is
|
|
|
|
// inheritable, but in practice PDF generators do make annotations
|
|
|
|
// inherit the options from a parent annotation (issue 8094).
|
2016-09-25 08:45:49 +09:00
|
|
|
this.data.options = [];
|
|
|
|
|
2018-02-19 05:42:33 +09:00
|
|
|
let options = getInheritableProperty({ dict: params.dict, key: 'Opt', });
|
2017-09-02 03:27:13 +09:00
|
|
|
if (Array.isArray(options)) {
|
2017-08-27 06:49:11 +09:00
|
|
|
let xref = params.xref;
|
|
|
|
for (let i = 0, ii = options.length; i < ii; i++) {
|
|
|
|
let option = xref.fetchIfRef(options[i]);
|
2017-09-02 03:27:13 +09:00
|
|
|
let isOptionArray = Array.isArray(option);
|
2016-09-25 08:45:49 +09:00
|
|
|
|
|
|
|
this.data.options[i] = {
|
2016-12-17 21:34:18 +09:00
|
|
|
exportValue: isOptionArray ? xref.fetchIfRef(option[0]) : option,
|
2017-11-19 00:47:32 +09:00
|
|
|
displayValue: stringToPDFString(isOptionArray ?
|
|
|
|
xref.fetchIfRef(option[1]) : option),
|
2016-09-25 08:45:49 +09:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the field value. In this case, it may be a string or an
|
|
|
|
// array of strings. For convenience in the display layer, convert the
|
|
|
|
// string to an array of one string as well.
|
2017-09-02 03:27:13 +09:00
|
|
|
if (!Array.isArray(this.data.fieldValue)) {
|
2016-09-25 08:45:49 +09:00
|
|
|
this.data.fieldValue = [this.data.fieldValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process field flags for the display layer.
|
|
|
|
this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
|
|
|
|
this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
|
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2016-09-25 08:45:49 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class TextAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
const DEFAULT_ICON_SIZE = 22; // px
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
super(parameters);
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2015-12-23 05:31:56 +09:00
|
|
|
this.data.annotationType = AnnotationType.TEXT;
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2015-12-23 05:31:56 +09:00
|
|
|
if (this.data.hasAppearance) {
|
|
|
|
this.data.name = 'NoIcon';
|
2014-03-07 23:48:42 +09:00
|
|
|
} else {
|
2015-12-23 05:31:56 +09:00
|
|
|
this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE;
|
|
|
|
this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE;
|
2016-02-23 08:21:28 +09:00
|
|
|
this.data.name = parameters.dict.has('Name') ?
|
|
|
|
parameters.dict.get('Name').name : 'Note';
|
2015-12-23 05:31:56 +09:00
|
|
|
}
|
2016-02-23 08:21:28 +09:00
|
|
|
this._preparePopup(parameters.dict);
|
2014-03-07 23:48:42 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class LinkAnnotation extends Annotation {
|
|
|
|
constructor(params) {
|
|
|
|
super(params);
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
this.data.annotationType = AnnotationType.LINK;
|
2013-03-21 17:04:44 +09:00
|
|
|
|
2016-09-30 23:08:03 +09:00
|
|
|
Catalog.parseDestDictionary({
|
|
|
|
destDict: params.dict,
|
2017-08-27 06:49:11 +09:00
|
|
|
resultObj: this.data,
|
2016-10-01 19:05:07 +09:00
|
|
|
docBaseUrl: params.pdfManager.docBaseUrl,
|
2016-09-30 23:08:03 +09:00
|
|
|
});
|
2016-03-03 21:07:22 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2016-03-03 21:07:22 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class PopupAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
2015-12-23 05:31:56 +09:00
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.POPUP;
|
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
let dict = parameters.dict;
|
|
|
|
let parentItem = dict.get('Parent');
|
2015-12-23 05:31:56 +09:00
|
|
|
if (!parentItem) {
|
|
|
|
warn('Popup annotation has a missing or invalid parent annotation.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
let parentSubtype = parentItem.get('Subtype');
|
2017-04-03 03:50:17 +09:00
|
|
|
this.data.parentType = isName(parentSubtype) ? parentSubtype.name : null;
|
2015-12-23 05:31:56 +09:00
|
|
|
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 {
|
2016-05-06 02:16:35 +09:00
|
|
|
this.setColor(parentItem.getArray('C'));
|
2015-12-23 05:31:56 +09:00
|
|
|
this.data.color = this.color;
|
|
|
|
}
|
2016-05-25 00:35:45 +09:00
|
|
|
|
|
|
|
// 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) {
|
2017-08-27 06:49:11 +09:00
|
|
|
let parentFlags = parentItem.get('F');
|
2016-05-25 00:35:45 +09:00
|
|
|
if (this._isViewable(parentFlags)) {
|
|
|
|
this.setFlags(parentFlags);
|
|
|
|
}
|
|
|
|
}
|
2015-12-23 05:31:56 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2015-12-23 05:31:56 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class LineAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
2017-04-03 03:50:17 +09:00
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.LINE;
|
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
let dict = parameters.dict;
|
2017-04-03 03:50:17 +09:00
|
|
|
this.data.lineCoordinates = Util.normalizeRect(dict.getArray('L'));
|
|
|
|
this._preparePopup(dict);
|
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2017-04-03 03:50:17 +09:00
|
|
|
|
2017-07-24 07:11:27 +09:00
|
|
|
class SquareAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.SQUARE;
|
|
|
|
this._preparePopup(parameters.dict);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-24 07:30:58 +09:00
|
|
|
class CircleAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.CIRCLE;
|
|
|
|
this._preparePopup(parameters.dict);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-18 03:18:22 +09:00
|
|
|
class PolylineAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.POLYLINE;
|
|
|
|
|
|
|
|
// The vertices array is an array of numbers representing the alternating
|
|
|
|
// horizontal and vertical coordinates, respectively, of each vertex.
|
|
|
|
// Convert this to an array of objects with x and y coordinates.
|
|
|
|
let dict = parameters.dict;
|
|
|
|
let rawVertices = dict.getArray('Vertices');
|
|
|
|
|
|
|
|
this.data.vertices = [];
|
|
|
|
for (let i = 0, ii = rawVertices.length; i < ii; i += 2) {
|
|
|
|
this.data.vertices.push({
|
|
|
|
x: rawVertices[i],
|
|
|
|
y: rawVertices[i + 1],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
this._preparePopup(dict);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-23 23:50:49 +09:00
|
|
|
class PolygonAnnotation extends PolylineAnnotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
// Polygons are specific forms of polylines, so reuse their logic.
|
|
|
|
super(parameters);
|
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.POLYGON;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-10 06:35:32 +09:00
|
|
|
class CaretAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.CARET;
|
|
|
|
this._preparePopup(parameters.dict);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-30 23:29:16 +09:00
|
|
|
class InkAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.INK;
|
|
|
|
|
|
|
|
let dict = parameters.dict;
|
|
|
|
const xref = parameters.xref;
|
|
|
|
|
|
|
|
let originalInkLists = dict.getArray('InkList');
|
|
|
|
this.data.inkLists = [];
|
|
|
|
for (let i = 0, ii = originalInkLists.length; i < ii; ++i) {
|
|
|
|
// The raw ink lists array contains arrays of numbers representing
|
|
|
|
// the alternating horizontal and vertical coordinates, respectively,
|
|
|
|
// of each vertex. Convert this to an array of objects with x and y
|
|
|
|
// coordinates.
|
|
|
|
this.data.inkLists.push([]);
|
|
|
|
for (let j = 0, jj = originalInkLists[i].length; j < jj; j += 2) {
|
|
|
|
this.data.inkLists[i].push({
|
|
|
|
x: xref.fetchIfRef(originalInkLists[i][j]),
|
|
|
|
y: xref.fetchIfRef(originalInkLists[i][j + 1]),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._preparePopup(dict);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class HighlightAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
2016-01-01 23:31:46 +09:00
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.HIGHLIGHT;
|
2016-02-23 08:21:28 +09:00
|
|
|
this._preparePopup(parameters.dict);
|
2016-01-01 23:31:46 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2016-01-01 23:31:46 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class UnderlineAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
2015-12-28 08:33:41 +09:00
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.UNDERLINE;
|
2016-02-23 08:21:28 +09:00
|
|
|
this._preparePopup(parameters.dict);
|
2015-12-28 08:33:41 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2015-12-28 08:33:41 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class SquigglyAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
2015-12-30 23:28:26 +09:00
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.SQUIGGLY;
|
2016-02-23 08:21:28 +09:00
|
|
|
this._preparePopup(parameters.dict);
|
2015-12-30 23:28:26 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2015-12-30 23:28:26 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class StrikeOutAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
2015-12-29 23:09:28 +09:00
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.STRIKEOUT;
|
2016-02-23 08:21:28 +09:00
|
|
|
this._preparePopup(parameters.dict);
|
2015-12-29 23:09:28 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2015-12-29 23:09:28 +09:00
|
|
|
|
2017-09-16 23:37:50 +09:00
|
|
|
class StampAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.STAMP;
|
|
|
|
this._preparePopup(parameters.dict);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
class FileAttachmentAnnotation extends Annotation {
|
|
|
|
constructor(parameters) {
|
|
|
|
super(parameters);
|
2015-12-29 23:09:28 +09:00
|
|
|
|
2017-08-27 06:49:11 +09:00
|
|
|
let file = new FileSpec(parameters.dict.get('FS'), parameters.xref);
|
2016-02-15 04:44:00 +09:00
|
|
|
|
|
|
|
this.data.annotationType = AnnotationType.FILEATTACHMENT;
|
|
|
|
this.data.file = file.serializable;
|
2016-02-23 08:21:28 +09:00
|
|
|
this._preparePopup(parameters.dict);
|
2016-02-15 04:44:00 +09:00
|
|
|
}
|
2017-08-27 06:49:11 +09:00
|
|
|
}
|
2016-02-15 04:44:00 +09:00
|
|
|
|
2017-04-02 23:14:30 +09:00
|
|
|
export {
|
|
|
|
Annotation,
|
|
|
|
AnnotationBorderStyle,
|
|
|
|
AnnotationFactory,
|
|
|
|
};
|