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.
 | 
			
		||||
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.
 | 
			
		||||
# "{{type}}" will be replaced with an annotation type from a list defined in
 | 
			
		||||
# 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. 
 | 
			
		||||
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.
 | 
			
		||||
# "{{type}}" will be replaced with an annotation type from a list defined in
 | 
			
		||||
# the PDF spec (32000-1:2008 Table 169 – Annotation types).
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -35,3 +35,9 @@
 | 
			
		||||
.annotationLayer .popupWrapper {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.annotationLayer .popup h1,
 | 
			
		||||
.annotationLayer .popup p {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -131,6 +131,34 @@ describe('annotation', function() {
 | 
			
		||||
      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() {
 | 
			
		||||
      const annotation = new Annotation({ dict, ref, });
 | 
			
		||||
      annotation.setFlags(13);
 | 
			
		||||
@ -1400,6 +1428,59 @@ describe('annotation', 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, ' +
 | 
			
		||||
       'but the parent is (PR 7352)', function(done) {
 | 
			
		||||
      const parentDict = new Dict();
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,8 @@
 | 
			
		||||
/* eslint no-var: error */
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  DOMCanvasFactory, DOMSVGFactory, getFilenameFromUrl, isValidFetchUrl
 | 
			
		||||
  DOMCanvasFactory, DOMSVGFactory, getFilenameFromUrl, isValidFetchUrl,
 | 
			
		||||
  PDFDateString
 | 
			
		||||
} from '../../src/display/display_utils';
 | 
			
		||||
import isNodeJS from '../../src/shared/is_node';
 | 
			
		||||
 | 
			
		||||
@ -220,4 +221,70 @@ describe('display_utils', function() {
 | 
			
		||||
      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;
 | 
			
		||||
  max-width: 20em;
 | 
			
		||||
  background-color: #FFFF99;
 | 
			
		||||
  box-shadow: 0px 2px 5px #333;
 | 
			
		||||
  box-shadow: 0px 2px 5px #888;
 | 
			
		||||
  border-radius: 2px;
 | 
			
		||||
  padding: 0.6em;
 | 
			
		||||
  padding: 6px;
 | 
			
		||||
  margin-left: 5px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  font: message-box;
 | 
			
		||||
  font-size: 9px;
 | 
			
		||||
  word-wrap: break-word;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.annotationLayer .popup > * {
 | 
			
		||||
  font-size: 9px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.annotationLayer .popup h1 {
 | 
			
		||||
  font-size: 1em;
 | 
			
		||||
  border-bottom: 1px solid #000000;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding-bottom: 0.2em;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.annotationLayer .popup span {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  margin-left: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.annotationLayer .popup p {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding-top: 0.2em;
 | 
			
		||||
  border-top: 1px solid #333;
 | 
			
		||||
  margin-top: 2px;
 | 
			
		||||
  padding-top: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.annotationLayer .highlightAnnotation,
 | 
			
		||||
 | 
			
		||||
@ -13,10 +13,10 @@
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { createPromiseCapability, PDFDateString } from 'pdfjs-lib';
 | 
			
		||||
import {
 | 
			
		||||
  getPageSizeInches, getPDFFileNameFromURL, isPortraitOrientation, NullL10n
 | 
			
		||||
} from './ui_utils';
 | 
			
		||||
import { createPromiseCapability } from 'pdfjs-lib';
 | 
			
		||||
 | 
			
		||||
const DEFAULT_FIELD_CONTENT = '-';
 | 
			
		||||
 | 
			
		||||
@ -363,51 +363,15 @@ class PDFDocumentProperties {
 | 
			
		||||
   * @private
 | 
			
		||||
   */
 | 
			
		||||
  _parseDate(inputDate) {
 | 
			
		||||
    if (!inputDate) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // 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();
 | 
			
		||||
    const dateObject = PDFDateString.toDateObject(inputDate);
 | 
			
		||||
    if (dateObject) {
 | 
			
		||||
      const dateString = dateObject.toLocaleDateString();
 | 
			
		||||
      const timeString = dateObject.toLocaleTimeString();
 | 
			
		||||
      return this.l10n.get('document_properties_date_string',
 | 
			
		||||
                           { date: dateString, time: timeString, },
 | 
			
		||||
                           '{{date}}, {{time}}');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @private
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user