Merge pull request #10771 from timvandermeij/annotation-dates
[api-minor] Implement creation/modification date for annotations
This commit is contained in:
commit
83f6de3cf8
@ -226,6 +226,10 @@ invalid_file_error=Invalid or corrupted PDF file.
|
|||||||
missing_file_error=Missing PDF file.
|
missing_file_error=Missing PDF file.
|
||||||
unexpected_response_error=Unexpected server response.
|
unexpected_response_error=Unexpected server response.
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (annotation_date_string): "{{date}}" and "{{time}}" will be
|
||||||
|
# replaced by the modification date, and time, of the annotation.
|
||||||
|
annotation_date_string={{date}}, {{time}}
|
||||||
|
|
||||||
# LOCALIZATION NOTE (text_annotation_type.alt): This is used as a tooltip.
|
# LOCALIZATION NOTE (text_annotation_type.alt): This is used as a tooltip.
|
||||||
# "{{type}}" will be replaced with an annotation type from a list defined in
|
# "{{type}}" will be replaced with an annotation type from a list defined in
|
||||||
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
||||||
|
@ -226,6 +226,10 @@ invalid_file_error=Ongeldig of beschadigd PDF-bestand.
|
|||||||
missing_file_error=PDF-bestand ontbreekt.
|
missing_file_error=PDF-bestand ontbreekt.
|
||||||
unexpected_response_error=Onverwacht serverantwoord.
|
unexpected_response_error=Onverwacht serverantwoord.
|
||||||
|
|
||||||
|
# LOCALIZATION NOTE (annotation_date_string): "{{date}}" and "{{time}}" will be
|
||||||
|
# replaced by the modification date, and time, of the annotation.
|
||||||
|
annotation_date_string={{date}}, {{time}}
|
||||||
|
|
||||||
# LOCALIZATION NOTE (text_annotation_type.alt): This is used as a tooltip.
|
# LOCALIZATION NOTE (text_annotation_type.alt): This is used as a tooltip.
|
||||||
# "{{type}}" will be replaced with an annotation type from a list defined in
|
# "{{type}}" will be replaced with an annotation type from a list defined in
|
||||||
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AnnotationBorderStyleType, AnnotationFieldFlag, AnnotationFlag,
|
AnnotationBorderStyleType, AnnotationFieldFlag, AnnotationFlag,
|
||||||
AnnotationType, OPS, stringToBytes, stringToPDFString, Util, warn
|
AnnotationType, isString, OPS, stringToBytes, stringToPDFString, Util, warn
|
||||||
} from '../shared/util';
|
} from '../shared/util';
|
||||||
import { Catalog, FileSpec, ObjectLoader } from './obj';
|
import { Catalog, FileSpec, ObjectLoader } from './obj';
|
||||||
import { Dict, isDict, isName, isRef, isStream } from './primitives';
|
import { Dict, isDict, isName, isRef, isStream } from './primitives';
|
||||||
@ -176,6 +176,8 @@ class Annotation {
|
|||||||
constructor(params) {
|
constructor(params) {
|
||||||
let dict = params.dict;
|
let dict = params.dict;
|
||||||
|
|
||||||
|
this.setCreationDate(dict.get('CreationDate'));
|
||||||
|
this.setModificationDate(dict.get('M'));
|
||||||
this.setFlags(dict.get('F'));
|
this.setFlags(dict.get('F'));
|
||||||
this.setRectangle(dict.getArray('Rect'));
|
this.setRectangle(dict.getArray('Rect'));
|
||||||
this.setColor(dict.getArray('C'));
|
this.setColor(dict.getArray('C'));
|
||||||
@ -187,8 +189,10 @@ class Annotation {
|
|||||||
annotationFlags: this.flags,
|
annotationFlags: this.flags,
|
||||||
borderStyle: this.borderStyle,
|
borderStyle: this.borderStyle,
|
||||||
color: this.color,
|
color: this.color,
|
||||||
|
creationDate: this.creationDate,
|
||||||
hasAppearance: !!this.appearance,
|
hasAppearance: !!this.appearance,
|
||||||
id: params.id,
|
id: params.id,
|
||||||
|
modificationDate: this.modificationDate,
|
||||||
rect: this.rectangle,
|
rect: this.rectangle,
|
||||||
subtype: params.subtype,
|
subtype: params.subtype,
|
||||||
};
|
};
|
||||||
@ -239,6 +243,31 @@ class Annotation {
|
|||||||
return this._isPrintable(this.flags);
|
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.
|
* Set the flags.
|
||||||
*
|
*
|
||||||
@ -947,6 +976,20 @@ class PopupAnnotation extends Annotation {
|
|||||||
this.data.title = stringToPDFString(parentItem.get('T') || '');
|
this.data.title = stringToPDFString(parentItem.get('T') || '');
|
||||||
this.data.contents = stringToPDFString(parentItem.get('Contents') || '');
|
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')) {
|
if (!parentItem.has('C')) {
|
||||||
// Fall back to the default background color.
|
// Fall back to the default background color.
|
||||||
this.data.color = null;
|
this.data.color = null;
|
||||||
|
@ -14,7 +14,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addLinkAttributes, DOMSVGFactory, getFilenameFromUrl, LinkTarget
|
addLinkAttributes, DOMSVGFactory, getFilenameFromUrl, LinkTarget,
|
||||||
|
PDFDateString
|
||||||
} from './display_utils';
|
} from './display_utils';
|
||||||
import {
|
import {
|
||||||
AnnotationBorderStyleType, AnnotationType, stringToPDFString, unreachable,
|
AnnotationBorderStyleType, AnnotationType, stringToPDFString, unreachable,
|
||||||
@ -251,6 +252,7 @@ class AnnotationElement {
|
|||||||
trigger,
|
trigger,
|
||||||
color: data.color,
|
color: data.color,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
|
modificationDate: data.modificationDate,
|
||||||
contents: data.contents,
|
contents: data.contents,
|
||||||
hideWrapper: true,
|
hideWrapper: true,
|
||||||
});
|
});
|
||||||
@ -664,6 +666,7 @@ class PopupAnnotationElement extends AnnotationElement {
|
|||||||
trigger: parentElement,
|
trigger: parentElement,
|
||||||
color: this.data.color,
|
color: this.data.color,
|
||||||
title: this.data.title,
|
title: this.data.title,
|
||||||
|
modificationDate: this.data.modificationDate,
|
||||||
contents: this.data.contents,
|
contents: this.data.contents,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -686,6 +689,7 @@ class PopupElement {
|
|||||||
this.trigger = parameters.trigger;
|
this.trigger = parameters.trigger;
|
||||||
this.color = parameters.color;
|
this.color = parameters.color;
|
||||||
this.title = parameters.title;
|
this.title = parameters.title;
|
||||||
|
this.modificationDate = parameters.modificationDate;
|
||||||
this.contents = parameters.contents;
|
this.contents = parameters.contents;
|
||||||
this.hideWrapper = parameters.hideWrapper || false;
|
this.hideWrapper = parameters.hideWrapper || false;
|
||||||
|
|
||||||
@ -724,9 +728,27 @@ class PopupElement {
|
|||||||
popup.style.backgroundColor = Util.makeCssRgb(r | 0, g | 0, b | 0);
|
popup.style.backgroundColor = Util.makeCssRgb(r | 0, g | 0, b | 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let contents = this._formatContents(this.contents);
|
|
||||||
let title = document.createElement('h1');
|
let title = document.createElement('h1');
|
||||||
title.textContent = this.title;
|
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.
|
// Attach the event listeners to the trigger element.
|
||||||
this.trigger.addEventListener('click', this._toggle.bind(this));
|
this.trigger.addEventListener('click', this._toggle.bind(this));
|
||||||
@ -734,8 +756,6 @@ class PopupElement {
|
|||||||
this.trigger.addEventListener('mouseout', this._hide.bind(this, false));
|
this.trigger.addEventListener('mouseout', this._hide.bind(this, false));
|
||||||
popup.addEventListener('click', this._hide.bind(this, true));
|
popup.addEventListener('click', this._hide.bind(this, true));
|
||||||
|
|
||||||
popup.appendChild(title);
|
|
||||||
popup.appendChild(contents);
|
|
||||||
wrapper.appendChild(popup);
|
wrapper.appendChild(popup);
|
||||||
return wrapper;
|
return wrapper;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
/* eslint no-var: error */
|
/* eslint no-var: error */
|
||||||
|
|
||||||
import {
|
import {
|
||||||
assert, CMapCompressionType, removeNullCharacters, stringToBytes,
|
assert, CMapCompressionType, isString, removeNullCharacters, stringToBytes,
|
||||||
unreachable, URL, Util, warn
|
unreachable, URL, Util, warn
|
||||||
} from '../shared/util';
|
} from '../shared/util';
|
||||||
|
|
||||||
@ -491,6 +491,91 @@ function releaseImageResources(img) {
|
|||||||
img.removeAttribute('src');
|
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 {
|
export {
|
||||||
PageViewport,
|
PageViewport,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
@ -508,4 +593,5 @@ export {
|
|||||||
loadScript,
|
loadScript,
|
||||||
deprecated,
|
deprecated,
|
||||||
releaseImageResources,
|
releaseImageResources,
|
||||||
|
PDFDateString,
|
||||||
};
|
};
|
||||||
|
@ -114,6 +114,7 @@ exports.getFilenameFromUrl = pdfjsDisplayDisplayUtils.getFilenameFromUrl;
|
|||||||
exports.LinkTarget = pdfjsDisplayDisplayUtils.LinkTarget;
|
exports.LinkTarget = pdfjsDisplayDisplayUtils.LinkTarget;
|
||||||
exports.addLinkAttributes = pdfjsDisplayDisplayUtils.addLinkAttributes;
|
exports.addLinkAttributes = pdfjsDisplayDisplayUtils.addLinkAttributes;
|
||||||
exports.loadScript = pdfjsDisplayDisplayUtils.loadScript;
|
exports.loadScript = pdfjsDisplayDisplayUtils.loadScript;
|
||||||
|
exports.PDFDateString = pdfjsDisplayDisplayUtils.PDFDateString;
|
||||||
exports.GlobalWorkerOptions = pdfjsDisplayWorkerOptions.GlobalWorkerOptions;
|
exports.GlobalWorkerOptions = pdfjsDisplayWorkerOptions.GlobalWorkerOptions;
|
||||||
exports.apiCompatibilityParams =
|
exports.apiCompatibilityParams =
|
||||||
pdfjsDisplayAPICompatibility.apiCompatibilityParams;
|
pdfjsDisplayAPICompatibility.apiCompatibilityParams;
|
||||||
|
@ -35,3 +35,9 @@
|
|||||||
.annotationLayer .popupWrapper {
|
.annotationLayer .popupWrapper {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.annotationLayer .popup h1,
|
||||||
|
.annotationLayer .popup p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
@ -131,6 +131,34 @@ describe('annotation', function() {
|
|||||||
dict = ref = null;
|
dict = ref = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set and get a valid creation date', function() {
|
||||||
|
const annotation = new Annotation({ dict, ref, });
|
||||||
|
annotation.setCreationDate('D:20190422');
|
||||||
|
|
||||||
|
expect(annotation.creationDate).toEqual('D:20190422');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set and get an invalid creation date', function() {
|
||||||
|
const annotation = new Annotation({ dict, ref, });
|
||||||
|
annotation.setCreationDate(undefined);
|
||||||
|
|
||||||
|
expect(annotation.creationDate).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set and get a valid modification date', function() {
|
||||||
|
const annotation = new Annotation({ dict, ref, });
|
||||||
|
annotation.setModificationDate('D:20190422');
|
||||||
|
|
||||||
|
expect(annotation.modificationDate).toEqual('D:20190422');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set and get an invalid modification date', function() {
|
||||||
|
const annotation = new Annotation({ dict, ref, });
|
||||||
|
annotation.setModificationDate(undefined);
|
||||||
|
|
||||||
|
expect(annotation.modificationDate).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
it('should set and get flags', function() {
|
it('should set and get flags', function() {
|
||||||
const annotation = new Annotation({ dict, ref, });
|
const annotation = new Annotation({ dict, ref, });
|
||||||
annotation.setFlags(13);
|
annotation.setFlags(13);
|
||||||
@ -1400,6 +1428,59 @@ describe('annotation', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('PopupAnnotation', function() {
|
describe('PopupAnnotation', function() {
|
||||||
|
it('should inherit properties from its parent', function(done) {
|
||||||
|
const parentDict = new Dict();
|
||||||
|
parentDict.set('Type', Name.get('Annot'));
|
||||||
|
parentDict.set('Subtype', Name.get('Text'));
|
||||||
|
parentDict.set('CreationDate', 'D:20190422');
|
||||||
|
parentDict.set('M', 'D:20190423');
|
||||||
|
parentDict.set('C', [0, 0, 1]);
|
||||||
|
|
||||||
|
const popupDict = new Dict();
|
||||||
|
popupDict.set('Type', Name.get('Annot'));
|
||||||
|
popupDict.set('Subtype', Name.get('Popup'));
|
||||||
|
popupDict.set('Parent', parentDict);
|
||||||
|
|
||||||
|
const popupRef = new Ref(13, 0);
|
||||||
|
const xref = new XRefMock([
|
||||||
|
{ ref: popupRef, data: popupDict, }
|
||||||
|
]);
|
||||||
|
|
||||||
|
AnnotationFactory.create(xref, popupRef, pdfManagerMock,
|
||||||
|
idFactoryMock).then(({ data, viewable, }) => {
|
||||||
|
expect(data.annotationType).toEqual(AnnotationType.POPUP);
|
||||||
|
expect(data.creationDate).toEqual('D:20190422');
|
||||||
|
expect(data.modificationDate).toEqual('D:20190423');
|
||||||
|
expect(data.color).toEqual(new Uint8ClampedArray([0, 0, 255]));
|
||||||
|
done();
|
||||||
|
}, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing parent properties', function(done) {
|
||||||
|
const parentDict = new Dict();
|
||||||
|
parentDict.set('Type', Name.get('Annot'));
|
||||||
|
parentDict.set('Subtype', Name.get('Text'));
|
||||||
|
|
||||||
|
const popupDict = new Dict();
|
||||||
|
popupDict.set('Type', Name.get('Annot'));
|
||||||
|
popupDict.set('Subtype', Name.get('Popup'));
|
||||||
|
popupDict.set('Parent', parentDict);
|
||||||
|
|
||||||
|
const popupRef = new Ref(13, 0);
|
||||||
|
const xref = new XRefMock([
|
||||||
|
{ ref: popupRef, data: popupDict, }
|
||||||
|
]);
|
||||||
|
|
||||||
|
AnnotationFactory.create(xref, popupRef, pdfManagerMock,
|
||||||
|
idFactoryMock).then(({ data, viewable, }) => {
|
||||||
|
expect(data.annotationType).toEqual(AnnotationType.POPUP);
|
||||||
|
expect(data.creationDate).toEqual(null);
|
||||||
|
expect(data.modificationDate).toEqual(null);
|
||||||
|
expect(data.color).toEqual(null);
|
||||||
|
done();
|
||||||
|
}, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
it('should inherit the parent flags when the Popup is not viewable, ' +
|
it('should inherit the parent flags when the Popup is not viewable, ' +
|
||||||
'but the parent is (PR 7352)', function(done) {
|
'but the parent is (PR 7352)', function(done) {
|
||||||
const parentDict = new Dict();
|
const parentDict = new Dict();
|
||||||
|
@ -15,7 +15,8 @@
|
|||||||
/* eslint no-var: error */
|
/* eslint no-var: error */
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DOMCanvasFactory, DOMSVGFactory, getFilenameFromUrl, isValidFetchUrl
|
DOMCanvasFactory, DOMSVGFactory, getFilenameFromUrl, isValidFetchUrl,
|
||||||
|
PDFDateString
|
||||||
} from '../../src/display/display_utils';
|
} from '../../src/display/display_utils';
|
||||||
import isNodeJS from '../../src/shared/is_node';
|
import isNodeJS from '../../src/shared/is_node';
|
||||||
|
|
||||||
@ -220,4 +221,70 @@ describe('display_utils', function() {
|
|||||||
expect(isValidFetchUrl('https://www.example.com')).toEqual(true);
|
expect(isValidFetchUrl('https://www.example.com')).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PDFDateString', function() {
|
||||||
|
describe('toDateObject', function() {
|
||||||
|
it('converts PDF date strings to JavaScript `Date` objects', function() {
|
||||||
|
const expectations = {
|
||||||
|
undefined: null,
|
||||||
|
null: null,
|
||||||
|
42: null,
|
||||||
|
'2019': null,
|
||||||
|
'D2019': null,
|
||||||
|
'D:': null,
|
||||||
|
'D:201': null,
|
||||||
|
'D:2019': new Date(Date.UTC(2019, 0, 1, 0, 0, 0)),
|
||||||
|
'D:20190': new Date(Date.UTC(2019, 0, 1, 0, 0, 0)),
|
||||||
|
'D:201900': new Date(Date.UTC(2019, 0, 1, 0, 0, 0)),
|
||||||
|
'D:201913': new Date(Date.UTC(2019, 0, 1, 0, 0, 0)),
|
||||||
|
'D:201902': new Date(Date.UTC(2019, 1, 1, 0, 0, 0)),
|
||||||
|
'D:2019020': new Date(Date.UTC(2019, 1, 1, 0, 0, 0)),
|
||||||
|
'D:20190200': new Date(Date.UTC(2019, 1, 1, 0, 0, 0)),
|
||||||
|
'D:20190232': new Date(Date.UTC(2019, 1, 1, 0, 0, 0)),
|
||||||
|
'D:20190203': new Date(Date.UTC(2019, 1, 3, 0, 0, 0)),
|
||||||
|
// Invalid dates like the 31th of April are handled by JavaScript:
|
||||||
|
'D:20190431': new Date(Date.UTC(2019, 4, 1, 0, 0, 0)),
|
||||||
|
'D:201902030': new Date(Date.UTC(2019, 1, 3, 0, 0, 0)),
|
||||||
|
'D:2019020300': new Date(Date.UTC(2019, 1, 3, 0, 0, 0)),
|
||||||
|
'D:2019020324': new Date(Date.UTC(2019, 1, 3, 0, 0, 0)),
|
||||||
|
'D:2019020304': new Date(Date.UTC(2019, 1, 3, 4, 0, 0)),
|
||||||
|
'D:20190203040': new Date(Date.UTC(2019, 1, 3, 4, 0, 0)),
|
||||||
|
'D:201902030400': new Date(Date.UTC(2019, 1, 3, 4, 0, 0)),
|
||||||
|
'D:201902030460': new Date(Date.UTC(2019, 1, 3, 4, 0, 0)),
|
||||||
|
'D:201902030405': new Date(Date.UTC(2019, 1, 3, 4, 5, 0)),
|
||||||
|
'D:2019020304050': new Date(Date.UTC(2019, 1, 3, 4, 5, 0)),
|
||||||
|
'D:20190203040500': new Date(Date.UTC(2019, 1, 3, 4, 5, 0)),
|
||||||
|
'D:20190203040560': new Date(Date.UTC(2019, 1, 3, 4, 5, 0)),
|
||||||
|
'D:20190203040506': new Date(Date.UTC(2019, 1, 3, 4, 5, 6)),
|
||||||
|
'D:20190203040506F': new Date(Date.UTC(2019, 1, 3, 4, 5, 6)),
|
||||||
|
'D:20190203040506Z': new Date(Date.UTC(2019, 1, 3, 4, 5, 6)),
|
||||||
|
'D:20190203040506-': new Date(Date.UTC(2019, 1, 3, 4, 5, 6)),
|
||||||
|
'D:20190203040506+': new Date(Date.UTC(2019, 1, 3, 4, 5, 6)),
|
||||||
|
'D:20190203040506+\'': new Date(Date.UTC(2019, 1, 3, 4, 5, 6)),
|
||||||
|
'D:20190203040506+0': new Date(Date.UTC(2019, 1, 3, 4, 5, 6)),
|
||||||
|
'D:20190203040506+01': new Date(Date.UTC(2019, 1, 3, 3, 5, 6)),
|
||||||
|
'D:20190203040506+00\'': new Date(Date.UTC(2019, 1, 3, 4, 5, 6)),
|
||||||
|
'D:20190203040506+24\'': new Date(Date.UTC(2019, 1, 3, 4, 5, 6)),
|
||||||
|
'D:20190203040506+01\'': new Date(Date.UTC(2019, 1, 3, 3, 5, 6)),
|
||||||
|
'D:20190203040506+01\'0': new Date(Date.UTC(2019, 1, 3, 3, 5, 6)),
|
||||||
|
'D:20190203040506+01\'00': new Date(Date.UTC(2019, 1, 3, 3, 5, 6)),
|
||||||
|
'D:20190203040506+01\'60': new Date(Date.UTC(2019, 1, 3, 3, 5, 6)),
|
||||||
|
'D:20190203040506+0102': new Date(Date.UTC(2019, 1, 3, 3, 3, 6)),
|
||||||
|
'D:20190203040506+01\'02': new Date(Date.UTC(2019, 1, 3, 3, 3, 6)),
|
||||||
|
'D:20190203040506+01\'02\'': new Date(Date.UTC(2019, 1, 3, 3, 3, 6)),
|
||||||
|
// Offset hour and minute that result in a day change:
|
||||||
|
'D:20190203040506+05\'07': new Date(Date.UTC(2019, 1, 2, 22, 58, 6)),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [input, expectation] of Object.entries(expectations)) {
|
||||||
|
const result = PDFDateString.toDateObject(input);
|
||||||
|
if (result) {
|
||||||
|
expect(result.getTime()).toEqual(expectation.getTime());
|
||||||
|
} else {
|
||||||
|
expect(result).toEqual(expectation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -158,25 +158,33 @@
|
|||||||
z-index: 200;
|
z-index: 200;
|
||||||
max-width: 20em;
|
max-width: 20em;
|
||||||
background-color: #FFFF99;
|
background-color: #FFFF99;
|
||||||
box-shadow: 0px 2px 5px #333;
|
box-shadow: 0px 2px 5px #888;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding: 0.6em;
|
padding: 6px;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font: message-box;
|
font: message-box;
|
||||||
|
font-size: 9px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.annotationLayer .popup > * {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
.annotationLayer .popup h1 {
|
.annotationLayer .popup h1 {
|
||||||
font-size: 1em;
|
display: inline-block;
|
||||||
border-bottom: 1px solid #000000;
|
}
|
||||||
margin: 0;
|
|
||||||
padding-bottom: 0.2em;
|
.annotationLayer .popup span {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.annotationLayer .popup p {
|
.annotationLayer .popup p {
|
||||||
margin: 0;
|
border-top: 1px solid #333;
|
||||||
padding-top: 0.2em;
|
margin-top: 2px;
|
||||||
|
padding-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.annotationLayer .highlightAnnotation,
|
.annotationLayer .highlightAnnotation,
|
||||||
|
@ -13,10 +13,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { createPromiseCapability, PDFDateString } from 'pdfjs-lib';
|
||||||
import {
|
import {
|
||||||
getPageSizeInches, getPDFFileNameFromURL, isPortraitOrientation, NullL10n
|
getPageSizeInches, getPDFFileNameFromURL, isPortraitOrientation, NullL10n
|
||||||
} from './ui_utils';
|
} from './ui_utils';
|
||||||
import { createPromiseCapability } from 'pdfjs-lib';
|
|
||||||
|
|
||||||
const DEFAULT_FIELD_CONTENT = '-';
|
const DEFAULT_FIELD_CONTENT = '-';
|
||||||
|
|
||||||
@ -363,50 +363,14 @@ class PDFDocumentProperties {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_parseDate(inputDate) {
|
_parseDate(inputDate) {
|
||||||
if (!inputDate) {
|
const dateObject = PDFDateString.toDateObject(inputDate);
|
||||||
return;
|
if (dateObject) {
|
||||||
|
const dateString = dateObject.toLocaleDateString();
|
||||||
|
const timeString = dateObject.toLocaleTimeString();
|
||||||
|
return this.l10n.get('document_properties_date_string',
|
||||||
|
{ date: dateString, time: timeString, },
|
||||||
|
'{{date}}, {{time}}');
|
||||||
}
|
}
|
||||||
// This is implemented according to the PDF specification, but note that
|
|
||||||
// Adobe Reader doesn't handle changing the date to universal time
|
|
||||||
// and doesn't use the user's time zone (they're effectively ignoring
|
|
||||||
// the HH' and mm' parts of the date string).
|
|
||||||
let dateToParse = inputDate;
|
|
||||||
|
|
||||||
// Remove the D: prefix if it is available.
|
|
||||||
if (dateToParse.substring(0, 2) === 'D:') {
|
|
||||||
dateToParse = dateToParse.substring(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all elements from the PDF date string.
|
|
||||||
// JavaScript's `Date` object expects the month to be between
|
|
||||||
// 0 and 11 instead of 1 and 12, so we're correcting for this.
|
|
||||||
let year = parseInt(dateToParse.substring(0, 4), 10);
|
|
||||||
let month = parseInt(dateToParse.substring(4, 6), 10) - 1;
|
|
||||||
let day = parseInt(dateToParse.substring(6, 8), 10);
|
|
||||||
let hours = parseInt(dateToParse.substring(8, 10), 10);
|
|
||||||
let minutes = parseInt(dateToParse.substring(10, 12), 10);
|
|
||||||
let seconds = parseInt(dateToParse.substring(12, 14), 10);
|
|
||||||
let utRel = dateToParse.substring(14, 15);
|
|
||||||
let offsetHours = parseInt(dateToParse.substring(15, 17), 10);
|
|
||||||
let offsetMinutes = parseInt(dateToParse.substring(18, 20), 10);
|
|
||||||
|
|
||||||
// As per spec, utRel = 'Z' means equal to universal time.
|
|
||||||
// The other cases ('-' and '+') have to be handled here.
|
|
||||||
if (utRel === '-') {
|
|
||||||
hours += offsetHours;
|
|
||||||
minutes += offsetMinutes;
|
|
||||||
} else if (utRel === '+') {
|
|
||||||
hours -= offsetHours;
|
|
||||||
minutes -= offsetMinutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the new date format from the user's locale.
|
|
||||||
let date = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
|
|
||||||
let dateString = date.toLocaleDateString();
|
|
||||||
let timeString = date.toLocaleTimeString();
|
|
||||||
return this.l10n.get('document_properties_date_string',
|
|
||||||
{ date: dateString, time: timeString, },
|
|
||||||
'{{date}}, {{time}}');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user