Retrieving and Putting Values for PDF Forms using Google Apps Script

Gists

This is a sample script for retrieving and putting values for PDF Forms using Google Apps Script.

PDF can have the PDF Form for inputting the values in the PDF by the user. Ref Recently, I had a situation that required me to retrieve and put the values to the PDF Form using Google Apps Script. In order to achieve this, I created a Class object with Google Apps Script. That is as follows.

Class PdfForm

In this Class, pdf-lib is loaded and PDF data is parsed with pdf-lib. When you test the following sample scripts, first, please copy and paste this Class to the script editor of Google Apps Script. The sample scripts call this Class.

/**
 * ### Description
 * This is a Class object for retrieving and putting values for the PDF form using Google Apps Script.
 *
 * Author: Tanaike ( https://tanaikech.github.io/ )
 */
class PdfForm {
  /**
   * ### Description
   * Constructor of this class.
   *
   * @return {void}
   */
  constructor(obj = {}) {
    this.cdnjs = "https://cdn.jsdelivr.net/npm/pdf-lib/dist/pdf-lib.min.js"; // or "https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js"
    this.loadPdfLib_();
    this.standardFont = null;
    this.customFont = null;

    if (obj.standardFont && typeof obj.standardFont == "string") {
      this.standardFont = obj.standardFont;
    } else if (obj.customFont && obj.customFont.toString() == "Blob") {
      this.customFont = obj.customFont;
      this.cdnFontkit = "https://unpkg.com/@pdf-lib/fontkit/dist/fontkit.umd.min.js";
      this.loadFontkit_();
    }
  }

  /**
   * ### Description
   * Get values from each field of PDF form using pdf-lib.
   *
   * @param {Object} blob Blob of PDF data by retrieving with Google Apps Script.
   * @return {Object} Object including the values of PDF form.
   */
  getValues(blob) {
    return new Promise(async (resolve, reject) => {
      try {
        const pdfDoc = await this.getPdfDocFromBlob_(blob).catch(err => reject(err));
        const form = pdfDoc.getForm();
        const { PDFTextField, PDFDropdown, PDFCheckBox, PDFRadioGroup } = this.PDFLib;
        const obj = form.getFields().map(function (f) {
          const retObj = { name: f.getName() };
          if (f instanceof PDFTextField) {
            retObj.value = f.getText();
            retObj.type = "Textbox";
          } else if (f instanceof PDFDropdown) {
            retObj.value = f.getSelected();
            retObj.options = f.getOptions();
            retObj.type = "Dropdown";
          } else if (f instanceof PDFCheckBox) {
            retObj.value = f.isChecked();
            retObj.type = "Checkbox";
          } else if (f instanceof PDFRadioGroup) {
            retObj.value = f.getSelected();
            retObj.options = f.getOptions();
            retObj.type = "Radiobutton";
          } else {
            retObj.type = "Unsupported type";
          }
          return retObj;
        });
        resolve(obj);
      } catch (e) {
        reject(e);
      }
    });
  }

  /**
   * ### Description
   * Put values to each field of PDF Forms using pdf-lib.
   *
   * @param {Object} blob Blob of PDF data by retrieving with Google Apps Script.
   * @param {Object} object An array object including the values to PDF forms.
   * @return {Object} Blob of updated PDF data.
   */
  setValues(blob, object) {
    return new Promise(async (resolve, reject) => {
      try {
        const pdfDoc = await this.getPdfDocFromBlob_(blob).catch(err => reject(err));
        const form = pdfDoc.getForm();
        if (this.standardFont || this.customFont) {
          await this.setCustomFont_(pdfDoc, form);
        }
        const { PDFTextField, PDFDropdown, PDFCheckBox, PDFRadioGroup } = this.PDFLib;
        for (let { name, value } of object) {
          const field = form.getField(name);
          if (field instanceof PDFTextField) {
            field.setText(value);
          } else if (field instanceof PDFDropdown) {
            if (field.isMultiselect()) {
              for (let v of value) {
                field.select(v);
              }
            } else {
              field.select(value);
            }
          } else if (field instanceof PDFCheckBox) {
            field[value ? "check" : "uncheck"]();
          } else if (field instanceof PDFRadioGroup) {
            field.select(value);
          }
        }
        const bytes = await pdfDoc.save();
        const [filename, extension] = blob.getName().split(".");
        const newBlob = Utilities.newBlob([...new Int8Array(bytes)], MimeType.PDF, `${filename}_updated${extension ? ("." + extension) : ""}`);
        resolve(newBlob);
      } catch (e) {
        reject(e);
      }
    });
  }

