JS - Handle correctly hierarchy of fields (#13133)
* JS - Handle correctly hierarchy of fields - it aims to fix #13132; - annotations can inherit their actions from the parent field; - there are some fields which act as a container for other fields: - they can be access through js so need to add them with an empty type (nothing in the spec about that but checked in Acrobat); - calculation order list (CO) can reference them so need make them through this.getField; - getArray method must return kids. - field values are number, string, ... depending of their type but nothing in the spec on how to know what's the type: - according to the comment for Canonical Format: https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf#page=461 - it seems that this "type" can be guessed from js action Format (when setting a type in Acrobat DC, the only affected thing is this action). - util.scand with an empty string returns the current date.
This commit is contained in:
		
							parent
							
								
									75a6b2fa13
								
							
						
					
					
						commit
						84d7cccb1d
					
				@ -64,10 +64,11 @@ class AnnotationFactory {
 | 
				
			|||||||
   * @param {Object} ref
 | 
					   * @param {Object} ref
 | 
				
			||||||
   * @param {PDFManager} pdfManager
 | 
					   * @param {PDFManager} pdfManager
 | 
				
			||||||
   * @param {Object} idFactory
 | 
					   * @param {Object} idFactory
 | 
				
			||||||
 | 
					   * @param {boolean} collectFields
 | 
				
			||||||
   * @returns {Promise} A promise that is resolved with an {Annotation}
 | 
					   * @returns {Promise} A promise that is resolved with an {Annotation}
 | 
				
			||||||
   *   instance.
 | 
					   *   instance.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  static create(xref, ref, pdfManager, idFactory) {
 | 
					  static create(xref, ref, pdfManager, idFactory, collectFields) {
 | 
				
			||||||
    return pdfManager.ensureCatalog("acroForm").then(acroForm => {
 | 
					    return pdfManager.ensureCatalog("acroForm").then(acroForm => {
 | 
				
			||||||
      return pdfManager.ensure(this, "_create", [
 | 
					      return pdfManager.ensure(this, "_create", [
 | 
				
			||||||
        xref,
 | 
					        xref,
 | 
				
			||||||
@ -75,6 +76,7 @@ class AnnotationFactory {
 | 
				
			|||||||
        pdfManager,
 | 
					        pdfManager,
 | 
				
			||||||
        idFactory,
 | 
					        idFactory,
 | 
				
			||||||
        acroForm,
 | 
					        acroForm,
 | 
				
			||||||
 | 
					        collectFields,
 | 
				
			||||||
      ]);
 | 
					      ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -82,7 +84,7 @@ class AnnotationFactory {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * @private
 | 
					   * @private
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  static _create(xref, ref, pdfManager, idFactory, acroForm) {
 | 
					  static _create(xref, ref, pdfManager, idFactory, acroForm, collectFields) {
 | 
				
			||||||
    const dict = xref.fetchIfRef(ref);
 | 
					    const dict = xref.fetchIfRef(ref);
 | 
				
			||||||
    if (!isDict(dict)) {
 | 
					    if (!isDict(dict)) {
 | 
				
			||||||
      return undefined;
 | 
					      return undefined;
 | 
				
			||||||
@ -103,6 +105,7 @@ class AnnotationFactory {
 | 
				
			|||||||
      id,
 | 
					      id,
 | 
				
			||||||
      pdfManager,
 | 
					      pdfManager,
 | 
				
			||||||
      acroForm: acroForm instanceof Dict ? acroForm : Dict.empty,
 | 
					      acroForm: acroForm instanceof Dict ? acroForm : Dict.empty,
 | 
				
			||||||
 | 
					      collectFields,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    switch (subtype) {
 | 
					    switch (subtype) {
 | 
				
			||||||
@ -178,6 +181,7 @@ class AnnotationFactory {
 | 
				
			|||||||
        return new FileAttachmentAnnotation(parameters);
 | 
					        return new FileAttachmentAnnotation(parameters);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
 | 
					        if (!collectFields) {
 | 
				
			||||||
          if (!subtype) {
 | 
					          if (!subtype) {
 | 
				
			||||||
            warn("Annotation is missing the required /Subtype.");
 | 
					            warn("Annotation is missing the required /Subtype.");
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
@ -188,6 +192,7 @@ class AnnotationFactory {
 | 
				
			|||||||
                "falling back to base annotation."
 | 
					                "falling back to base annotation."
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return new Annotation(parameters);
 | 
					        return new Annotation(parameters);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -345,6 +350,31 @@ class Annotation {
 | 
				
			|||||||
      subtype: params.subtype,
 | 
					      subtype: params.subtype,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (params.collectFields) {
 | 
				
			||||||
 | 
					      // Fields can act as container for other fields and have
 | 
				
			||||||
 | 
					      // some actions even if no Annotation inherit from them.
 | 
				
			||||||
 | 
					      // Those fields can be referenced by CO (calculation order).
 | 
				
			||||||
 | 
					      const kids = dict.get("Kids");
 | 
				
			||||||
 | 
					      if (Array.isArray(kids)) {
 | 
				
			||||||
 | 
					        const kidIds = [];
 | 
				
			||||||
 | 
					        for (const kid of kids) {
 | 
				
			||||||
 | 
					          if (isRef(kid)) {
 | 
				
			||||||
 | 
					            kidIds.push(kid.toString());
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (kidIds.length !== 0) {
 | 
				
			||||||
 | 
					          this.data.kidIds = kidIds;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.data.actions = collectActions(
 | 
				
			||||||
 | 
					        params.xref,
 | 
				
			||||||
 | 
					        dict,
 | 
				
			||||||
 | 
					        AnnotationActionEventType
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      this.data.fieldName = this._constructFieldName(dict);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this._fallbackFontDict = null;
 | 
					    this._fallbackFontDict = null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -644,6 +674,15 @@ class Annotation {
 | 
				
			|||||||
   * @returns {Object | null}
 | 
					   * @returns {Object | null}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  getFieldObject() {
 | 
					  getFieldObject() {
 | 
				
			||||||
 | 
					    if (this.data.kidIds) {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        id: this.data.id,
 | 
				
			||||||
 | 
					        actions: this.data.actions,
 | 
				
			||||||
 | 
					        name: this.data.fieldName,
 | 
				
			||||||
 | 
					        type: "",
 | 
				
			||||||
 | 
					        kidIds: this.data.kidIds,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -670,6 +709,65 @@ class Annotation {
 | 
				
			|||||||
      stream.reset();
 | 
					      stream.reset();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Construct the (fully qualified) field name from the (partial) field
 | 
				
			||||||
 | 
					   * names of the field and its ancestors.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * @private
 | 
				
			||||||
 | 
					   * @memberof Annotation
 | 
				
			||||||
 | 
					   * @param {Dict} dict - Complete widget annotation dictionary
 | 
				
			||||||
 | 
					   * @returns {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.
 | 
				
			||||||
 | 
					    const fieldName = [];
 | 
				
			||||||
 | 
					    if (dict.has("T")) {
 | 
				
			||||||
 | 
					      fieldName.unshift(stringToPDFString(dict.get("T")));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let loopDict = dict;
 | 
				
			||||||
 | 
					    const visited = new RefSet();
 | 
				
			||||||
 | 
					    if (dict.objId) {
 | 
				
			||||||
 | 
					      visited.put(dict.objId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    while (loopDict.has("Parent")) {
 | 
				
			||||||
 | 
					      loopDict = loopDict.get("Parent");
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        !(loopDict instanceof Dict) ||
 | 
				
			||||||
 | 
					        (loopDict.objId && visited.has(loopDict.objId))
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        // 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).
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // If parent has been already visited, it means that we're
 | 
				
			||||||
 | 
					        // in an infinite loop.
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (loopDict.objId) {
 | 
				
			||||||
 | 
					        visited.put(loopDict.objId);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (loopDict.has("T")) {
 | 
				
			||||||
 | 
					        fieldName.unshift(stringToPDFString(loopDict.get("T")));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return fieldName.join(".");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -995,8 +1093,16 @@ class WidgetAnnotation extends Annotation {
 | 
				
			|||||||
    this.ref = params.ref;
 | 
					    this.ref = params.ref;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data.annotationType = AnnotationType.WIDGET;
 | 
					    data.annotationType = AnnotationType.WIDGET;
 | 
				
			||||||
 | 
					    if (data.fieldName === undefined) {
 | 
				
			||||||
      data.fieldName = this._constructFieldName(dict);
 | 
					      data.fieldName = this._constructFieldName(dict);
 | 
				
			||||||
    data.actions = collectActions(params.xref, dict, AnnotationActionEventType);
 | 
					    }
 | 
				
			||||||
 | 
					    if (data.actions === undefined) {
 | 
				
			||||||
 | 
					      data.actions = collectActions(
 | 
				
			||||||
 | 
					        params.xref,
 | 
				
			||||||
 | 
					        dict,
 | 
				
			||||||
 | 
					        AnnotationActionEventType
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const fieldValue = getInheritableProperty({
 | 
					    const fieldValue = getInheritableProperty({
 | 
				
			||||||
      dict,
 | 
					      dict,
 | 
				
			||||||
@ -1059,65 +1165,6 @@ class WidgetAnnotation extends Annotation {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * 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
 | 
					 | 
				
			||||||
   * @returns {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.
 | 
					 | 
				
			||||||
    const fieldName = [];
 | 
					 | 
				
			||||||
    if (dict.has("T")) {
 | 
					 | 
				
			||||||
      fieldName.unshift(stringToPDFString(dict.get("T")));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let loopDict = dict;
 | 
					 | 
				
			||||||
    const visited = new RefSet();
 | 
					 | 
				
			||||||
    if (dict.objId) {
 | 
					 | 
				
			||||||
      visited.put(dict.objId);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    while (loopDict.has("Parent")) {
 | 
					 | 
				
			||||||
      loopDict = loopDict.get("Parent");
 | 
					 | 
				
			||||||
      if (
 | 
					 | 
				
			||||||
        !(loopDict instanceof Dict) ||
 | 
					 | 
				
			||||||
        (loopDict.objId && visited.has(loopDict.objId))
 | 
					 | 
				
			||||||
      ) {
 | 
					 | 
				
			||||||
        // 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).
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        // If parent has been already visited, it means that we're
 | 
					 | 
				
			||||||
        // in an infinite loop.
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (loopDict.objId) {
 | 
					 | 
				
			||||||
        visited.put(loopDict.objId);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (loopDict.has("T")) {
 | 
					 | 
				
			||||||
        fieldName.unshift(stringToPDFString(loopDict.get("T")));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return fieldName.join(".");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Decode the given form value.
 | 
					   * Decode the given form value.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
 | 
				
			|||||||
@ -287,8 +287,22 @@ function _collectJS(entry, xref, list, parents) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function collectActions(xref, dict, eventType) {
 | 
					function collectActions(xref, dict, eventType) {
 | 
				
			||||||
  const actions = Object.create(null);
 | 
					  const actions = Object.create(null);
 | 
				
			||||||
  if (dict.has("AA")) {
 | 
					  const additionalActionsDicts = getInheritableProperty({
 | 
				
			||||||
    const additionalActions = dict.get("AA");
 | 
					    dict,
 | 
				
			||||||
 | 
					    key: "AA",
 | 
				
			||||||
 | 
					    stopWhenFound: false,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  if (additionalActionsDicts) {
 | 
				
			||||||
 | 
					    // additionalActionsDicts contains dicts from ancestors
 | 
				
			||||||
 | 
					    // as they're found in the tree from bottom to top.
 | 
				
			||||||
 | 
					    // So the dicts are visited in reverse order to guarantee
 | 
				
			||||||
 | 
					    // that actions from elder ancestors will be overwritten
 | 
				
			||||||
 | 
					    // by ones from younger ancestors.
 | 
				
			||||||
 | 
					    for (let i = additionalActionsDicts.length - 1; i >= 0; i--) {
 | 
				
			||||||
 | 
					      const additionalActions = additionalActionsDicts[i];
 | 
				
			||||||
 | 
					      if (!(additionalActions instanceof Dict)) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      for (const key of additionalActions.getKeys()) {
 | 
					      for (const key of additionalActions.getKeys()) {
 | 
				
			||||||
        const action = eventType[key];
 | 
					        const action = eventType[key];
 | 
				
			||||||
        if (!action) {
 | 
					        if (!action) {
 | 
				
			||||||
@ -303,6 +317,7 @@ function collectActions(xref, dict, eventType) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  // Collect the Action if any (we may have one on pushbutton).
 | 
					  // Collect the Action if any (we may have one on pushbutton).
 | 
				
			||||||
  if (dict.has("A")) {
 | 
					  if (dict.has("A")) {
 | 
				
			||||||
    const actionDict = dict.get("A");
 | 
					    const actionDict = dict.get("A");
 | 
				
			||||||
 | 
				
			|||||||
@ -471,7 +471,8 @@ class Page {
 | 
				
			|||||||
              this.xref,
 | 
					              this.xref,
 | 
				
			||||||
              annotationRef,
 | 
					              annotationRef,
 | 
				
			||||||
              this.pdfManager,
 | 
					              this.pdfManager,
 | 
				
			||||||
              this._localIdFactory
 | 
					              this._localIdFactory,
 | 
				
			||||||
 | 
					              /* collectFields */ false
 | 
				
			||||||
            ).catch(function (reason) {
 | 
					            ).catch(function (reason) {
 | 
				
			||||||
              warn(`_parsedAnnotations: "${reason}".`);
 | 
					              warn(`_parsedAnnotations: "${reason}".`);
 | 
				
			||||||
              return null;
 | 
					              return null;
 | 
				
			||||||
@ -1098,7 +1099,8 @@ class PDFDocument {
 | 
				
			|||||||
        this.xref,
 | 
					        this.xref,
 | 
				
			||||||
        fieldRef,
 | 
					        fieldRef,
 | 
				
			||||||
        this.pdfManager,
 | 
					        this.pdfManager,
 | 
				
			||||||
        this._localIdFactory
 | 
					        this._localIdFactory,
 | 
				
			||||||
 | 
					        /* collectFields */ true
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
        .then(annotation => annotation && annotation.getFieldObject())
 | 
					        .then(annotation => annotation && annotation.getFieldObject())
 | 
				
			||||||
        .catch(function (reason) {
 | 
					        .catch(function (reason) {
 | 
				
			||||||
 | 
				
			|||||||
@ -636,9 +636,11 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
 | 
				
			|||||||
      // NOTE: We cannot set the values using `element.value` below, since it
 | 
					      // NOTE: We cannot set the values using `element.value` below, since it
 | 
				
			||||||
      //       prevents the AnnotationLayer rasterizer in `test/driver.js`
 | 
					      //       prevents the AnnotationLayer rasterizer in `test/driver.js`
 | 
				
			||||||
      //       from parsing the elements correctly for the reference tests.
 | 
					      //       from parsing the elements correctly for the reference tests.
 | 
				
			||||||
      const textContent = storage.getValue(id, {
 | 
					      const storedData = storage.getValue(id, {
 | 
				
			||||||
        value: this.data.fieldValue,
 | 
					        value: this.data.fieldValue,
 | 
				
			||||||
      }).value;
 | 
					        valueAsString: this.data.fieldValue,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      const textContent = storedData.valueAsString || storedData.value || "";
 | 
				
			||||||
      const elementData = {
 | 
					      const elementData = {
 | 
				
			||||||
        userValue: null,
 | 
					        userValue: null,
 | 
				
			||||||
        formattedValue: null,
 | 
					        formattedValue: null,
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,14 @@
 | 
				
			|||||||
 * limitations under the License.
 | 
					 * limitations under the License.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const FieldType = {
 | 
				
			||||||
 | 
					  none: 0,
 | 
				
			||||||
 | 
					  number: 1,
 | 
				
			||||||
 | 
					  percent: 2,
 | 
				
			||||||
 | 
					  date: 3,
 | 
				
			||||||
 | 
					  time: 4,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createActionsMap(actions) {
 | 
					function createActionsMap(actions) {
 | 
				
			||||||
  const actionsMap = new Map();
 | 
					  const actionsMap = new Map();
 | 
				
			||||||
  if (actions) {
 | 
					  if (actions) {
 | 
				
			||||||
@ -23,4 +31,28 @@ function createActionsMap(actions) {
 | 
				
			|||||||
  return actionsMap;
 | 
					  return actionsMap;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { createActionsMap };
 | 
					function getFieldType(actions) {
 | 
				
			||||||
 | 
					  let format = actions.get("Format");
 | 
				
			||||||
 | 
					  if (!format) {
 | 
				
			||||||
 | 
					    return FieldType.none;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  format = format[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  format = format.trim();
 | 
				
			||||||
 | 
					  if (format.startsWith("AFNumber_")) {
 | 
				
			||||||
 | 
					    return FieldType.number;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (format.startsWith("AFPercent_")) {
 | 
				
			||||||
 | 
					    return FieldType.percent;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (format.startsWith("AFDate_")) {
 | 
				
			||||||
 | 
					    return FieldType.date;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (format.startsWith("AFTime__")) {
 | 
				
			||||||
 | 
					    return FieldType.time;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return FieldType.none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { createActionsMap, FieldType, getFieldType };
 | 
				
			||||||
 | 
				
			|||||||
@ -187,18 +187,29 @@ class EventDispatcher {
 | 
				
			|||||||
        continue;
 | 
					        continue;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      event.value = null;
 | 
				
			||||||
      const target = this._objects[targetId];
 | 
					      const target = this._objects[targetId];
 | 
				
			||||||
      this.runActions(source, target, event, "Calculate");
 | 
					      this.runActions(source, target, event, "Calculate");
 | 
				
			||||||
 | 
					      if (!event.rc) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (event.value !== null) {
 | 
				
			||||||
 | 
					        target.wrapped.value = event.value;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      event.value = target.obj.value;
 | 
				
			||||||
      this.runActions(target, target, event, "Validate");
 | 
					      this.runActions(target, target, event, "Validate");
 | 
				
			||||||
      if (!event.rc) {
 | 
					      if (!event.rc) {
 | 
				
			||||||
        continue;
 | 
					        continue;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      target.wrapped.value = event.value;
 | 
					      event.value = target.obj.value;
 | 
				
			||||||
      this.runActions(target, target, event, "Format");
 | 
					      this.runActions(target, target, event, "Format");
 | 
				
			||||||
 | 
					      if (event.value !== null) {
 | 
				
			||||||
        target.wrapped.valueAsString = event.value;
 | 
					        target.wrapped.valueAsString = event.value;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { Event, EventDispatcher };
 | 
					export { Event, EventDispatcher };
 | 
				
			||||||
 | 
				
			|||||||
@ -13,8 +13,8 @@
 | 
				
			|||||||
 * limitations under the License.
 | 
					 * limitations under the License.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { createActionsMap, FieldType, getFieldType } from "./common.js";
 | 
				
			||||||
import { Color } from "./color.js";
 | 
					import { Color } from "./color.js";
 | 
				
			||||||
import { createActionsMap } from "./common.js";
 | 
					 | 
				
			||||||
import { PDFObject } from "./pdf_object.js";
 | 
					import { PDFObject } from "./pdf_object.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Field extends PDFObject {
 | 
					class Field extends PDFObject {
 | 
				
			||||||
@ -82,8 +82,11 @@ class Field extends PDFObject {
 | 
				
			|||||||
    this._textColor = data.textColor || ["G", 0];
 | 
					    this._textColor = data.textColor || ["G", 0];
 | 
				
			||||||
    this._value = data.value || "";
 | 
					    this._value = data.value || "";
 | 
				
			||||||
    this._valueAsString = data.valueAsString;
 | 
					    this._valueAsString = data.valueAsString;
 | 
				
			||||||
 | 
					    this._kidIds = data.kidIds || null;
 | 
				
			||||||
 | 
					    this._fieldType = getFieldType(this._actions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this._globalEval = data.globalEval;
 | 
					    this._globalEval = data.globalEval;
 | 
				
			||||||
 | 
					    this._appObjects = data.appObjects;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get currentValueIndices() {
 | 
					  get currentValueIndices() {
 | 
				
			||||||
@ -200,7 +203,23 @@ class Field extends PDFObject {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  set value(value) {
 | 
					  set value(value) {
 | 
				
			||||||
 | 
					    if (value === "") {
 | 
				
			||||||
 | 
					      this._value = "";
 | 
				
			||||||
 | 
					    } else if (typeof value === "string") {
 | 
				
			||||||
 | 
					      switch (this._fieldType) {
 | 
				
			||||||
 | 
					        case FieldType.number:
 | 
				
			||||||
 | 
					        case FieldType.percent:
 | 
				
			||||||
 | 
					          value = parseFloat(value);
 | 
				
			||||||
 | 
					          if (!isNaN(value)) {
 | 
				
			||||||
            this._value = value;
 | 
					            this._value = value;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          this._value = value;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this._value = value;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (this._isChoice) {
 | 
					    if (this._isChoice) {
 | 
				
			||||||
      if (this.multipleSelection) {
 | 
					      if (this.multipleSelection) {
 | 
				
			||||||
        const values = new Set(value);
 | 
					        const values = new Set(value);
 | 
				
			||||||
@ -332,6 +351,10 @@ class Field extends PDFObject {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getArray() {
 | 
					  getArray() {
 | 
				
			||||||
 | 
					    if (this._kidIds) {
 | 
				
			||||||
 | 
					      return this._kidIds.map(id => this._appObjects[id].wrapped);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this._children === null) {
 | 
					    if (this._children === null) {
 | 
				
			||||||
      this._children = this._document.obj._getChildren(this._fieldPath);
 | 
					      this._children = this._document.obj._getChildren(this._fieldPath);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -67,20 +67,39 @@ function initSandbox(params) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const util = new Util({ externalCall });
 | 
					  const util = new Util({ externalCall });
 | 
				
			||||||
 | 
					  const appObjects = app._objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (data.objects) {
 | 
					  if (data.objects) {
 | 
				
			||||||
 | 
					    const annotations = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const [name, objs] of Object.entries(data.objects)) {
 | 
					    for (const [name, objs] of Object.entries(data.objects)) {
 | 
				
			||||||
      const obj = objs[0];
 | 
					      annotations.length = 0;
 | 
				
			||||||
 | 
					      let container = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (const obj of objs) {
 | 
				
			||||||
 | 
					        if (obj.type !== "") {
 | 
				
			||||||
 | 
					          annotations.push(obj);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          container = obj;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let obj = container;
 | 
				
			||||||
 | 
					      if (annotations.length > 0) {
 | 
				
			||||||
 | 
					        obj = annotations[0];
 | 
				
			||||||
        obj.send = send;
 | 
					        obj.send = send;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      obj.globalEval = globalEval;
 | 
					      obj.globalEval = globalEval;
 | 
				
			||||||
      obj.doc = _document;
 | 
					      obj.doc = _document;
 | 
				
			||||||
      obj.fieldPath = name;
 | 
					      obj.fieldPath = name;
 | 
				
			||||||
 | 
					      obj.appObjects = appObjects;
 | 
				
			||||||
      let field;
 | 
					      let field;
 | 
				
			||||||
      if (obj.type === "radiobutton") {
 | 
					      if (obj.type === "radiobutton") {
 | 
				
			||||||
        const otherButtons = objs.slice(1);
 | 
					        const otherButtons = annotations.slice(1);
 | 
				
			||||||
        field = new RadioButtonField(otherButtons, obj);
 | 
					        field = new RadioButtonField(otherButtons, obj);
 | 
				
			||||||
      } else if (obj.type === "checkbox") {
 | 
					      } else if (obj.type === "checkbox") {
 | 
				
			||||||
        const otherButtons = objs.slice(1);
 | 
					        const otherButtons = annotations.slice(1);
 | 
				
			||||||
        field = new CheckboxField(otherButtons, obj);
 | 
					        field = new CheckboxField(otherButtons, obj);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        field = new Field(obj);
 | 
					        field = new Field(obj);
 | 
				
			||||||
@ -90,7 +109,10 @@ function initSandbox(params) {
 | 
				
			|||||||
      doc._addField(name, wrapped);
 | 
					      doc._addField(name, wrapped);
 | 
				
			||||||
      const _object = { obj: field, wrapped };
 | 
					      const _object = { obj: field, wrapped };
 | 
				
			||||||
      for (const object of objs) {
 | 
					      for (const object of objs) {
 | 
				
			||||||
        app._objects[object.id] = _object;
 | 
					        appObjects[object.id] = _object;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (container) {
 | 
				
			||||||
 | 
					        appObjects[container.id] = _object;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -372,6 +372,10 @@ class Util extends PDFObject {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  scand(cFormat, cDate) {
 | 
					  scand(cFormat, cDate) {
 | 
				
			||||||
 | 
					    if (cDate === "") {
 | 
				
			||||||
 | 
					      return new Date();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    switch (cFormat) {
 | 
					    switch (cFormat) {
 | 
				
			||||||
      case 0:
 | 
					      case 0:
 | 
				
			||||||
        return this.scand("D:yyyymmddHHMMss", cDate);
 | 
					        return this.scand("D:yyyymmddHHMMss", cDate);
 | 
				
			||||||
@ -525,14 +529,14 @@ class Util extends PDFObject {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this._scandCache.set(cFormat, [new RegExp(re, "g"), actions]);
 | 
					      this._scandCache.set(cFormat, [re, actions]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [regexForFormat, actions] = this._scandCache.get(cFormat);
 | 
					    const [re, actions] = this._scandCache.get(cFormat);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const matches = regexForFormat.exec(cDate);
 | 
					    const matches = new RegExp(re, "g").exec(cDate);
 | 
				
			||||||
    if (matches.length !== actions.length + 1) {
 | 
					    if (!matches || matches.length !== actions.length + 1) {
 | 
				
			||||||
      throw new Error("Invalid date in util.scand");
 | 
					      return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const data = {
 | 
					    const data = {
 | 
				
			||||||
 | 
				
			|||||||
@ -695,4 +695,71 @@ describe("Interaction", () => {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe("in issue13132.pdf", () => {
 | 
				
			||||||
 | 
					    let pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    beforeAll(async () => {
 | 
				
			||||||
 | 
					      pages = await loadAndWait("issue13132.pdf", "#\\31 71R");
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    afterAll(async () => {
 | 
				
			||||||
 | 
					      await closePages(pages);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("must compute sum of fields", async () => {
 | 
				
			||||||
 | 
					      await Promise.all(
 | 
				
			||||||
 | 
					        pages.map(async ([browserName, page]) => {
 | 
				
			||||||
 | 
					          await page.waitForFunction(
 | 
				
			||||||
 | 
					            "window.PDFViewerApplication.scriptingReady === true"
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          await page.evaluate(() => {
 | 
				
			||||||
 | 
					            window.document.getElementById("171R").scrollIntoView();
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          let sum = 0;
 | 
				
			||||||
 | 
					          for (const [id, val] of [
 | 
				
			||||||
 | 
					            ["#\\31 38R", 1],
 | 
				
			||||||
 | 
					            ["#\\37 7R", 2],
 | 
				
			||||||
 | 
					            ["#\\39 3R", 3],
 | 
				
			||||||
 | 
					            ["#\\31 51R", 4],
 | 
				
			||||||
 | 
					            ["#\\37 9R", 5],
 | 
				
			||||||
 | 
					          ]) {
 | 
				
			||||||
 | 
					            const prev = await page.$eval("#\\31 71R", el => el.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await page.type(id, val.toString(), { delay: 100 });
 | 
				
			||||||
 | 
					            await page.keyboard.press("Tab");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await page.waitForFunction(
 | 
				
			||||||
 | 
					              _prev =>
 | 
				
			||||||
 | 
					                getComputedStyle(document.querySelector("#\\31 71R")).value !==
 | 
				
			||||||
 | 
					                _prev,
 | 
				
			||||||
 | 
					              {},
 | 
				
			||||||
 | 
					              prev
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            sum += val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const total = await page.$eval("#\\31 71R", el => el.value);
 | 
				
			||||||
 | 
					            expect(total).withContext(`In ${browserName}`).toEqual(`£${sum}`);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // Some unrendered annotations have been updated, so check
 | 
				
			||||||
 | 
					          // that they've the correct value when rendered.
 | 
				
			||||||
 | 
					          await page.evaluate(() => {
 | 
				
			||||||
 | 
					            window.document
 | 
				
			||||||
 | 
					              .querySelectorAll('[data-page-number="4"][class="page"]')[0]
 | 
				
			||||||
 | 
					              .scrollIntoView();
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          await page.waitForSelector("#\\32 99R", {
 | 
				
			||||||
 | 
					            timeout: 0,
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const total = await page.$eval("#\\32 99R", el => el.value);
 | 
				
			||||||
 | 
					          expect(total).withContext(`In ${browserName}`).toEqual(`£${sum}`);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								test/pdfs/issue13132.pdf.link
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/pdfs/issue13132.pdf.link
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					https://web.archive.org/web/20210324123420/https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/866023/IHT205_2004-2006.pdf
 | 
				
			||||||
@ -2561,6 +2561,12 @@
 | 
				
			|||||||
       "link": true,
 | 
					       "link": true,
 | 
				
			||||||
       "type": "load"
 | 
					       "type": "load"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {  "id": "issue13132",
 | 
				
			||||||
 | 
					       "file": "pdfs/issue13132.pdf",
 | 
				
			||||||
 | 
					       "md5": "1b28964b9188047bc6c786302c95029f",
 | 
				
			||||||
 | 
					       "link": true,
 | 
				
			||||||
 | 
					       "type": "other"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    {  "id": "issue4722",
 | 
					    {  "id": "issue4722",
 | 
				
			||||||
       "file": "pdfs/issue4722.pdf",
 | 
					       "file": "pdfs/issue4722.pdf",
 | 
				
			||||||
       "md5": "a42ca858af7d179358f92f47e57c0fed",
 | 
					       "md5": "a42ca858af7d179358f92f47e57c0fed",
 | 
				
			||||||
 | 
				
			|||||||
@ -313,10 +313,9 @@ class PDFScriptingManager {
 | 
				
			|||||||
    if (element) {
 | 
					    if (element) {
 | 
				
			||||||
      element.dispatchEvent(new CustomEvent("updatefromsandbox", { detail }));
 | 
					      element.dispatchEvent(new CustomEvent("updatefromsandbox", { detail }));
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      if (value !== undefined && value !== null) {
 | 
					      delete detail.id;
 | 
				
			||||||
      // The element hasn't been rendered yet, use the AnnotationStorage.
 | 
					      // The element hasn't been rendered yet, use the AnnotationStorage.
 | 
				
			||||||
        this._pdfDocument?.annotationStorage.setValue(id, value);
 | 
					      this._pdfDocument?.annotationStorage.setValue(id, detail);
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user