{"id":14062838,"url":"https://github.com/zboris12/zgapdfsigner","last_synced_at":"2026-04-18T05:08:57.879Z","repository":{"id":63462805,"uuid":"537806573","full_name":"zboris12/zgapdfsigner","owner":"zboris12","description":"A javascript tool to sign a pdf in web browser, google apps script and nodejs.","archived":false,"fork":false,"pushed_at":"2025-09-28T07:27:02.000Z","size":350,"stargazers_count":30,"open_issues_count":0,"forks_count":8,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-28T09:20:26.424Z","etag":null,"topics":["google-apps-script","javascript","ltv","pdf-encryption","pdf-sign","pdf-signature","tsa","typescript"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zboris12.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-09-17T12:48:13.000Z","updated_at":"2025-09-28T07:27:06.000Z","dependencies_parsed_at":"2024-03-24T10:25:45.504Z","dependency_job_id":"c6b6ccab-2159-49bc-a42c-831ae5f24eeb","html_url":"https://github.com/zboris12/zgapdfsigner","commit_stats":{"total_commits":23,"total_committers":3,"mean_commits":7.666666666666667,"dds":"0.13043478260869568","last_synced_commit":"4136451365e9c828db20cb1d052b6da2ca70c947"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/zboris12/zgapdfsigner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zboris12%2Fzgapdfsigner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zboris12%2Fzgapdfsigner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zboris12%2Fzgapdfsigner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zboris12%2Fzgapdfsigner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zboris12","download_url":"https://codeload.github.com/zboris12/zgapdfsigner/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zboris12%2Fzgapdfsigner/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31957158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["google-apps-script","javascript","ltv","pdf-encryption","pdf-sign","pdf-signature","tsa","typescript"],"created_at":"2024-08-13T07:02:31.185Z","updated_at":"2026-04-18T05:08:57.863Z","avatar_url":"https://github.com/zboris12.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\u003cimg src=\"logo.png\" title=\"zgapdfsigner\"\u003e\u003c/div\u003e\n\n# ZgaPdfSigner\n![version](https://img.shields.io/github/package-json/v/zboris12/zgapdfsigner)\n![license](https://img.shields.io/github/license/zboris12/zgapdfsigner)\n![build status](https://github.com/zboris12/zgapdfsigner/actions/workflows/build.yml/badge.svg)\n\nA javascript tool to sign a pdf or set protection of a pdf in web browser.  \nAnd it is more powerful when used in [Google Apps Script](https://developers.google.com/apps-script) or [nodejs](https://nodejs.org/).  \n\nPS: __ZGA__ is the abbreviation of my father's name.  \nAnd I use this name to hope the merits from this application will be dedicated to my parents.\n\n## Main features\n\n* Sign a pdf with an invisible pkcs#7 signature.\n* Sign a pdf with a visible pkcs#7 signature by drawing an image or a text or both.\n* A visible signature can be placed on multiple pages. (In the same position)\n* Sign a pdf and set [DocMDP](https://github.com/zboris12/zgapdfsigner/wiki/API#note).\n* Add a new signature to a pdf if it has been signed already. (An incremental update)\n* Add a document timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser :sunflower:)\n* Sign a pdf with a timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser :sunflower:)\n* Enable signature's [LTV](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser :sunflower:)\n* Set password protection to a pdf. Supported algorithms:\n  * 40bit RC4 Encryption\n  * 128bit RC4 Encryption\n  * 128bit AES Encryption\n  * 256bit AES Encryption\n* Set public-key certificate protection to a pdf.\n  Supported algorithms are as same as the password protection.\n\n## About signing with [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note) and [LTV](https://github.com/zboris12/zgapdfsigner/wiki/API#note)\n\nBecause of the [CORS](https://github.com/zboris12/zgapdfsigner/wiki/API#note) security restrictions in web browser,\nsigning with a timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note) or enabling [LTV](https://github.com/zboris12/zgapdfsigner/wiki/API#note) can only be used in [Google Apps Script](https://developers.google.com/apps-script) or [nodejs](https://nodejs.org/).  \n:sunflower: However, if you can avoid the [CORS](https://github.com/zboris12/zgapdfsigner/wiki/API#note) security restrictions\nby creating your own service or providing a reverse proxy server, these features are also available in web browser.\n\n## The Dependencies\n\n* [pdf-lib](https://pdf-lib.js.org/)\n* [node-forge](https://github.com/digitalbazaar/forge)\n* [follow-redirects](https://github.com/follow-redirects/follow-redirects)\n* [pako](https://github.com/nodeca/pako) - For drawing text\n* [pdf-fontkit](https://github.com/znacloud/pdf-fontkit) - For drawing text\n\n## How to use this tool\n\n:question: For more details please see the [wiki](https://github.com/zboris12/zgapdfsigner/wiki/API).\n\n### Web Browser\nJust import the dependencies and this tool.\n```html\n\u003cscript src=\"https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://unpkg.com/node-forge@1.3.1/dist/forge.min.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/zgapdfsigner/dist/zgapdfsigner.min.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\n```\nWhen drawing text for signature, importing fontkit and pako library is necessary.\n```html\n\u003cscript src=\"https://unpkg.com/pdf-fontkit@1.8.9/dist/fontkit.umd.min.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://unpkg.com/pako@1.0.11/dist/pako_inflate.min.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\n```\nThanks to [znacloud](https://github.com/znacloud/pdf-fontkit) for fixing the font subsetting issue in [@pdf-lib/fontkit](https://github.com/Hopding/fontkit).\n\n### [Google Apps Script](https://developers.google.com/apps-script)\nLoad the dependencies and this tool.\n```js\n// Simulate setTimeout function for pdf-lib\nfunction setTimeout(func, sleep){\n  Utilities.sleep(sleep);\n  func();\n}\n// Simulate clearTimeout function for pdf-fontkit\nfunction clearTimeout(timeoutID){\n  // Do nothing\n}\n// Simulate window for node-forge\nvar window = globalThis;\n// Load pdf-lib\neval(UrlFetchApp.fetch(\"https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js\").getContentText());\n// It is necessary for drawing text for signature.\neval(UrlFetchApp.fetch(\"https://unpkg.com/pdf-fontkit@1.8.9/dist/fontkit.umd.min.js\").getContentText());\n// Load pako, It is necessary for drawing text for signature.\neval(UrlFetchApp.fetch(\"https://unpkg.com/pako@1.0.11/dist/pako_inflate.min.js\").getContentText());\n// Load node-forge\neval(UrlFetchApp.fetch(\"https://unpkg.com/node-forge@1.3.1/dist/forge.min.js\").getContentText());\n// Load ZgaPdfSigner\neval(UrlFetchApp.fetch(\"https://cdn.jsdelivr.net/npm/zgapdfsigner/dist/zgapdfsigner.min.js\").getContentText());\n```\nOr simply import the library of [ZgaPdfToolkit](https://script.google.com/macros/library/d/1T0UPf50gGp2fJ4dR1rZfEFgKYC5VpCwUVooCRNySiL7klvIUVsFBCZ9m/7)\n1. Add the library of ZgaPdfToolkit to your project, and suppose the id of library you defined is \"pdfkit\".  \n   Script id: `1T0UPf50gGp2fJ4dR1rZfEFgKYC5VpCwUVooCRNySiL7klvIUVsFBCZ9m`\n2. Load the library.\n```js\npdfkit.loadZga(globalThis);\n```\n\n### [nodejs](https://nodejs.org/)\n1. Install\n```\nnpm install zgapdfsigner\n```\nIf using [typescript](https://www.typescriptlang.org/) for development, installation of [definitely typed for node-forge](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node-forge) is necessary.\n```\nnpm install --save-dev @types/node-forge\n```\n2. Import\n```js\n// CommonJS Mode\nconst Zga = require(\"zgapdfsigner\");\n// ES Module Mode\nimport { default as Zga } from \"zgapdfsigner\";\n// Typescript\nimport * as Zga from \"zgapdfsigner\";\n```\n\n## Let's sign\n\nSign with an invisible signature.\n\n```js\n/**\n * @param {ArrayBuffer} pdf\n * @param {ArrayBuffer} cert\n * @param {string} pwd\n * @return {Promise\u003cBlob\u003e}\n */\nasync function sign1(pdf, cert, pwd){\n  /** @type {SignOption} */\n  var sopt = {\n    p12cert: cert,\n    pwd: pwd,\n    permission: 1,\n  };\n  var signer = new Zga.PdfSigner(sopt);\n  var u8arr = await signer.sign(pdf);\n  return new Blob([u8arr], {\"type\" : \"application/pdf\"});\n}\n```\n\nSign with a visible signature of an image.\n\n```js\n/**\n * @param {ArrayBuffer} pdf\n * @param {ArrayBuffer} cert\n * @param {string} pwd\n * @param {ArrayBuffer} imgdat\n * @param {string} imgtyp\n * @return {Promise\u003cBlob\u003e}\n */\nasync function sign2(pdf, cert, pwd, imgdat, imgtyp){\n  /** @type {SignOption} */\n  var sopt = {\n    p12cert: cert,\n    pwd: pwd,\n    drawinf: {\n      area: {\n        x: 25,  // left\n        y: 150, // top\n        w: 60,  // width\n        h: 60,  // height\n      },\n      imgInfo: {\n        imgData: imgdat,\n        imgType: imgtyp,\n      },\n    },\n  };\n  var signer = new Zga.PdfSigner(sopt);\n  var u8arr = await signer.sign(pdf);\n  return new Blob([u8arr], {\"type\" : \"application/pdf\"});\n}\n```\n\nSign with a visible signature by drawing a text.\n\n```js\n/**\n * @param {ArrayBuffer} pdf\n * @param {ArrayBuffer} cert\n * @param {string} pwd\n * @param {string} txt\n * @param {ArrayBuffer} fontdat\n * @return {Promise\u003cBlob\u003e}\n */\nasync function sign3(pdf, cert, pwd, txt, fontdat){\n  /** @type {SignOption} */\n  var sopt = {\n    p12cert: cert,\n    pwd: pwd,\n    drawinf: {\n      area: {\n        x: 25,  // left\n        y: 150, // top\n        w: 60,  // width\n        h: 60,  // height\n      },\n      textInfo: {\n        text: txt,\n        fontData: fontdat,\n        color: \"#00f0f1\",\n        size: 16,\n      },\n    },\n  };\n  var signer = new Zga.PdfSigner(sopt);\n  var u8arr = await signer.sign(pdf);\n  return new Blob([u8arr], {\"type\" : \"application/pdf\"});\n}\n```\n\nUse it in [Google Apps Script](https://developers.google.com/apps-script)\n\n```js\n/**\n * @param {string} pwd Passphrase of certificate\n * @return {Promise}\n */\nasync function createPdf(pwd){\n  // Load pdf, certificate\n  var pdfBlob = DriveApp.getFilesByName(\"_test.pdf\").next().getBlob();\n  var certBlob = DriveApp.getFilesByName(\"_test.pfx\").next().getBlob();\n  // Sign the pdf\n  /** @type {SignOption} */\n  var sopt = {\n    p12cert: certBlob.getBytes(),\n    pwd,\n    signdate: \"1\",\n    ltv: 1,\n  };\n  var signer = new Zga.PdfSigner(sopt);\n  var u8arr = await signer.sign(pdfBlob.getBytes());\n  // Save the result pdf to some folder\n  var fld = DriveApp.getFolderById(\"a folder's id\");\n  fld.createFile(Utilities.newBlob(u8arr, \"application/pdf\").setName(\"signed_test.pdf\"));\n}\n```\n\nUse queryPassword function in [ZgaPdfToolkit](https://script.google.com/macros/library/d/1T0UPf50gGp2fJ4dR1rZfEFgKYC5VpCwUVooCRNySiL7klvIUVsFBCZ9m/5).\n\n```js\nfunction myfunction(){\n  var spd = SpreadsheetApp.getActiveSpreadsheet();\n  pdfkit.queryPassword(\"createPdf\", \"Please input the passphrase\", spd.getName());\n}\n```\n\nUse it in [nodejs](https://nodejs.org/)\n```js\nconst m_fs = require(\"fs\");\nconst m_path = require(\"path\");\nasync function main(){\n  /** @type {string} */\n  var pdfPath = m_path.join(__dirname, \"_test.pdf\");\n  /** @type {string} */\n  var pfxPath = m_path.join(__dirname, \"_test.pfx\");\n  /** @type {string} */\n  var ps = \"\";\n  /** @type {string} */\n  var imgPath = m_path.join(__dirname, \"_test.png\");\n  /** @type {string} */\n  var txt = \"I am a test string!\";\n  /** @type {string} */\n  var fontPath = m_path.join(__dirname, \"_test.ttf\");\n\n  if(process.argv.length \u003e 3){\n    pfxPath = process.argv[2];\n    ps = process.argv[3];\n  }else if(process.argv[2]){\n    ps = process.argv[2];\n  }\n\n  if(!ps){\n    // throw new Error(\"The passphrase is not specified.\");\n    pfxPath = \"\";\n  }\n\n  /** @type {Buffer} */\n  var pdf = m_fs.readFileSync(pdfPath);\n  /** @type {Buffer} */\n  var pfx = null;\n  if(pfxPath){\n    pfx = m_fs.readFileSync(pfxPath);\n  }\n  /** @type {Buffer} */\n  var img = null;\n  /** @type {string} */\n  var imgType = \"\";\n  if(imgPath){\n    img = m_fs.readFileSync(imgPath);\n    imgType = m_path.extname(imgPath).slice(1);\n  }\n  /** @type {Buffer} */\n  var font = null;\n  if(fontPath){\n    font = m_fs.readFileSync(fontPath);\n  }\n\n  /** @type {SignOption} */\n  var sopt = {\n    p12cert: pfx,\n    pwd: ps,\n    permission: pfx ? 2 : 0,\n    signdate: \"1\",\n    reason: \"I have a test reason.\",\n    location: \"I am on the earth.\",\n    contact: \"zga@zga.com\",\n    ltv: 1,\n    debug: true,\n  };\n  if(img || txt){\n    sopt.drawinf = {\n      area: {\n        x: 25, // left\n        y: 50, // top\n        w: txt ? undefined : 60,\n        h: txt ? undefined : 100,\n      },\n      pageidx: \"2-3\", // Placed the signature on the 3rd page and the 4th page. (Indexes of pages start from 0)\n      imgInfo: img ? {\n        imgData: img,\n        imgType: imgType,\n      } : undefined,\n      textInfo: txt ? {\n        text: txt,\n        fontData: font,\n        size: 16,\n      } : undefined,\n    };\n  }\n\n  /** @type {Zga.PdfSigner} */\n  var ser = new Zga.PdfSigner(sopt);\n  /** @type {Uint8Array} */\n  var u8dat = await ser.sign(pdf);\n\n  if(u8dat){\n    /** @type {string} */\n    var outPath = m_path.join(__dirname, \"test_signed.pdf\");\n    m_fs.writeFileSync(outPath, u8dat);\n    console.log(\"Output file: \" + outPath);\n  }\n\n  console.log(\"Done\");\n}\n```\n\n## Let's protect the pdf\n\nSet password protection to the pdf.\n\n```js\n/**\n * @param {ArrayBuffer} pdf\n * @param {string} upwd\n * @param {string} opwd\n * @return {Promise\u003cBlob\u003e}\n */\nasync function protect1(pdf, upwd, opwd){\n  /** @type {EncryptOption} */\n  var eopt = {\n    mode: Zga.Crypto.Mode.RC4_40,\n    permissions: [\"modify\", \"annot-forms\", \"fill-forms\", \"extract\", \"assemble\"],\n    userpwd: upwd,\n    ownerpwd: opwd,\n  };\n  var cyptor = new Zga.PdfCryptor(eopt);\n  var pdfdoc = await cyptor.encryptPdf(pdf);\n  u8arr = await pdfdoc.save({\"useObjectStreams\": false});\n  return new Blob([u8arr], {\"type\" : \"application/pdf\"});\n}\n```\n\nSet public-key certificate protection to the pdf.\n\n```js\n/**\n * @param {ArrayBuffer} pdf\n * @param {ArrayBuffer} cert\n * @return {Promise\u003cBlob\u003e}\n */\nasync function protect2(pdf, cert){\n  /** @type {EncryptOption} */\n  var eopt = {\n    mode: Zga.Crypto.Mode.AES_128,\n    pubkeys: [{\n      c: cert,\n      p: [\"copy\", \"modify\", \"copy-extract\", \"annot-forms\", \"fill-forms\", \"extract\", \"assemble\"],\n    }],\n  };\n  var cyptor = new Zga.PdfCryptor(eopt);\n  var pdfdoc = await cyptor.encryptPdf(pdf);\n  u8arr = await pdfdoc.save({\"useObjectStreams\": false});\n  return new Blob([u8arr], {\"type\" : \"application/pdf\"});\n}\n```\n\nSign and set protection.\n\n```js\n/**\n * @param {ArrayBuffer} pdf\n * @param {ArrayBuffer} cert\n * @param {string} pwd\n * @param {string} opwd\n * @return {Promise\u003cBlob\u003e}\n */\nasync function signAndProtect1(pdf, cert, pwd, opwd){\n  /** @type {SignOption} */\n  var sopt = {\n    p12cert: cert,\n    pwd: pwd,\n  };\n  /** @type {EncryptOption} */\n  var eopt = {\n    mode: Zga.Crypto.Mode.RC4_128,\n    permissions: [\"modify\", \"annot-forms\", \"fill-forms\", \"extract\", \"assemble\"],\n    ownerpwd: opwd,\n  };\n  var signer = new Zga.PdfSigner(sopt);\n  var u8arr = await signer.sign(pdf, eopt);\n  return new Blob([u8arr], {\"type\" : \"application/pdf\"});\n}\n```\n\nSign and set protection by the same certificate.\n\n```js\n/**\n * @param {ArrayBuffer} pdf\n * @param {ArrayBuffer} cert\n * @param {string} pwd\n * @return {Promise\u003cBlob\u003e}\n */\nasync function signAndProtect2(pdf, cert, pwd){\n  /** @type {SignOption} */\n  var sopt = {\n    p12cert: cert,\n    pwd: pwd,\n  };\n  /** @type {EncryptOption} */\n  var eopt = {\n    mode: Zga.Crypto.Mode.AES_256,\n    permissions: [\"modify\", \"annot-forms\", \"fill-forms\", \"extract\", \"assemble\"],\n    pubkeys: [],\n  };\n  var signer = new Zga.PdfSigner(sopt);\n  var u8arr = await signer.sign(pdf, eopt);\n  return new Blob([u8arr], {\"type\" : \"application/pdf\"});\n}\n```\n\n## Thanks\n* The module of setting protection was almost migrated from [TCPDF](http://www.tcpdf.org).\n\n## License\n\nThis tool is available under the\n[MIT license](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzboris12%2Fzgapdfsigner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzboris12%2Fzgapdfsigner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzboris12%2Fzgapdfsigner/lists"}