  /**
   * ### Description
   * Load pdf-lib. https://pdf-lib.js.org/docs/api/classes/pdfdocument
   *
   * @return {void}
   */
  loadPdfLib_() {
    eval(UrlFetchApp.fetch(this.cdnjs).getContentText().replace(/setTimeout\(.*?,.*?(\d*?)\)/g, "Utilities.sleep($1);return t();"));
  }

  /**
   * ### Description
   * Load fontkit. https://github.com/Hopding/fontkit
   *
   * @return {void}
   */
  loadFontkit_() {
    eval(UrlFetchApp.fetch(this.cdnFontkit).getContentText());
  }

  /**
   * ### Description
   * Get PDF document object using pdf-lib.
   *
   * @param {Object} blob Blob of PDF data by retrieving with Google Apps Script.
   * @return {Object} PDF document object using pdf-lib.
   */
  async getPdfDocFromBlob_(blob) {
    if (blob.toString() != "Blob") {
      throw new error("Please set PDF blob.");
    }
    return await this.PDFLib.PDFDocument.load(new Uint8Array(blob.getBytes()), { updateMetadata: true });
  }

  /**
   * ### Description
   * Set custom font to PDF form.
   *
   * @param {Object} pdfDoc Object of PDF document.
   * @param {Object} form Object of PDF form.
   * @return {void}
   */
  async setCustomFont_(pdfDoc, form) {
    let customfont;
    if (this.standardFont) {
      customfont = await pdfDoc.embedFont(this.PDFLib.StandardFonts[this.standardFont]);
    } else if (this.customFont) {
      pdfDoc.registerFontkit(this.fontkit);
      customfont = await pdfDoc.embedFont(new Uint8Array(this.customFont.getBytes()));
    }

    // Ref: https://github.com/Hopding/pdf-lib/issues/1152
    const rawUpdateFieldAppearances = form.updateFieldAppearances.bind(form);
    form.updateFieldAppearances = function () {
      return rawUpdateFieldAppearances(customfont);
    };

  }
}

Updated on August 5, 2023: Standard font and custom font can be used.

Limitations

  • In the current stage, the fields of the textbox, the checkbox, the dropdown list, and the radio button of the PDF Form can be used.

Preparation

This is a sample PDF file for testing the following sample scripts.

The image converted from this PDF is as follows.

When you test the following sample scripts, please download this PDF file and upload it to your Google Drive. And please retrieve the file ID of the PDF file.

Sample 1: Retrieve values from PDF form

In this sample script, the values are retrieved from each field of the PDF form.

function getValuesFromPdfForm() {
  const fileId = "###"; // Please set the file ID of the PDF file on Google Drive.

  const blob = DriveApp.getFileById(fileId).getBlob();
  const PF = new PdfForm();
  PF.getValues(blob)
    .then((res) => console.log(res))
    .catch((err) => console.log(err));
}

When the above sample PDF file is used, the following value is obtained.

[
  {
    "name": "textbox.sample1.sample1.page1",
    "value": "sample text",
    "type": "Textbox"
  },
  {
    "name": "dropdownlist.sample2.sample1.page1",
    "value": ["sample option3"],
    "options": [
      "sample option1",
      "sample option2",
      "sample option3",
      "sample option4",
      "sample option5"
    ],
    "type": "Dropdown"
  },
  {
    "name": "checkbox.sample3.checkbox1.page1",
    "value": true,
    "type": "Checkbox"
  },
  {
    "name": "checkbox.sample3.checkbox2.page1",
    "value": true,
    "type": "Checkbox"
  },
  {
    "name": "checkbox.sample3.checkbox3.page1",
    "value": false,
    "type": "Checkbox"
  },
  {
    "name": "radiobutton.sample4.page1",
    "value": "radiobutton.sample4.radiobutton5.page1",
    "options": [
      "radiobutton.sample4.radiobutton1.page1",
      "radiobutton.sample4.radiobutton2.page1",
      "radiobutton.sample4.radiobutton3.page1",
      "radiobutton.sample4.radiobutton4.page1",
      "radiobutton.sample4.radiobutton5.page1"
    ],
    "type": "Radiobutton"
  }
]

Each value of name is the unique name on all pages in the PDF file.

Sample 2: Put values to PDF form

In this sample script, the values are put in each field of the PDF form. In order to put the values into PDF Form, please do the following flow.

  1. Retrieve the field names from PDF Form. In this case, you can retrieve them using the above sample script of getValuesFromPdfForm(). Please correspond the input values to the field names. As the current specification of PDF format, it seems that it is required to be that the field names are unique names on all pages in a PDF.

  2. Create an object for putting values in PDF Form using the input values and the field names.

  3. Put the values using the following sample script.

function setValuesToPdfForm() {
  const fileId = "###"; // Please set the file ID of the PDF file on Google Drive.

  // Please set an object for putting values to PDF form.
  // Before you use this script, please retrieve "name" of each field of the PDF form using the function "getValuesFromPDFForm".
  const obj = [
    { name: "textbox.sample1.sample1.page1", value: "updated1" },
    { name: "dropdownlist.sample2.sample1.page1", value: "sample option5" },
    { name: "checkbox.sample3.checkbox1.page1", value: true },
    { name: "checkbox.sample3.checkbox2.page1", value: false },
    { name: "checkbox.sample3.checkbox3.page1", value: true },
    {
      name: "radiobutton.sample4.page1",
      value: "radiobutton.sample4.radiobutton2.page1",
    },
  ];

  const blob = DriveApp.getFileById(fileId).getBlob();
  const PF = new PdfForm();
  PF.setValues(blob, obj)
    .then((newBlob) => DriveApp.createFile(newBlob))
    .catch((err) => console.log(err));
}

When this script is run with the above sample PDF file, you can see a new PDF file in the root folder. When you open the new PDF file, you can see the updated fields of the PDF Form.

The image converted from the updated PDF is as follows.

Sample 3: Put values to PDF form with custom font

In this sample script, the above script is used with the custom font. In this case, please prepare the font of TTF and OTF on your Google Drive, and retrieve the file ID.

function setValuesToPdfForm2() {
  const fileId = "###"; // Please set the file ID of the PDF file on Google Drive.
  const fileIdOfFontFile = "###"; // File ID of ttf and otf file you want to use.

  // Please set an object for putting values to PDF form.
  // Before you use this script, please retrieve "name" of each field of PDF form using the function "getValuesFromPDFForm".
  const obj = [
    { "name": "textbox.sample1.sample1.page1", "value": "サンプルテキスト" },
    { "name": "dropdownlist.sample2.sample1.page1", "value": "sample option5" },
    { "name": "checkbox.sample3.checkbox1.page1", "value": true },
    { "name": "checkbox.sample3.checkbox2.page1", "value": false },
    { "name": "checkbox.sample3.checkbox3.page1", "value": true },
    { "name": "radiobutton.sample4.page1", "value": "radiobutton.sample4.radiobutton2.page1" }
  ];

  const customFont = DriveApp.getFileById(fileIdOfFontFile).getBlob();
  const PF = new PdfForm({ customFont });
  
  // or, when you want to use the standard font.
  // const standardFont = "TimesRoman";
  // const PF = new PdfForm({ standardFont });

  const blob = DriveApp.getFileById(fileId).getBlob();
  PF.setValues(blob, obj)
    .then((newBlob) => DriveApp.createFile(newBlob))
    .catch((err) => console.log(err));
}

The image converted from the updated PDF is as follows.

References

 Share!