mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
Implement creation/modification date for annotations
This includes the information in the core and display layers. The date parsing logic from the document properties is rewritten according to the specification and now includes unit tests. Moreover, missing unit tests for the color of a popup annotation have been added. Finally the styling of the popup is changed slightly to make the text a bit smaller (it's currently quite large in comparison to other viewers) and to make the drop shadow a bit more subtle. The former is done to be able to easily include the modification date in the popup similar to how other viewers do this.
This commit is contained in:
parent
6cfb1e1a63
commit
be1d6626a7
11 changed files with 343 additions and 59 deletions
|
@ -16,7 +16,7 @@
|
|||
|
||||
import {
|
||||
AnnotationBorderStyleType, AnnotationFieldFlag, AnnotationFlag,
|
||||
AnnotationType, OPS, stringToBytes, stringToPDFString, Util, warn
|
||||
AnnotationType, isString, OPS, stringToBytes, stringToPDFString, Util, warn
|
||||
} from '../shared/util';
|
||||
import { Catalog, FileSpec, ObjectLoader } from './obj';
|
||||
import { Dict, isDict, isName, isRef, isStream } from './primitives';
|
||||
|
@ -176,6 +176,8 @@ class Annotation {
|
|||
constructor(params) {
|
||||
let dict = params.dict;
|
||||
|
||||
this.setCreationDate(dict.get('CreationDate'));
|
||||
this.setModificationDate(dict.get('M'));
|
||||
this.setFlags(dict.get('F'));
|
||||
this.setRectangle(dict.getArray('Rect'));
|
||||
this.setColor(dict.getArray('C'));
|
||||
|
@ -187,8 +189,10 @@ class Annotation {
|
|||
annotationFlags: this.flags,
|
||||
borderStyle: this.borderStyle,
|
||||
color: this.color,
|
||||
creationDate: this.creationDate,
|
||||
hasAppearance: !!this.appearance,
|
||||
id: params.id,
|
||||
modificationDate: this.modificationDate,
|
||||
rect: this.rectangle,
|
||||
subtype: params.subtype,
|
||||
};
|
||||
|
@ -239,6 +243,31 @@ class Annotation {
|
|||
return this._isPrintable(this.flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the creation date.
|
||||
*
|
||||
* @public
|
||||
* @memberof Annotation
|
||||
* @param {string} creationDate - PDF date string that indicates when the
|
||||
* annotation was originally created
|
||||
*/
|
||||
setCreationDate(creationDate) {
|
||||
this.creationDate = isString(creationDate) ? creationDate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the modification date.
|
||||
*
|
||||
* @public
|
||||
* @memberof Annotation
|
||||
* @param {string} modificationDate - PDF date string that indicates when the
|
||||
* annotation was last modified
|
||||
*/
|
||||
setModificationDate(modificationDate) {
|
||||
this.modificationDate = isString(modificationDate) ?
|
||||
modificationDate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flags.
|
||||
*
|
||||
|
@ -947,6 +976,20 @@ class PopupAnnotation extends Annotation {
|
|||
this.data.title = stringToPDFString(parentItem.get('T') || '');
|
||||
this.data.contents = stringToPDFString(parentItem.get('Contents') || '');
|
||||
|
||||
if (!parentItem.has('CreationDate')) {
|
||||
this.data.creationDate = null;
|
||||
} else {
|
||||
this.setCreationDate(parentItem.get('CreationDate'));
|
||||
this.data.creationDate = this.creationDate;
|
||||
}
|
||||
|
||||
if (!parentItem.has('M')) {
|
||||
this.data.modificationDate = null;
|
||||
} else {
|
||||
this.setModificationDate(parentItem.get('M'));
|
||||
this.data.modificationDate = this.modificationDate;
|
||||
}
|
||||
|
||||
if (!parentItem.has('C')) {
|
||||
// Fall back to the default background color.
|
||||
this.data.color = null;
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
addLinkAttributes, DOMSVGFactory, getFilenameFromUrl, LinkTarget
|
||||
addLinkAttributes, DOMSVGFactory, getFilenameFromUrl, LinkTarget,
|
||||
PDFDateString
|
||||
} from './display_utils';
|
||||
import {
|
||||
AnnotationBorderStyleType, AnnotationType, stringToPDFString, unreachable,
|
||||
|
@ -251,6 +252,7 @@ class AnnotationElement {
|
|||
trigger,
|
||||
color: data.color,
|
||||
title: data.title,
|
||||
modificationDate: data.modificationDate,
|
||||
contents: data.contents,
|
||||
hideWrapper: true,
|
||||
});
|
||||
|
@ -664,6 +666,7 @@ class PopupAnnotationElement extends AnnotationElement {
|
|||
trigger: parentElement,
|
||||
color: this.data.color,
|
||||
title: this.data.title,
|
||||
modificationDate: this.data.modificationDate,
|
||||
contents: this.data.contents,
|
||||
});
|
||||
|
||||
|
@ -686,6 +689,7 @@ class PopupElement {
|
|||
this.trigger = parameters.trigger;
|
||||
this.color = parameters.color;
|
||||
this.title = parameters.title;
|
||||
this.modificationDate = parameters.modificationDate;
|
||||
this.contents = parameters.contents;
|
||||
this.hideWrapper = parameters.hideWrapper || false;
|
||||
|
||||
|
@ -724,9 +728,27 @@ class PopupElement {
|
|||
popup.style.backgroundColor = Util.makeCssRgb(r | 0, g | 0, b | 0);
|
||||
}
|
||||
|
||||
let contents = this._formatContents(this.contents);
|
||||
let title = document.createElement('h1');
|
||||
title.textContent = this.title;
|
||||
popup.appendChild(title);
|
||||
|
||||
// The modification date is shown in the popup instead of the creation
|
||||
// date if it is available and can be parsed correctly, which is
|
||||
// consistent with other viewers such as Adobe Acrobat.
|
||||
const dateObject = PDFDateString.toDateObject(this.modificationDate);
|
||||
if (dateObject) {
|
||||
const modificationDate = document.createElement('span');
|
||||
modificationDate.textContent = '{{date}}, {{time}}';
|
||||
modificationDate.dataset.l10nId = 'annotation_date_string';
|
||||
modificationDate.dataset.l10nArgs = JSON.stringify({
|
||||
date: dateObject.toLocaleDateString(),
|
||||
time: dateObject.toLocaleTimeString(),
|
||||
});
|
||||
popup.appendChild(modificationDate);
|
||||
}
|
||||
|
||||
let contents = this._formatContents(this.contents);
|
||||
popup.appendChild(contents);
|
||||
|
||||
// Attach the event listeners to the trigger element.
|
||||
this.trigger.addEventListener('click', this._toggle.bind(this));
|
||||
|
@ -734,8 +756,6 @@ class PopupElement {
|
|||
this.trigger.addEventListener('mouseout', this._hide.bind(this, false));
|
||||
popup.addEventListener('click', this._hide.bind(this, true));
|
||||
|
||||
popup.appendChild(title);
|
||||
popup.appendChild(contents);
|
||||
wrapper.appendChild(popup);
|
||||
return wrapper;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
/* eslint no-var: error */
|
||||
|
||||
import {
|
||||
assert, CMapCompressionType, removeNullCharacters, stringToBytes,
|
||||
assert, CMapCompressionType, isString, removeNullCharacters, stringToBytes,
|
||||
unreachable, URL, Util, warn
|
||||
} from '../shared/util';
|
||||
|
||||
|
@ -491,6 +491,91 @@ function releaseImageResources(img) {
|
|||
img.removeAttribute('src');
|
||||
}
|
||||
|
||||
let pdfDateStringRegex;
|
||||
|
||||
class PDFDateString {
|
||||
/**
|
||||
* Convert a PDF date string to a JavaScript `Date` object.
|
||||
*
|
||||
* The PDF date string format is described in section 7.9.4 of the official
|
||||
* PDF 32000-1:2008 specification. However, in the PDF 1.7 reference (sixth
|
||||
* edition) Adobe describes the same format including a trailing apostrophe.
|
||||
* This syntax in incorrect, but Adobe Acrobat creates PDF files that contain
|
||||
* them. We ignore all apostrophes as they are not necessary for date parsing.
|
||||
*
|
||||
* Moreover, Adobe Acrobat doesn't handle changing the date to universal time
|
||||
* and doesn't use the user's time zone (effectively ignoring the HH' and mm'
|
||||
* parts of the date string).
|
||||
*
|
||||
* @param {string} input
|
||||
* @return {Date|null}
|
||||
*/
|
||||
static toDateObject(input) {
|
||||
if (!input || !isString(input)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Lazily initialize the regular expression.
|
||||
if (!pdfDateStringRegex) {
|
||||
pdfDateStringRegex = new RegExp(
|
||||
'^D:' + // Prefix (required)
|
||||
'(\\d{4})' + // Year (required)
|
||||
'(\\d{2})?' + // Month (optional)
|
||||
'(\\d{2})?' + // Day (optional)
|
||||
'(\\d{2})?' + // Hour (optional)
|
||||
'(\\d{2})?' + // Minute (optional)
|
||||
'(\\d{2})?' + // Second (optional)
|
||||
'([Z|+|-])?' + // Universal time relation (optional)
|
||||
'(\\d{2})?' + // Offset hour (optional)
|
||||
'\'?' + // Splitting apostrophe (optional)
|
||||
'(\\d{2})?' + // Offset minute (optional)
|
||||
'\'?' // Trailing apostrophe (optional)
|
||||
);
|
||||
}
|
||||
|
||||
// Optional fields that don't satisfy the requirements from the regular
|
||||
// expression (such as incorrect digit counts or numbers that are out of
|
||||
// range) will fall back the defaults from the specification.
|
||||
const matches = pdfDateStringRegex.exec(input);
|
||||
if (!matches) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// JavaScript's `Date` object expects the month to be between 0 and 11
|
||||
// instead of 1 and 12, so we have to correct for that.
|
||||
const year = parseInt(matches[1], 10);
|
||||
let month = parseInt(matches[2], 10);
|
||||
month = (month >= 1 && month <= 12) ? month - 1 : 0;
|
||||
let day = parseInt(matches[3], 10);
|
||||
day = (day >= 1 && day <= 31) ? day : 1;
|
||||
let hour = parseInt(matches[4], 10);
|
||||
hour = (hour >= 0 && hour <= 23) ? hour : 0;
|
||||
let minute = parseInt(matches[5], 10);
|
||||
minute = (minute >= 0 && minute <= 59) ? minute : 0;
|
||||
let second = parseInt(matches[6], 10);
|
||||
second = (second >= 0 && second <= 59) ? second : 0;
|
||||
const universalTimeRelation = matches[7] || 'Z';
|
||||
let offsetHour = parseInt(matches[8], 10);
|
||||
offsetHour = (offsetHour >= 0 && offsetHour <= 23) ? offsetHour : 0;
|
||||
let offsetMinute = parseInt(matches[9], 10) || 0;
|
||||
offsetMinute = (offsetMinute >= 0 && offsetMinute <= 59) ? offsetMinute : 0;
|
||||
|
||||
// Universal time relation 'Z' means that the local time is equal to the
|
||||
// universal time, whereas the relations '+'/'-' indicate that the local
|
||||
// time is later respectively earlier than the universal time. Every date
|
||||
// is normalized to universal time.
|
||||
if (universalTimeRelation === '-') {
|
||||
hour += offsetHour;
|
||||
minute += offsetMinute;
|
||||
} else if (universalTimeRelation === '+') {
|
||||
hour -= offsetHour;
|
||||
minute -= offsetMinute;
|
||||
}
|
||||
|
||||
return new Date(Date.UTC(year, month, day, hour, minute, second));
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
PageViewport,
|
||||
RenderingCancelledException,
|
||||
|
@ -508,4 +593,5 @@ export {
|
|||
loadScript,
|
||||
deprecated,
|
||||
releaseImageResources,
|
||||
PDFDateString,
|
||||
};
|
||||
|
|
|
@ -114,6 +114,7 @@ exports.getFilenameFromUrl = pdfjsDisplayDisplayUtils.getFilenameFromUrl;
|
|||
exports.LinkTarget = pdfjsDisplayDisplayUtils.LinkTarget;
|
||||
exports.addLinkAttributes = pdfjsDisplayDisplayUtils.addLinkAttributes;
|
||||
exports.loadScript = pdfjsDisplayDisplayUtils.loadScript;
|
||||
exports.PDFDateString = pdfjsDisplayDisplayUtils.PDFDateString;
|
||||
exports.GlobalWorkerOptions = pdfjsDisplayWorkerOptions.GlobalWorkerOptions;
|
||||
exports.apiCompatibilityParams =
|
||||
pdfjsDisplayAPICompatibility.apiCompatibilityParams;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue