Compare commits

..

20 Commits
2.5.1 ... main

Author SHA1 Message Date
zboris12 0d39f569ae Changed to allow TSA feature in web browser 2024-08-24 12:17:43 +09:00
zboris12 e5372e2652 Added explanation of simulating clearTimeout function. 2024-08-19 21:54:15 +09:00
zboris12 9706bfe31a Improved to support font subsetting and changed to use pdf-fontkit. 2024-08-19 21:33:38 +09:00
zboris12 4def4068f7 Improved to reuse the font if it is already embedded in the PDF. 2024-08-18 21:32:27 +09:00
zboris12 1f92ca61d7 Added support for typescript development. 2024-08-17 11:51:46 +09:00
zboris12 e1ed49d43f Made comment more accurate. 2024-08-10 20:34:50 +09:00
zboris12 de687d8bf7 Fixed a bug of break line. 2024-08-10 20:12:19 +09:00
zboris12 0505fd1e0e Working on text signature and placing on multiple pages. 2024-08-09 21:57:20 +09:00
zboris12 fd2b97d5b3 Working on text signature. 2024-08-04 20:07:21 +09:00
zboris12 fa4eb2d5c0 Changed the minimized js URL described in readme to jsdelivr.net 2024-04-22 21:19:54 +09:00
zboris12 cc045171da Fixed the abnormal display of some icons in readme and the autofix by npm publish in package.json 2024-04-21 12:22:31 +09:00
zboris12 90474e9393 Changed verion of follow-redirects to 1.15.6 2024-04-21 11:45:42 +09:00
zboris12 0473f700a1 Changed position of badges. 2024-03-24 21:37:31 +09:00
zboris12 8417fad06b Added some badges to readme. 2024-03-24 21:33:56 +09:00
zboris12 3216d917ed Added to show build result after github-actions. Again. 2024-03-24 20:51:44 +09:00
zboris12 9a47c162aa Made build.sh easier to read. 2024-03-24 20:46:49 +09:00
zboris12 d5b9c4ad22 Added to show build result after github-actions. 2024-03-24 18:47:08 +09:00
zboris12 201e5a99b9 Fixed build error of "Syntax error: redirection unexpected". 2024-03-24 18:44:00 +09:00
zboris12 2c8de1a007 Fixed "Permission denied" on github-actions. 2024-03-24 18:22:01 +09:00
zboris12 f4cbbcdb7c Changed google compile method from java to npx. 2024-03-24 18:15:37 +09:00
21 changed files with 1936 additions and 478 deletions

View File

@ -1,2 +0,0 @@
java="C:\java8\jre\bin\java.exe"
closure="C:\closure-compiler\closure-compiler-v20220104.jar"

16
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Build
run-name: Build for browser and GAS
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['lts/*']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build

115
README.md
View File

@ -1,6 +1,10 @@
<div align="center"><img src="logo.png" title="zgapdfsigner"></div> <div align="center"><img src="logo.png" title="zgapdfsigner"></div>
# ZgaPdfSigner # ZgaPdfSigner
![version](https://img.shields.io/github/package-json/v/zboris12/zgapdfsigner)
![license](https://img.shields.io/github/license/zboris12/zgapdfsigner)
![build status](https://github.com/zboris12/zgapdfsigner/actions/workflows/build.yml/badge.svg)
A javascript tool to sign a pdf or set protection of a pdf in web browser. A javascript tool to sign a pdf or set protection of a pdf in web browser.
And it is more powerful when used in [Google Apps Script](https://developers.google.com/apps-script) or [nodejs](https://nodejs.org/). And it is more powerful when used in [Google Apps Script](https://developers.google.com/apps-script) or [nodejs](https://nodejs.org/).
@ -10,12 +14,13 @@ And I use this name to hope the merits from this application will be dedicated t
## Main features ## Main features
* Sign a pdf with an invisible pkcs#7 signature. * Sign a pdf with an invisible pkcs#7 signature.
* Sign a pdf with a visible pkcs#7 signature by drawing an image. * Sign a pdf with a visible pkcs#7 signature by drawing an image or a text or both.
* A visible signature can be placed on multiple pages. (In the same position)
* Sign a pdf and set [DocMDP](https://github.com/zboris12/zgapdfsigner/wiki/API#note). * Sign a pdf and set [DocMDP](https://github.com/zboris12/zgapdfsigner/wiki/API#note).
* Add a new signature to a pdf if it has been signed already. (An incremental update) * Add a new signature to a pdf if it has been signed already. (An incremental update)
* Add a document timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note). (:no_entry_sign:__Not__ available in web browser) * Add a document timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser :sunflower:)
* Sign a pdf with a timestamp from [TSA](https://github.com/zboris12/zgapdfsigner/wiki/API#note). (:no_entry_sign:__Not__ available in web browser) * 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:)
* Enable signature's [LTV](https://github.com/zboris12/zgapdfsigner/wiki/API#note). (:no_entry_sign:__Not__ available in web browser) * Enable signature's [LTV](https://github.com/zboris12/zgapdfsigner/wiki/API#note). ( :no_entry_sign:__Not__ available in web browser :sunflower:)
* Set password protection to a pdf. Supported algorithms: * Set password protection to a pdf. Supported algorithms:
* 40bit RC4 Encryption * 40bit RC4 Encryption
* 128bit RC4 Encryption * 128bit RC4 Encryption
@ -28,21 +33,34 @@ And I use this name to hope the merits from this application will be dedicated t
Because of the [CORS](https://github.com/zboris12/zgapdfsigner/wiki/API#note) security restrictions in web browser, Because of the [CORS](https://github.com/zboris12/zgapdfsigner/wiki/API#note) security restrictions in web browser,
signing 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/). signing 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/).
:sunflower: However, if you can avoid the [CORS](https://github.com/zboris12/zgapdfsigner/wiki/API#note) security restrictions
by creating your own service or providing a reverse proxy server, these features are also available in web browser.
## The Dependencies ## The Dependencies
* [pdf-lib](https://pdf-lib.js.org/) * [pdf-lib](https://pdf-lib.js.org/)
* [node-forge](https://github.com/digitalbazaar/forge) * [node-forge](https://github.com/digitalbazaar/forge)
* [follow-redirects](https://github.com/follow-redirects/follow-redirects)
* [pako](https://github.com/nodeca/pako) - For drawing text
* [pdf-fontkit](https://github.com/znacloud/pdf-fontkit) - For drawing text
## How to use this tool ## How to use this tool
:question: For more details please see the [wiki](https://github.com/zboris12/zgapdfsigner/wiki/API).
### Web Browser ### Web Browser
Just import the dependencies and this tool. Just import the dependencies and this tool.
```html ```html
<script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js" type="text/javascript"></script> <script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js" type="text/javascript"></script>
<script src="https://unpkg.com/node-forge@1.3.1/dist/forge.min.js" type="text/javascript"></script> <script src="https://unpkg.com/node-forge@1.3.1/dist/forge.min.js" type="text/javascript"></script>
<script src="https://github.com/zboris12/zgapdfsigner/releases/download/2.5.0/zgapdfsigner.min.js" type="text/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/zgapdfsigner/dist/zgapdfsigner.min.js" type="text/javascript"></script>
``` ```
When drawing text for signature, importing fontkit and pako library is necessary.
```html
<script src="https://unpkg.com/pdf-fontkit@1.8.9/dist/fontkit.umd.min.js" type="text/javascript"></script>
<script src="https://unpkg.com/pako@1.0.11/dist/pako_inflate.min.js" type="text/javascript"></script>
```
Thanks to [znacloud](https://github.com/znacloud/pdf-fontkit) for fixing the font subsetting issue in [@pdf-lib/fontkit](https://github.com/Hopding/fontkit).
### [Google Apps Script](https://developers.google.com/apps-script) ### [Google Apps Script](https://developers.google.com/apps-script)
Load the dependencies and this tool. Load the dependencies and this tool.
@ -52,16 +70,24 @@ function setTimeout(func, sleep){
Utilities.sleep(sleep); Utilities.sleep(sleep);
func(); func();
} }
// Simulate clearTimeout function for pdf-fontkit
function clearTimeout(timeoutID){
// Do nothing
}
// Simulate window for node-forge // Simulate window for node-forge
var window = globalThis; var window = globalThis;
// Load pdf-lib // Load pdf-lib
eval(UrlFetchApp.fetch("https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js").getContentText()); eval(UrlFetchApp.fetch("https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js").getContentText());
// It is necessary for drawing text for signature.
eval(UrlFetchApp.fetch("https://unpkg.com/pdf-fontkit@1.8.9/dist/fontkit.umd.min.js").getContentText());
// Load pako, It is necessary for drawing text for signature.
eval(UrlFetchApp.fetch("https://unpkg.com/pako@1.0.11/dist/pako_inflate.min.js").getContentText());
// Load node-forge // Load node-forge
eval(UrlFetchApp.fetch("https://unpkg.com/node-forge@1.3.1/dist/forge.min.js").getContentText()); eval(UrlFetchApp.fetch("https://unpkg.com/node-forge@1.3.1/dist/forge.min.js").getContentText());
// Load ZgaPdfSigner // Load ZgaPdfSigner
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/2.5.0/zgapdfsigner.min.js").getContentText()); eval(UrlFetchApp.fetch("https://cdn.jsdelivr.net/npm/zgapdfsigner/dist/zgapdfsigner.min.js").getContentText());
``` ```
Or simply import the library of [ZgaPdfToolkit](https://script.google.com/macros/library/d/1T0UPf50gGp2fJ4dR1rZfEFgKYC5VpCwUVooCRNySiL7klvIUVsFBCZ9m/5) Or simply import the library of [ZgaPdfToolkit](https://script.google.com/macros/library/d/1T0UPf50gGp2fJ4dR1rZfEFgKYC5VpCwUVooCRNySiL7klvIUVsFBCZ9m/7)
1. Add the library of ZgaPdfToolkit to your project, and suppose the id of library you defined is "pdfkit". 1. Add the library of ZgaPdfToolkit to your project, and suppose the id of library you defined is "pdfkit".
Script id: `1T0UPf50gGp2fJ4dR1rZfEFgKYC5VpCwUVooCRNySiL7klvIUVsFBCZ9m` Script id: `1T0UPf50gGp2fJ4dR1rZfEFgKYC5VpCwUVooCRNySiL7klvIUVsFBCZ9m`
2. Load the library. 2. Load the library.
@ -74,9 +100,18 @@ pdfkit.loadZga(globalThis);
``` ```
npm install zgapdfsigner npm install zgapdfsigner
``` ```
If 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.
```
npm install --save-dev @types/node-forge
```
2. Import 2. Import
```js ```js
// CommonJS Mode
const Zga = require("zgapdfsigner"); const Zga = require("zgapdfsigner");
// ES Module Mode
import { default as Zga } from "zgapdfsigner";
// Typescript
import * as Zga from "zgapdfsigner";
``` ```
## Let's sign ## Let's sign
@ -126,9 +161,11 @@ async function sign2(pdf, cert, pwd, imgdat, imgtyp){
w: 60, // width w: 60, // width
h: 60, // height h: 60, // height
}, },
imgInfo: {
imgData: imgdat, imgData: imgdat,
imgType: imgtyp, imgType: imgtyp,
}, },
},
}; };
var signer = new Zga.PdfSigner(sopt); var signer = new Zga.PdfSigner(sopt);
var u8arr = await signer.sign(pdf); var u8arr = await signer.sign(pdf);
@ -136,10 +173,41 @@ async function sign2(pdf, cert, pwd, imgdat, imgtyp){
} }
``` ```
Sign with a visible signature of drawing a text. Sign with a visible signature by drawing a text.
```js ```js
//TODO /**
* @param {ArrayBuffer} pdf
* @param {ArrayBuffer} cert
* @param {string} pwd
* @param {string} txt
* @param {ArrayBuffer} fontdat
* @return {Promise<Blob>}
*/
async function sign3(pdf, cert, pwd, txt, fontdat){
/** @type {SignOption} */
var sopt = {
p12cert: cert,
pwd: pwd,
drawinf: {
area: {
x: 25, // left
y: 150, // top
w: 60, // width
h: 60, // height
},
textInfo: {
text: txt,
fontData: fontdat,
color: "#00f0f1",
size: 16,
},
},
};
var signer = new Zga.PdfSigner(sopt);
var u8arr = await signer.sign(pdf);
return new Blob([u8arr], {"type" : "application/pdf"});
}
``` ```
Use it in [Google Apps Script](https://developers.google.com/apps-script) Use it in [Google Apps Script](https://developers.google.com/apps-script)
@ -191,6 +259,10 @@ async function main(){
var ps = ""; var ps = "";
/** @type {string} */ /** @type {string} */
var imgPath = m_path.join(__dirname, "_test.png"); var imgPath = m_path.join(__dirname, "_test.png");
/** @type {string} */
var txt = "I am a test string!";
/** @type {string} */
var fontPath = m_path.join(__dirname, "_test.ttf");
if(process.argv.length > 3){ if(process.argv.length > 3){
pfxPath = process.argv[2]; pfxPath = process.argv[2];
@ -219,6 +291,11 @@ async function main(){
img = m_fs.readFileSync(imgPath); img = m_fs.readFileSync(imgPath);
imgType = m_path.extname(imgPath).slice(1); imgType = m_path.extname(imgPath).slice(1);
} }
/** @type {Buffer} */
var font = null;
if(fontPath){
font = m_fs.readFileSync(fontPath);
}
/** @type {SignOption} */ /** @type {SignOption} */
var sopt = { var sopt = {
@ -232,16 +309,24 @@ async function main(){
ltv: 1, ltv: 1,
debug: true, debug: true,
}; };
if(img){ if(img || txt){
sopt.drawinf = { sopt.drawinf = {
area: { area: {
x: 25, // left x: 25, // left
y: 150, // top y: 50, // top
w: 60, w: txt ? undefined : 60,
h: 60, h: txt ? undefined : 100,
}, },
pageidx: "2-3", // Placed the signature on the 3rd page and the 4th page. (Indexes of pages start from 0)
imgInfo: img ? {
imgData: img, imgData: img,
imgType: imgType, imgType: imgType,
} : undefined,
textInfo: txt ? {
text: txt,
fontData: font,
size: 16,
} : undefined,
}; };
} }
@ -261,8 +346,6 @@ async function main(){
} }
``` ```
:question: For more details please see the [wiki](https://github.com/zboris12/zgapdfsigner/wiki/API).
## Let's protect the pdf ## Let's protect the pdf
Set password protection to the pdf. Set password protection to the pdf.
@ -368,8 +451,6 @@ async function signAndProtect2(pdf, cert, pwd){
} }
``` ```
:question: For more details please see the [wiki](https://github.com/zboris12/zgapdfsigner/wiki/API).
## Thanks ## Thanks
* The module of setting protection was almost migrated from [TCPDF](http://www.tcpdf.org). * The module of setting protection was almost migrated from [TCPDF](http://www.tcpdf.org).

58
build.sh Executable file
View File

@ -0,0 +1,58 @@
# !/bin/sh
# set -x
OUTFLDR=dist
if [ -d ${OUTFLDR} ]
then
rm -f ${OUTFLDR}/*
else
mkdir ${OUTFLDR}
fi
VER=$(sed -n -r "s/^.*\"version\": ?\"([0-9.]+)\".*$/\1/p" package.json)
GCCOPT="--charset UTF-8 --compilation_level SIMPLE_OPTIMIZATIONS --warning_level VERBOSE"
GCCEXT="--externs closure/google-ext.js --externs closure/forge-ext.js --externs closure/pdflib-ext.js --externs closure/zb-externs.js"
jss=""
while read js
do
if [ -n "${js}" ]
then
c=$(echo "${js}" | cut -b1)
if [ "$c" != "#" ]
then
outf="${OUTFLDR}/_${js}"
if [ "${js}" = "zgaindex.js" ]
then
sed -e "s/\/\/Only for nodejs Start\/\//\/*/g" -e "s/\/\/Only for nodejs End\/\//*\//g" -e "s/ver: \"\"/ver: \"${VER}\"/" "lib/${js}" > "${outf}"
else
sed -e "s/\/\/Only for nodejs Start\/\//\/*/g" -e "s/\/\/Only for nodejs End\/\//*\//g" "lib/${js}" > "${outf}"
fi
if [ $? -eq 0 ]
then
echo "Created js file: ${outf}"
jss="${jss} --js ${outf}"
else
echo "Failed create js file: ${outf}"
exit 10
fi
fi
fi
done <<EOF
zgafetch.js
zgacertsutil.js
zgapdfcryptor.js
zgapdfsigner.js
zgaindex.js
EOF
npx google-closure-compiler ${GCCOPT} ${GCCEXT} ${jss} --js_output_file ${OUTFLDR}/zgapdfsigner.min.js
if [ $? -eq 0 ]
then
echo "Build result:"
ls -l ${OUTFLDR}/*.min.js
else
echo "google-closure-compiler failed."
exit 20
fi
exit 0

View File

@ -1,180 +0,0 @@
const m_fs = require("fs");
const m_path = require("path");
const m_cp = require("child_process");
/** @const {string} */
const m_libpath = "lib/";
/** @const {string} */
const m_distpath = "dist/_";
/** @const {Array<string>} */
const m_targets = ["zgacertsutil.js", "zgapdfcryptor.js", "zgapdfsigner.js", "zgaindex.js"];
/** @const {Array<string>} */
const m_dists = [];
/**
* @param {string} raw
* @return {Uint8Array}
*/
function rawToU8arr(raw){
/** @type {Uint8Array} */
var arr = new Uint8Array(raw.length);
for(var i=0; i<raw.length; i++){
arr[i] = raw.charCodeAt(i);
}
return arr;
}
/** @const {number} */
const m_repcode = "*".charCodeAt(0);
/** @const {Uint8Array} */
const m_cprbufst = rawToU8arr("//Only for nodejs Start");
/** @const {number} */
const m_repidxst = 1;
/** @const {Uint8Array} */
const m_cprbufed = rawToU8arr("Only for nodejs End//");
/** @const {number} */
const m_repidxed = 19;
/** @type {boolean} */
var m_debug = false;
/**
* @param {Uint8Array} tgtbuf
* @param {number} idx
* @param {Uint8Array} cprbuf
* @return {boolean}
*/
function sameBuffer(tgtbuf, idx, cprbuf){
for(var i=0; i<cprbuf.length; i++,idx++){
if(idx >= tgtbuf.length){
return false;
}else if(tgtbuf[idx] != cprbuf[i]){
return false;
}
}
return true;
}
/**
* @param {string} js
*/
function fixjs(js){
/** @type {string} */
var jspath = m_path.join(__dirname, m_libpath + js);
/** @type {Buffer} */
var jsbuf = m_fs.readFileSync(jspath);
for(var i=0; i<jsbuf.length; i++){
if(sameBuffer(jsbuf, i, m_cprbufst)){
jsbuf[i + m_repidxst] = m_repcode;
}else if(sameBuffer(jsbuf, i, m_cprbufed)){
jsbuf[i + m_repidxed] = m_repcode;
}
}
jspath = m_distpath + js;
m_dists.push(jspath);
jspath = m_path.join(__dirname, jspath);
m_fs.writeFileSync(jspath, jsbuf);
if(m_debug){
console.log("Output file: " + jspath);
}
}
/**
* @param {string} js
*/
function deltmpjs(js){
/** @type {string} */
var jspath = m_path.join(__dirname, js);
m_fs.rmSync(jspath);
if(m_debug){
console.log("Deleted file: " + jspath);
}
}
/**
* @param {envfil}
* @return {Object<string, string>}
*/
function loadEnv(envfil){
/** @type {Object<string, string>} */
var retobj = {};
/** @type {string} */
var envpath = m_path.join(__dirname, envfil);
/** @type {Array<string>} */
var envs = m_fs.readFileSync(envpath, "utf8").split("\n");
envs.forEach(function(/** @type {string} */a_env){
a_env = a_env.trimStart();
if(a_env.charAt(0) != "#"){
var a_idx = a_env.indexOf("=");
if(a_idx > 0){
retobj[a_env.substring(0, a_idx)] = a_env.substring(a_idx + 1);
}
}
});
if(m_debug){
console.log("Environment:");
console.log(retobj);
}
return retobj;
}
function main(){
if(process.argv.indexOf("-debug") > 0){
m_debug = true;
}
/** @type {Object<string, string>} */
var env = loadEnv(".env");
/** @type {boolean} */
var flg = true;
if(!env.java){
console.error("Can't find java's execution path in .env file.");
flg = false;
}
if(!env.closure){
console.error("Can't find closure complier's path in .env file.");
flg = false;
}
if(!flg){
return;
}
m_targets.forEach(fixjs);
/** @type {Array<string>} */
var cmd = [env.java];
cmd.push("-jar " + env.closure);
cmd.push("--charset UTF-8");
cmd.push("--compilation_level SIMPLE_OPTIMIZATIONS");
cmd.push("--warning_level VERBOSE");
cmd.push("--externs closure/google-ext.js");
cmd.push("--externs closure/forge-ext.js");
cmd.push("--externs closure/pdflib-ext.js");
cmd.push("--externs closure/zb-externs.js");
m_dists.forEach(function(a_js){
cmd.push("--js " + a_js);
});
cmd.push("--js_output_file dist/zgapdfsigner.min.js");
if(m_debug){
console.log(cmd.join(" "));
}
console.log("Excuting google closure compiler...\n");
m_cp.exec(cmd.join(" "), function(a_err, a_stdout, a_stderr){
const a_rex = new RegExp("^" + m_distpath, "g");
// if(a_err){
// console.log(a_err);
// }
if(a_stdout){
console.log(a_stdout.replaceAll(a_rex, m_libpath));
}
if(a_stderr){
console.log(a_stderr.replaceAll(a_rex, m_libpath));
}
m_dists.forEach(deltmpjs);
console.log("Done");
});
}
main();

View File

@ -1,16 +0,0 @@
@echo off
set csr=\java\8\jre\bin\java.exe -jar \closure-compiler\closure-compiler-v20220104.jar --charset UTF-8 --compilation_level SIMPLE_OPTIMIZATIONS --warning_level VERBOSE
doskey csr=%csr% $*
set externs=--externs closure\google-ext.js --externs closure\forge-ext.js --externs closure\pdflib-ext.js --externs closure\zb-externs.js
rem main
set src=lib
set jss=--js %src%\zgacertsutil.js --js %src%\zgapdfcryptor.js --js %src%\zgapdfsigner.js --js %src%\zgaindex.js
echo $
set chkj=%%externs%% --checks_only %%jss%%
echo chkj=csr %chkj%
doskey chkj=%csr% %chkj%
set csrj=%%externs%% %%jss%% --js_output_file dist\zgapdfsigner.min.js
echo csrj=csr %csrj%
doskey csrj=%csr% %csrj%

View File

@ -8,6 +8,19 @@
*/ */
var PdfLoadOptions; var PdfLoadOptions;
/** @const */
var pako = {};
/**
* @param {Uint8Array} input
* @return {Uint8Array}
*/
pako.inflate = function(input){};
/** @constructor */
var Fontkit = function(){};
/** @const {Fontkit} */
var fontkit;
/** @const */ /** @const */
var PDFLib = {}; var PDFLib = {};
@ -23,6 +36,26 @@ PDFLib.copyStringIntoBuffer = function(str, buffer, offset){};
* @return {Uint8Array} * @return {Uint8Array}
*/ */
PDFLib.toUint8Array = function(input){}; PDFLib.toUint8Array = function(input){};
/**
* @param {string} text
* @return {string}
*/
PDFLib.cleanText = function(text){};
/**
* @param {string} text
* @return {Array<string>}
*/
PDFLib.lineSplit = function(text){};
/**
* @param {string} text
* @return {boolean}
*/
PDFLib.isNewlineChar = function(text){};
/**
* @param {string} fntnm
* @return {boolean}
*/
PDFLib.isStandardFont = function(fntnm){};
/** @constructor */ /** @constructor */
PDFLib.PDFDocument = function(){}; PDFLib.PDFDocument = function(){};
@ -50,6 +83,10 @@ PDFLib.PDFDocument.prototype.save = function(options){};
* @returns {Array<PDFLib.PDFPage>} * @returns {Array<PDFLib.PDFPage>}
*/ */
PDFLib.PDFDocument.prototype.getPages = function(){}; PDFLib.PDFDocument.prototype.getPages = function(){};
/**
* @returns {number}
*/
PDFLib.PDFDocument.prototype.getPageCount = function(){};
/** /**
* @param {ArrayBuffer|Uint8Array|string} png * @param {ArrayBuffer|Uint8Array|string} png
* @returns {Promise<PDFLib.PDFImage>} * @returns {Promise<PDFLib.PDFImage>}
@ -60,6 +97,32 @@ PDFLib.PDFDocument.prototype.embedPng = function(png){};
* @returns {Promise<PDFLib.PDFImage>} * @returns {Promise<PDFLib.PDFImage>}
*/ */
PDFLib.PDFDocument.prototype.embedJpg = function(jpg){}; PDFLib.PDFDocument.prototype.embedJpg = function(jpg){};
/**
* @typedef
* {{
* customName: (string|undefined),
* features: (Object<string, boolean>|undefined),
* subset: (boolean|undefined),
* }}
*/
var EmbedFontOptions;
/**
* @param {ArrayBuffer|Uint8Array} font
* @param {EmbedFontOptions=} options
* @returns {Promise<PDFLib.PDFFont>}
*/
PDFLib.PDFDocument.prototype.embedFont = function(font, options){};
/**
* @param {string} font
* @param {string=} customName
* @returns {PDFLib.PDFFont}
*/
PDFLib.PDFDocument.prototype.embedStandardFont = function(font, customName){};
/**
* @param {Fontkit} fkt
*/
PDFLib.PDFDocument.prototype.registerFontkit = function(fkt){};
/** /**
* @returns {Promise<number>} * @returns {Promise<number>}
*/ */
@ -268,6 +331,8 @@ PDFLib.PDFName.Annots;
* @return {PDFLib.PDFName} * @return {PDFLib.PDFName}
*/ */
PDFLib.PDFName.of = function(value){}; PDFLib.PDFName.of = function(value){};
/** @return {string} */
PDFLib.PDFName.prototype.asString = function(){};
/** @type {string} */ /** @type {string} */
PDFLib.PDFName.prototype.encodedName; PDFLib.PDFName.prototype.encodedName;
/** @type {number} */ /** @type {number} */
@ -290,10 +355,20 @@ PDFLib.PDFArray.prototype.push = function(object){};
* @return {PDFLib.PDFObject} * @return {PDFLib.PDFObject}
*/ */
PDFLib.PDFArray.prototype.get = function(idx){}; PDFLib.PDFArray.prototype.get = function(idx){};
/**
* @return {number}
*/
PDFLib.PDFArray.prototype.size = function(){};
/** /**
* @return {Array<PDFLib.PDFObject>} * @return {Array<PDFLib.PDFObject>}
*/ */
PDFLib.PDFArray.prototype.asArray = function(){}; PDFLib.PDFArray.prototype.asArray = function(){};
/**
* @param {number} idx
* @param {*} typ
* @return {PDFLib.PDFObject}
*/
PDFLib.PDFArray.prototype.lookupMaybe = function(idx, typ){};
/** /**
* @constructor * @constructor
@ -355,12 +430,55 @@ PDFLib.PDFImage.prototype.size = function(){};
/** @type {PDFLib.PDFRef} */ /** @type {PDFLib.PDFRef} */
PDFLib.PDFImage.prototype.ref; PDFLib.PDFImage.prototype.ref;
/** @constructor */
PDFLib.StandardFontEmbedder = function(){};
/**
* @param {string} fontName
* @param {string=} customName
* @return {PDFLib.StandardFontEmbedder}
*/
PDFLib.StandardFontEmbedder.for = function(fontName, customName){};
/** @constructor */
PDFLib.CustomFontEmbedder = function(){};
/**
* @param {Fontkit} fontkit
* @param {Uint8Array} fontData
* @param {string=} customName
* @return {PDFLib.CustomFontEmbedder}
*/
PDFLib.CustomFontEmbedder.for = function(fontkit, fontData, customName){};
/** @constructor */ /** @constructor */
PDFLib.PDFFont = function(){}; PDFLib.PDFFont = function(){};
/**
* @param {PDFLib.PDFRef} ref
* @param {PDFLib.PDFDocument} doc
* @param {PDFLib.StandardFontEmbedder|PDFLib.CustomFontEmbedder} embedder
* @return {PDFLib.PDFFont}
*/
PDFLib.PDFFont.of = function(ref, doc, embedder){};
/** @type {PDFLib.PDFRef} */ /** @type {PDFLib.PDFRef} */
PDFLib.PDFFont.prototype.ref; PDFLib.PDFFont.prototype.ref;
/** @constructor */ /** @type {string} */
PDFLib.StandardFonts = function(){}; PDFLib.PDFFont.prototype.name;
/**
* @param {string} text
* @return {PDFLib.PDFHexString }
*/
PDFLib.PDFFont.prototype.encodeText = function(text){};
/**
* @param {number} size
* @param {Object<string, boolean>=} options
* @return {number}
*/
PDFLib.PDFFont.prototype.heightAtSize = function(size, options){};
/**
* @param {string} text
* @param {number} size
* @return {number}
*/
PDFLib.PDFFont.prototype.widthOfTextAtSize = function(text, size){};
PDFLib.RotationTypes = {}; PDFLib.RotationTypes = {};
/** @type {string} */ /** @type {string} */
@ -392,14 +510,49 @@ PDFLib.PDFOperator = function(){};
* rotate: (PDFLib.Rotation|undefined), * rotate: (PDFLib.Rotation|undefined),
* xSkew: (PDFLib.Rotation|undefined), * xSkew: (PDFLib.Rotation|undefined),
* ySkew: (PDFLib.Rotation|undefined), * ySkew: (PDFLib.Rotation|undefined),
* graphicsState: (string|undefined),
* }} * }}
*/ */
var PdfDrawimgOption; var PdfDrawimgOption;
/** /**
* @param {string} name * @param {string} name
* @param {PdfDrawimgOption} options * @param {PdfDrawimgOption} options
* @return {Array<PDFLib.PDFOperator>}
*/ */
PDFLib.drawImage = function(name, options){}; PDFLib.drawImage = function(name, options){};
/**
* @constructor
*/
PDFLib.Color = function(){};
/**
* @typedef
* {{
* color: PDFLib.Color,
* font: string,
* graphicsState: (string|undefined),
* lineHeight: number,
* size: number,
* rotate: (PDFLib.Rotation|undefined),
* xSkew: (PDFLib.Rotation|undefined),
* ySkew: (PDFLib.Rotation|undefined),
* x: number,
* y: number,
* }}
*/
var DrawLinesOfTextOptions;
/**
* @param {Array<PDFLib.PDFHexString>} lines
* @param {DrawLinesOfTextOptions} options
* @return {Array<PDFLib.PDFOperator>}
*/
PDFLib.drawLinesOfText = function(lines, options){};
/**
* @param {number} red
* @param {number} green
* @param {number} blue
* @return {PDFLib.Color}
*/
PDFLib.rgb = function(red, green, blue){};
/** /**
* @constructor * @constructor
@ -440,6 +593,12 @@ PDFLib.PDFContentStream.of = function(dict, operators, encode){};
* @extends {PDFLib.PDFStream} * @extends {PDFLib.PDFStream}
*/ */
PDFLib.PDFRawStream = function(){}; PDFLib.PDFRawStream = function(){};
/** @type {PDFLib.PDFDict} */
PDFLib.PDFRawStream.prototype.dict;
/**
* @return {Uint8Array}
*/
PDFLib.PDFRawStream.prototype.getContents = function(){};
/** /**
* @constructor * @constructor

View File

@ -9,24 +9,75 @@
var TsaServiceInfo; var TsaServiceInfo;
/** /**
* the base point of x, y is top left corner. * the base point of x, y is top left corner.
* wDraw, hDraw: Only for internal process.
* @typedef * @typedef
* {{ * {{
* x: number, * x: number,
* y: number, * y: number,
* w: number, * w: (number|undefined),
* h: number, * h: (number|undefined),
* wDraw: (number|undefined),
* hDraw: (number|undefined),
* }} * }}
*/ */
var SignAreaInfo; var SignAreaInfo;
/** /**
* fontData: default: StandardFonts.Helvetica
* color: A Hex string of color. default: #000
* opacity: valid value is from 0 to 1. default: 1 // Not implemented
* blendMode: https://pdf-lib.js.org/docs/api/enums/blendmode // Not implemented
* lineHeight: default is the height of the font at the given size
* xOffset: An offset from SignAreaInfo's x
* yOffset: An offset from SignAreaInfo's y
* align: Text alignment: 0 left, 1 center, 2 right. default: 0
* noBreaks: A regular expression string that indicates which characters should not be used to break a word. default: [A-Za-z0-9]
*
* @typedef
* {{
* text: string,
* fontData: (Array<number>|Uint8Array|ArrayBuffer|string|undefined),
* subset: (boolean|undefined),
* color: (string|undefined),
* opacity: (number|undefined),
* blendMode: (string|undefined),
* lineHeight: (number|undefined),
* size: number,
* xOffset: (number|undefined),
* yOffset: (number|undefined),
* wMax: (number|undefined),
* align: (number|undefined),
* noBreaks: (string|undefined),
* }}
*/
var SignTextInfo;
/**
* opacity: valid value is from 0 to 1. default: 1 // Not implemented
* blendMode: https://pdf-lib.js.org/docs/api/enums/blendmode // Not implemented
*
* @typedef
* {{
* imgData: (Array<number>|Uint8Array|ArrayBuffer|string|undefined),
* imgType: (string|undefined),
* opacity: (number|undefined),
* blendMode: (string|undefined),
* }}
*/
var SignImageInfo;
/**
* The signature can be placed in the same position on multiple pages, but all pages must have the same size and rotation angle.
* pageidx: Can be a string to indicate placing the signature on multiple pages.
* For example: A pdf contains 17 pages and specify "-3,5-7,9,12,15-" means [0,1,2,3,5,6,7,9,12,15,16]
* imgData, imgType: Deprecated, use imgInfo instead.
* img, font: Only for internal process.
*
* @typedef * @typedef
* {{ * {{
* area: SignAreaInfo, * area: SignAreaInfo,
* pageidx: (number|undefined), * pageidx: (number|string|undefined),
* imgData: (Array<number>|Uint8Array|ArrayBuffer|string|undefined), * imgData: (Array<number>|Uint8Array|ArrayBuffer|string|undefined),
* imgType: (string|undefined), * imgType: (string|undefined),
* text: (string|undefined), * imgInfo: (SignImageInfo|undefined),
* fontData: (PDFLib.StandardFonts|Array<number>|Uint8Array|ArrayBuffer|string|undefined), * textInfo: (SignTextInfo|undefined),
* img: (PDFLib.PDFImage|undefined), * img: (PDFLib.PDFImage|undefined),
* font: (PDFLib.PDFFont|undefined), * font: (PDFLib.PDFFont|undefined),
* }} * }}

140
lib/zgafetch.js Normal file
View File

@ -0,0 +1,140 @@
/**
* @param {Object<string, *>} z
*/
function supplyZgaUrlFetch(z){
//Only for nodejs Start//
const m_urlparser = require("url");
const m_h = {
"http:": require('follow-redirects').http,
"https:": require('follow-redirects').https,
};
// @type {boolean}
z.isNode = function(){return this === globalThis.global;}();
//Only for nodejs End//
/** @type {boolean} */
z.isBrowser = function(){return this === globalThis.self;}();
/**
* @param {string} url
* @param {UrlFetchParams} params
* @return {Promise<Uint8Array>}
*/
z.urlFetch = function(url, params){
//Only for nodejs Start//
if(z.isNode){
return new Promise(function(resolve, reject){
// @type {URL}
var opts = m_urlparser.parse(url);
var http = m_h[opts.protocol];
// @type {string|Buffer}
var dat = null;
var encoding = undefined;
opts.method = "GET";
if(params){
if(params.payload instanceof Buffer){
dat = params.payload;
}else if(params.payload instanceof Uint8Array){
dat = Buffer.from(params.payload.buffer);
}else if(params.payload instanceof ArrayBuffer){
dat = Buffer.from(params.payload);
}else{
dat = params.payload;
encoding = "binary";
}
if(params.headers){
opts.headers = params.headers;
}
if(params.method){
opts.method = params.method;
}
if(params.validateHttpsCertificates === false){
opts.rejectUnauthorized = false;
}
}
// @type {http.ClientRequest}
var hreq = http.request(opts, function(a_res){ // @type {http.IncomingMessage} a_res
if(a_res.statusCode !== 200){
var a_err = new Error("Failed to request url. " + url + "\n Status Code: " + a_res.statusCode);
a_res.resume();
throw a_err;
}
// @type {Array<Buffer>}
var a_bufs = [];
var a_bufs_len = 0;
a_res.on("data", function(b_chunk){ // @type {Buffer} b_chunk
a_bufs.push(b_chunk);
a_bufs_len += b_chunk.length;
});
a_res.on("end", function(){
// @type {Buffer}
var b_bdat = Buffer.concat(a_bufs, a_bufs_len);
resolve(b_bdat);
});
});
hreq.on("error", function(a_err){
throw a_err;
});
hreq.end(dat, encoding);
});
}
//Only for nodejs End//
// Google Apps Script
if(globalThis.UrlFetchApp){
return new Promise(function(resolve){
/** @type {GBlob} */
var tblob = UrlFetchApp.fetch(url, params).getBlob();
resolve(new Uint8Array(tblob.getBytes()));
});
}
// browser
if(z.isBrowser && globalThis.self.fetch){
/**
* @return {Promise<Uint8Array>}
*/
var func = async function(){
/** @type {!RequestInit} */
var reqinf = {
method: "GET",
redirect: "follow",
};
if(params){
if(params.payload){
reqinf.body = params.payload;
}
if(params.headers){
reqinf.headers = params.headers;
}
if(params.method){
reqinf.method = params.method;
}
}
/** @type {Response} */
var resp = await fetch(url, reqinf);
if(resp.ok){
/** @type {ArrayBuffer} */
var abdat = await resp.arrayBuffer();
return new Uint8Array(abdat);
}else{
/** @type {string} */
var msg = await resp.text();
throw new Error("Fetch failed." + resp.status + ": " + msg);
}
};
return func();
}
return null;
};
}
//Only for nodejs Start//
if(typeof exports === "object" && typeof module !== "undefined"){
module.exports = supplyZgaUrlFetch;
}
//Only for nodejs End//

View File

@ -5,14 +5,16 @@
*/ */
function genZga(){ function genZga(){
/** @const {Object<string, *>} */ /** @const {Object<string, *>} */
const z = {}; const z = {
ver: "",
};
/** /**
* @param {string} msg * @param {...string} msg
*/ */
z.log = function(msg){ z.log = function(...msg){
if(z.debug){ if(z.debug){
console.log(msg); console.log(...msg);
} }
}; };
@ -42,22 +44,6 @@ function genZga(){
return arr; return arr;
}; };
// Google Apps Script
if(globalThis.UrlFetchApp){
/**
* @param {string} url
* @param {UrlFetchParams} params
* @return {Promise<Uint8Array>}
*/
z.urlFetch = function(url, params){
return new Promise(function(resolve){
/** @type {GBlob} */
var tblob = UrlFetchApp.fetch(url, params).getBlob();
resolve(new Uint8Array(tblob.getBytes()));
});
};
}
return z; return z;
} }
@ -68,6 +54,7 @@ if(typeof exports === "object" && typeof module !== "undefined"){
//Only for nodejs End// //Only for nodejs End//
if(!globalThis.Zga){ if(!globalThis.Zga){
globalThis.Zga = genZga(); globalThis.Zga = genZga();
supplyZgaUrlFetch(globalThis.Zga);
supplyZgaCertsChain(globalThis.Zga); supplyZgaCertsChain(globalThis.Zga);
supplyZgaCryptor(globalThis.Zga); supplyZgaCryptor(globalThis.Zga);
supplyZgaSigner(globalThis.Zga); supplyZgaSigner(globalThis.Zga);

117
lib/zganode.d.ts vendored Normal file
View File

@ -0,0 +1,117 @@
import * as forge from "node-forge";
import * as PDFLib from "pdf-lib";
export * as forge from "node-forge";
export * as PDFLib from "pdf-lib";
export declare function u8arrToRaw(uarr: Uint8Array): string;
export declare function rawToU8arr(raw: string): Uint8Array;
export declare namespace Crypto {
enum Mode {
RC4_40,
RC4_128,
AES_128,
AES_256,
}
}
export type DSSInfo = {
certs?: Array<forge.pki.Certificate>;
ocsps?: Array<Uint8Array>;
crls?: Array<Uint8Array>;
};
export type EncryptOption = {
mode: Crypto.Mode;
permissions?: Array<string>;
userpwd?: string;
ownerpwd?: string;
pubkeys?: Array<PubKeyInfo>;
};
export type PubKeyInfo = {
c?: Array<number> | Uint8Array | ArrayBuffer | string | forge.pki.Certificate;
p?: Array<string>;
};
export type SignAreaInfo = {
x: number;
y: number;
w?: number;
h?: number;
};
export type SignTextInfo = {
text: string,
fontData?: Array<number> | Uint8Array | ArrayBuffer | PDFLib.StandardFonts;
subset?: boolean;
color?: string;
opacity?: number;
blendMode?: string;
lineHeight?: number;
size: number,
xOffset?: number;
yOffset?: number;
wMax?: number;
align?: number;
noBreaks?: string;
};
export type SignImageInfo = {
imgData: Array<number> | Uint8Array | ArrayBuffer | string;
imgType: string;
opacity?: number;
blendMode?: string;
};
export type SignDrawInfo = {
area: SignAreaInfo;
pageidx?: number | string;
/** @deprecated use imgInfo instead */
imgData?: Array<number> | Uint8Array | ArrayBuffer | string;
/** @deprecated use imgInfo instead */
imgType?: string;
imgInfo?: SignImageInfo;
textInfo?: SignTextInfo;
};
export type SignOption = {
p12cert?: Array<number> | Uint8Array | ArrayBuffer | string;
pwd?: string;
permission?: number;
reason?: string;
location?: string;
contact?: string;
signdate?: Date | TsaServiceInfo | string;
signame?: string;
drawinf?: SignDrawInfo;
ltv?: number;
debug?: boolean;
};
export type TsaServiceInfo = {
url: string;
len?: number;
headers?: Record<string, any>;
};
export declare class CertsChain {
constructor(certs?: Array<forge.pki.Certificate | forge.asn1.Asn1 | string>);
buildChain(cert: forge.pki.Certificate): Promise<boolean>;
getAllCerts(): Array<forge.pki.Certificate>;
getSignCert(): forge.pki.Certificate;
isSelfSignedCert(): boolean;
prepareDSSInf(crlOnly?: boolean): Promise<DSSInfo>;
}
export declare class PdfCryptor {
constructor(encopt: EncryptOption);
encryptPdf(pdf: PDFLib.PDFDocument | Array<number> | Uint8Array | ArrayBuffer | string, ref?: PDFLib.PDFRef): Promise<PDFLib.PDFDocument>;
encryptObject(num: number, val: PDFLib.PDFObject): void;
}
export declare class PdfSigner {
constructor(signopt: SignOption);
sign(pdf: PDFLib.PDFDocument | Array<number> | Uint8Array | ArrayBuffer | string, cypopt?: EncryptOption): Promise<Uint8Array>;
}
export declare class TsaFetcher {
constructor(inf: TsaServiceInfo);
url: string;
len: number;
getCertsChain(): CertsChain;
getToken(forP7?: boolean): forge.asn1.Asn1;
queryTsa(data?: string): Promise<string>;
}
export declare class PdfFonts {
private constructor();
static from(pdfdoc: PDFLib.PDFDocument): Promise<PdfFonts>;
getEmbeddedFont(fontData?: Array<number> | Uint8Array | ArrayBuffer | PDFLib.StandardFonts, subset?: boolean): Promise<PDFLib.PDFFont>;
}

View File

@ -6,69 +6,11 @@ const m_h = {
const z = require("./zgaindex.js"); const z = require("./zgaindex.js");
z.forge = require("node-forge"); z.forge = require("node-forge");
z.PDFLib = require("pdf-lib"); z.PDFLib = require("pdf-lib");
/** // z.fontkit = require("@pdf-lib/fontkit");
* @param {string} url z.fontkit = require("pdf-fontkit");
* @param {UrlFetchParams} params z.pako = require("pako");
* @return {Promise<Uint8Array>}
*/
z.urlFetch = function(url, params){
return new Promise(function(resolve, reject){
/** @type {URL} */
var opts = m_urlparser.parse(url);
var http = m_h[opts.protocol];
/** @type {string|Buffer} */
var dat = null;
var encoding = undefined;
opts.method = "GET";
if(params){
if(params.payload instanceof Buffer){
dat = params.payload;
}else if(params.payload instanceof Uint8Array){
dat = Buffer.from(params.payload.buffer);
}else if(params.payload instanceof ArrayBuffer){
dat = Buffer.from(params.payload);
}else{
dat = params.payload;
encoding = "binary";
}
if(params.headers){
opts.headers = params.headers;
}
if(params.method){
opts.method = params.method;
}
if(params.validateHttpsCertificates === false){
opts.rejectUnauthorized = false;
}
}
/** @type {http.ClientRequest} */
var hreq = http.request(opts, function(/** @type {http.IncomingMessage} */a_res){
if(a_res.statusCode !== 200){
var a_err = new Error("Failed to request url. " + url + "\n Status Code: " + a_res.statusCode);
a_res.resume();
throw a_err;
}
/** @type {Array<Buffer>} */
var a_bufs = [];
var a_bufs_len = 0;
a_res.on("data", function(/** @type {Buffer} */b_chunk){
a_bufs.push(b_chunk);
a_bufs_len += b_chunk.length;
});
a_res.on("end", function(){
/** @type {Buffer} */
var b_bdat = Buffer.concat(a_bufs, a_bufs_len);
resolve(b_bdat);
});
});
hreq.on("error", function(a_err){
throw a_err;
});
hreq.end(dat, encoding);
});
};
require("./zgafetch.js")(z);
require("./zgacertsutil.js")(z); require("./zgacertsutil.js")(z);
require("./zgapdfcryptor.js")(z); require("./zgapdfcryptor.js")(z);
require("./zgapdfsigner.js")(z); require("./zgapdfsigner.js")(z);

View File

@ -12,6 +12,12 @@ if(z.forge){
if(z.PDFLib){ if(z.PDFLib){
var PDFLib = z.PDFLib; var PDFLib = z.PDFLib;
} }
if(z.fontkit){
var fontkit = z.fontkit;
}
if(z.pako){
var pako = z.pako;
}
//Only for nodejs End// //Only for nodejs End//
/** @type {Object<string, TsaServiceInfo>} */ /** @type {Object<string, TsaServiceInfo>} */
@ -240,6 +246,8 @@ z.PdfSigner = class{
this.oriU8pdf = null; this.oriU8pdf = null;
/** @private @type {Array<PdfObjEntry>} */ /** @private @type {Array<PdfObjEntry>} */
this.apobjs = null; this.apobjs = null;
/** @private @type {z.PdfFonts} */
this.fonts = null;
if(typeof this.opt.debug == "boolean"){ if(typeof this.opt.debug == "boolean"){
z.debug = this.opt.debug; z.debug = this.opt.debug;
@ -252,6 +260,9 @@ z.PdfSigner = class{
if(!(globalThis.forge || forge)){ if(!(globalThis.forge || forge)){
throw new Error("node-forge is not imported."); throw new Error("node-forge is not imported.");
} }
if(z.ver){
z.log("ZgaPdfSigner Version:", z.ver);
}
/** @type {?TsaServiceInfo} */ /** @type {?TsaServiceInfo} */
var tsainf = null; var tsainf = null;
if(signopt.signdate){ if(signopt.signdate){
@ -265,11 +276,13 @@ z.PdfSigner = class{
} }
if(tsainf){ if(tsainf){
if(!z.urlFetch){ if(!z.urlFetch){
throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser."); // throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser.");
throw new Error("No fetch method found in this environment.");
} }
if(z.TSAURLS[tsainf.url]){ if(z.TSAURLS[tsainf.url]){
Object.assign(tsainf, z.TSAURLS[tsainf.url]); Object.assign(tsainf, z.TSAURLS[tsainf.url]);
}else if(!(new RegExp("^https?://")).test(tsainf.url)){ }else if(!tsainf.url || (!z.isBrowser && !(new RegExp("^https?://")).test(tsainf.url))){
// It may be a relative path in browser environment, so only check in non-browser environment
throw new Error("Unknown tsa data. " + JSON.stringify(tsainf)); throw new Error("Unknown tsa data. " + JSON.stringify(tsainf));
} }
if(!tsainf.len){ if(!tsainf.len){
@ -312,23 +325,36 @@ z.PdfSigner = class{
pdfdoc = await PDFLib.PDFDocument.load(_this.oriU8pdf); pdfdoc = await PDFLib.PDFDocument.load(_this.oriU8pdf);
} }
if(_this.opt.drawinf && _this.opt.drawinf.imgData && !_this.opt.drawinf.img){ // For backward compatibility
if(_this.opt.drawinf && _this.opt.drawinf.imgData && !_this.opt.drawinf.imgInfo){
_this.opt.drawinf.imgInfo = {
imgData: _this.opt.drawinf.imgData,
imgType: _this.opt.drawinf.imgType,
};
}
if(_this.opt.drawinf && _this.opt.drawinf.imgInfo && !_this.opt.drawinf.img){
/** @type {Uint8Array|ArrayBuffer|string} */ /** @type {Uint8Array|ArrayBuffer|string} */
var imgData2 = null; var imgData2 = null;
if(Array.isArray(_this.opt.drawinf.imgData)){ if(Array.isArray(_this.opt.drawinf.imgInfo.imgData)){
imgData2 = new Uint8Array(_this.opt.drawinf.imgData); imgData2 = new Uint8Array(_this.opt.drawinf.imgInfo.imgData);
}else{ }else{
imgData2 = _this.opt.drawinf.imgData; imgData2 = _this.opt.drawinf.imgInfo.imgData;
} }
if(_this.opt.drawinf.imgType == "png"){ if(_this.opt.drawinf.imgInfo.imgType == "png"){
_this.opt.drawinf.img = await pdfdoc.embedPng(imgData2); _this.opt.drawinf.img = await pdfdoc.embedPng(imgData2);
}else if(_this.opt.drawinf.imgType == "jpg"){ }else if(_this.opt.drawinf.imgInfo.imgType == "jpg"){
_this.opt.drawinf.img = await pdfdoc.embedJpg(imgData2); _this.opt.drawinf.img = await pdfdoc.embedJpg(imgData2);
}else{ }else{
throw new Error("Unkown image type. " + _this.opt.drawinf.imgType); throw new Error("Unkown image type. " + _this.opt.drawinf.imgInfo.imgType);
} }
} }
if(_this.opt.drawinf && _this.opt.drawinf.textInfo && !_this.opt.drawinf.font){
_this.fonts = await z.PdfFonts.from(pdfdoc);
_this.opt.drawinf.font = await _this.fonts.getEmbeddedFont(_this.opt.drawinf.textInfo.fontData, _this.opt.drawinf.textInfo.subset);
}
/** @type {forge_cert} */ /** @type {forge_cert} */
var cert = _this.loadP12cert(_this.opt.p12cert, _this.opt.pwd); var cert = _this.loadP12cert(_this.opt.p12cert, _this.opt.pwd);
/** @type {Zga.CertsChain} */ /** @type {Zga.CertsChain} */
@ -362,6 +388,8 @@ z.PdfSigner = class{
var dmydoc = await _this.addDss(pdfdoc); var dmydoc = await _this.addDss(pdfdoc);
if(dmydoc){ if(dmydoc){
z.log("In order to enable LTV, DSS informations has been added to the pdf."); z.log("In order to enable LTV, DSS informations has been added to the pdf.");
}else{
await pdfdoc.flush();
} }
// Clear ltv // Clear ltv
_this.opt.ltv = 0; _this.opt.ltv = 0;
@ -698,14 +726,18 @@ z.PdfSigner = class{
/** @const {PDFLib.PDFContext} */ /** @const {PDFLib.PDFContext} */
const pdfcont = pdfdoc.context; const pdfcont = pdfdoc.context;
/** @const {z.SignatureCreator} */ /** @const {z.SignatureCreator} */
const signcrt = new z.SignatureCreator(_this.opt.drawinf); const signcrt = new z.SignatureCreator(_this.opt.drawinf, pdfdoc.getPageCount());
/** @type {Array<number>} */
var pgidxs = signcrt.getPageIndexes();
/** @const {PDFLib.PDFPage} */ /** @const {PDFLib.PDFPage} */
const page = pdfdoc.getPages()[signcrt.getPageIndex()]; const page = pdfdoc.getPages()[pgidxs[0]];
/** @type {PDFLib.PDFRef} */ /** @type {PDFLib.PDFRef} */
var strmRef = signcrt.createStream(pdfdoc, _this.opt.signame); var strmRef = signcrt.createStream(pdfdoc, _this.opt.signame);
if(docMdp && !strmRef){ if(docMdp && !strmRef){
strmRef = signcrt.createEmptyField(pdfcont); strmRef = signcrt.createEmptyField(pdfcont);
// For invisible signature, only place on one page.
pgidxs = [pgidxs[0]];
} }
/** @type {Array<string>} */ /** @type {Array<string>} */
@ -813,18 +845,22 @@ z.PdfSigner = class{
/** @type {PDFLib.PDFRef} */ /** @type {PDFLib.PDFRef} */
var widgetDictRef = pdfcont.register(pdfcont.obj(widgetObj)); var widgetDictRef = pdfcont.register(pdfcont.obj(widgetObj));
// Add our signature widget to the page // Add our signature widget to the pages
pgidxs.forEach(function(pi){
/** @const {PDFLib.PDFPage} */
var p = pdfdoc.getPages()[pi];
/** @type {PDFLib.PDFArray} */ /** @type {PDFLib.PDFArray} */
var ans = page.node.Annots(); var ans = p.node.Annots();
if(!ans){ if(!ans){
ans = new PDFLib.PDFArray(pdfcont); ans = new PDFLib.PDFArray(pdfcont);
// if(docMdp){ // if(docMdp){
page.node.set(PDFLib.PDFName.Annots, ans); p.node.set(PDFLib.PDFName.Annots, ans);
// }else{ // }else{
// page.node.set(PDFLib.PDFName.Annots, pdfcont.register(ans)); // p.node.set(PDFLib.PDFName.Annots, pdfcont.register(ans));
// } // }
} }
ans.push(widgetDictRef); ans.push(widgetDictRef);
});
if(!afrm.dict.lookup(PDFLib.PDFName.of("SigFlags"))){ if(!afrm.dict.lookup(PDFLib.PDFName.of("SigFlags"))){
afrm.dict.set(PDFLib.PDFName.of("SigFlags"), PDFLib.PDFNumber.of(3)); afrm.dict.set(PDFLib.PDFName.of("SigFlags"), PDFLib.PDFNumber.of(3));
@ -1212,10 +1248,11 @@ z.PdfSigner = class{
z.SignatureCreator = class{ z.SignatureCreator = class{
/** /**
* @param {SignDrawInfo=} drawinf * @param {SignDrawInfo=} drawinf
* @param {number=} pgcnt
*/ */
constructor(drawinf){ constructor(drawinf, pgcnt){
/** @private @type {number} */ /** @private @type {Array<number>} */
this.pgidx = 0; this.pgidxs = [];
/** @private @type {Array<number>} */ /** @private @type {Array<number>} */
this.rect = [0, 0, 0, 0]; this.rect = [0, 0, 0, 0];
/** @private @type {?SignDrawInfo} */ /** @private @type {?SignDrawInfo} */
@ -1223,18 +1260,40 @@ z.SignatureCreator = class{
if(drawinf){ if(drawinf){
this.drawinf = drawinf; this.drawinf = drawinf;
if(this.drawinf.pageidx){ if(typeof this.drawinf.pageidx == "string"){
this.pgidx = this.drawinf.pageidx; /** @type {Array<string>} */
var sarr = this.drawinf.pageidx.split(",");
/** @type {number} */
var i = 0;
for(i=0; i<sarr.length; i++){
if(sarr[i]){
/** @type {Array<string>} */
var sarr2 = sarr[i].split("-");
/** @type {number} */
var j = sarr2[0] ? parseInt(sarr2[0], 10) : 0;
/** @type {number} */
var ed = sarr2[sarr2.length - 1] ? parseInt(sarr2[sarr2.length - 1], 10) : (pgcnt ? pgcnt - 1 : j);
while(j <= ed){
this.pgidxs.push(j);
j++;
} }
} }
} }
}else if(this.drawinf.pageidx){
this.pgidxs = [/** @type {number} */(this.drawinf.pageidx)];
}
}
if(this.pgidxs.length == 0){
this.pgidxs = [0];
}
}
/** /**
* @public * @public
* @return {number} * @return {Array<number>}
*/ */
getPageIndex(){ getPageIndexes(){
return this.pgidx; return this.pgidxs;
} }
/** /**
@ -1268,7 +1327,7 @@ z.SignatureCreator = class{
createStream(pdfdoc, signame){ createStream(pdfdoc, signame){
if(!this.drawinf){ if(!this.drawinf){
return null; return null;
}else if(!(this.drawinf.img || (this.drawinf.font && this.drawinf.font))){ }else if(!(this.drawinf.img || this.drawinf.textInfo)){
return null; return null;
} }
@ -1276,8 +1335,8 @@ z.SignatureCreator = class{
var pages = pdfdoc.getPages(); var pages = pdfdoc.getPages();
/** @type {PDFLib.PDFPage} */ /** @type {PDFLib.PDFPage} */
var page = null; var page = null;
if(this.pgidx < pages.length){ if(this.pgidxs[0] < pages.length){
page = pages[this.pgidx]; page = pages[this.pgidxs[0]];
}else{ }else{
throw new Error("Page index is overflow to pdf pages."); throw new Error("Page index is overflow to pdf pages.");
} }
@ -1288,51 +1347,46 @@ z.SignatureCreator = class{
/** @type {PdfSize} */ /** @type {PdfSize} */
var pgsz = page.getSize(); var pgsz = page.getSize();
/** @type {SignAreaInfo} */ /** @type {SignAreaInfo} */
var areainf = this.calcAreaInf(pgsz, pgrot.angle, this.drawinf.area); var areainf = this.drawinf.area;
// resources object // resources object
/** @type {Object<string, *>} */ /** @type {Object<string, *>} */
var rscObj = {}; var rscObj = {};
/** @type {Array<PDFLib.PDFOperator>} */
var sigOprs = [];
/** @type {string} */ /** @type {string} */
var imgName = signame ? signame.concat("Img") : "SigImg"; var imgName = signame ? signame.concat("Img") : "SigImg";
/** @type {string} */ /** @type {string} */
var fontName = signame ? signame.concat("Font") : "SigFont"; var fontName = signame ? signame.concat("Font") : "SigFont";
if(this.drawinf.img){
// Get scaled image size
/** @type {PdfSize} */
var imgsz = this.drawinf.img.size();
/** @type {number} */
var tmp = areainf.w * imgsz.height / imgsz.width;
if(tmp <= areainf.h){
areainf.h = tmp;
}else{
areainf.w = areainf.h * imgsz.width / imgsz.height;
}
rscObj["XObject"] = { /** @type {Array<PDFLib.PDFOperator>} */
[imgName]: this.drawinf.img.ref, var txtOprs = [];
}; if(this.drawinf.textInfo){
sigOprs = sigOprs.concat(PDFLib.drawImage(imgName, this.calcDrawImgInf(pgrot, areainf)));
}
if(this.drawinf.font){
rscObj["Font"] = { rscObj["Font"] = {
[fontName]: this.drawinf.font.ref, [fontName]: this.drawinf.font.ref,
}; };
txtOprs = this.createDrawTextOper(pdfdoc, pgrot, fontName, areainf);
} }
/** @type {Array<PDFLib.PDFOperator>} */
var imgOprs = [];
if(this.drawinf.img){
rscObj["XObject"] = {
[imgName]: this.drawinf.img.ref,
};
imgOprs = PDFLib.drawImage(imgName, this.calcDrawImgInf(pgrot, this.drawinf.img.size(), areainf, txtOprs.length == 0));
}
areainf = this.calcAreaInf(pgsz, pgrot.angle, areainf);
this.rect = this.calcRect(pgrot.angle, areainf); this.rect = this.calcRect(pgrot.angle, areainf);
var frmDict = /** @type {PDFLib.PDFDict} */(pdfdoc.context.obj({ var frmDict = /** @type {PDFLib.PDFDict} */(pdfdoc.context.obj({
"Type": "XObject", "Type": "XObject",
"Subtype": "Form", "Subtype": "Form",
"FormType": 1, "FormType": 1,
"BBox": [0, 0, areainf.w, areainf.h], "BBox": [0, 0, areainf.wDraw, areainf.hDraw],
"Resources": rscObj, "Resources": rscObj,
})); }));
/** @type {PDFLib.PDFContentStream} */ /** @type {PDFLib.PDFContentStream} */
var strm = PDFLib.PDFContentStream.of(frmDict, sigOprs, true); var strm = PDFLib.PDFContentStream.of(frmDict, imgOprs.concat(txtOprs), true);
return pdfdoc.context.register(strm); return pdfdoc.context.register(strm);
} }
@ -1350,25 +1404,25 @@ z.SignatureCreator = class{
// Calculate position after rotate // Calculate position after rotate
switch(angle){ switch(angle){
case 90: case 90:
ret.w = visinf.h; ret.wDraw = visinf.hDraw;
ret.h = visinf.w; ret.hDraw = visinf.wDraw;
ret.x = visinf.y + visinf.h; ret.x = visinf.y + visinf.hDraw;
ret.y = visinf.x; ret.y = visinf.x;
break; break;
case 180: case 180:
case -180: case -180:
ret.x = pgsz.width - visinf.x; ret.x = pgsz.width - visinf.x;
ret.y = visinf.y + visinf.h; ret.y = visinf.y + visinf.hDraw;
break; break;
case 270: case 270:
case -90: case -90:
ret.w = visinf.h; ret.wDraw = visinf.hDraw;
ret.h = visinf.w; ret.hDraw = visinf.wDraw;
ret.x = pgsz.width - visinf.y - visinf.h; ret.x = pgsz.width - visinf.y - visinf.hDraw;
ret.y = pgsz.height - visinf.x; ret.y = pgsz.height - visinf.x;
break; break;
default: default:
ret.y = pgsz.height - visinf.y - visinf.h; ret.y = pgsz.height - visinf.y - visinf.hDraw;
} }
return ret; return ret;
} }
@ -1376,7 +1430,7 @@ z.SignatureCreator = class{
/** /**
* @private * @private
* @param {number} angle * @param {number} angle
* @param {SignAreaInfo} areainf // { x, y, w, h } * @param {SignAreaInfo} areainf
* @return {Array<number>} * @return {Array<number>}
*/ */
calcRect(angle, areainf){ calcRect(angle, areainf){
@ -1386,22 +1440,22 @@ z.SignatureCreator = class{
rect[1] = areainf.y; rect[1] = areainf.y;
switch(angle){ switch(angle){
case 90: case 90:
rect[2] = areainf.x - areainf.h; rect[2] = areainf.x - areainf.wDraw;
rect[3] = areainf.y + areainf.w; rect[3] = areainf.y + areainf.hDraw;
break; break;
case 180: case 180:
case -180: case -180:
rect[2] = areainf.x - areainf.w; rect[2] = areainf.x - areainf.wDraw;
rect[3] = areainf.y - areainf.h; rect[3] = areainf.y - areainf.hDraw;
break; break;
case 270: case 270:
case -90: case -90:
rect[2] = areainf.x + areainf.h; rect[2] = areainf.x + areainf.wDraw;
rect[3] = areainf.y - areainf.w; rect[3] = areainf.y - areainf.hDraw;
break; break;
default: default:
rect[2] = areainf.x + areainf.w; rect[2] = areainf.x + areainf.wDraw;
rect[3] = areainf.y + areainf.h; rect[3] = areainf.y + areainf.hDraw;
} }
return rect; return rect;
} }
@ -1411,41 +1465,580 @@ z.SignatureCreator = class{
* *
* @private * @private
* @param {PDFLib.Rotation} rot * @param {PDFLib.Rotation} rot
* @param {SignAreaInfo} areainf // { x, y, w, h } * @param {PdfSize} imgsz
* @param {SignAreaInfo} areainf
* @param {boolean} canResize
* @return {PdfDrawimgOption} * @return {PdfDrawimgOption}
*/ */
calcDrawImgInf(rot, areainf){ calcDrawImgInf(rot, imgsz, areainf, canResize){
if(!areainf.wDraw){
if(areainf.w){
areainf.wDraw = areainf.w;
}else{
areainf.wDraw = imgsz.width;
}
}
if(!areainf.hDraw){
if(areainf.h){
areainf.hDraw = areainf.h;
}else{
areainf.hDraw = imgsz.height;
}
}
/** @type {number} */
var wImg = areainf.wDraw;
/** @type {number} */
var hImg = areainf.hDraw;
if(wImg != imgsz.width && hImg != imgsz.height){
/** @type {number} */
var tmp = wImg * imgsz.height / imgsz.width;
if(tmp <= hImg){
hImg = tmp;
}else{
wImg = hImg * imgsz.width / imgsz.height;
}
}
if(canResize){
areainf.wDraw = wImg;
areainf.hDraw = hImg;
}
/** @type {PdfDrawimgOption} */ /** @type {PdfDrawimgOption} */
var ret = { var ret = {
"x": 0, "x": 0,
"y": 0, "y": 0,
"width": areainf.w, "width": wImg,
"height": areainf.h, "height": hImg,
"rotate": rot, "rotate": rot,
"xSkew": PDFLib.degrees(0), "xSkew": PDFLib.degrees(0),
"ySkew": PDFLib.degrees(0), "ySkew": PDFLib.degrees(0),
// "graphicsState": "",
}; };
switch(rot.angle){ switch(rot.angle){
case 0:
ret["y"] = areainf.hDraw - hImg - ret["y"];
break;
case 90: case 90:
ret["x"] = areainf.w; ret["x"] += hImg;
ret["width"] = areainf.h;
ret["height"] = areainf.w;
break; break;
case 180: case 180:
case -180: case -180:
ret["x"] = areainf.w; ret["x"] = areainf.wDraw - ret["x"];
ret["y"] = areainf.h; ret["y"] += hImg;
break; break;
case 270: case 270:
case -90: case -90:
ret["y"] = areainf.h; ret["x"] = areainf.hDraw - hImg - ret["x"];
ret["width"] = areainf.h; ret["y"] = areainf.wDraw - ret["y"];
ret["height"] = areainf.w;
break; break;
} }
return ret; return ret;
} }
/**
* Create operations for drawing text after rotate
*
* @private
* @param {PDFLib.PDFDocument} pdfdoc
* @param {PDFLib.Rotation} rot
* @param {string} fontName
* @param {SignAreaInfo} areainf
* @return {Array<PDFLib.PDFOperator>}
*/
createDrawTextOper(pdfdoc, rot, fontName, areainf){
/** @const {z.SignatureCreator} */
const _this = this;
var txtInf = /** @type {SignTextInfo} */(_this.drawinf.textInfo);
var font = /** @type {!PDFLib.PDFFont} */(_this.drawinf.font);
/** @type {DrawLinesOfTextOptions} */
var opts = {
"x": txtInf.xOffset || 0,
"y": txtInf.yOffset || 0,
"color": _this.hexToColor(txtInf.color),
"font": fontName,
"lineHeight": txtInf.lineHeight || font.heightAtSize(txtInf.size, {descender: true}),
"size": txtInf.size,
"rotate": rot,
"xSkew": PDFLib.degrees(0),
"ySkew": PDFLib.degrees(0),
// "graphicsState": "",
}; };
/**
* @param {string} t
* @return {number}
*/
var calcTextWidth = function(t){
return font.widthOfTextAtSize(t, txtInf.size);
};
/** @type {Array<string>} */
var txts = [];
/** @type {boolean} */
var needW = false;
/** @type {number} */
var w = txtInf.wMax || areainf.w || 0;
if(w){
txts = _this.breakTextIntoLines(txtInf.text, w, calcTextWidth, txtInf.noBreaks);
}else{
txts = PDFLib.lineSplit(PDFLib.cleanText(txtInf.text));
needW = true;
}
/** @type {Array<number>} */
var wids = [];
/** @type {Array<PDFLib.PDFHexString>} */
var enctxts = txts.map(function(t2){
/** @type {number} */
var cw = 0;
t2 = t2.trim();
if(needW || txtInf.align){
cw = calcTextWidth(t2);
wids.push(cw);
}
if(needW){
w = Math.max(w, cw);
}
return font.encodeText(t2);
});
if(areainf.w){
areainf.wDraw = areainf.w;
}else{
areainf.wDraw = w + opts["x"];
}
if(areainf.h){
areainf.hDraw = areainf.h;
}else{
areainf.hDraw = txts.length * opts["lineHeight"] + opts["y"];
}
/** @type {Array<PDFLib.PDFOperator>} */
var ret = [];
/** @type {Array<number>} */
var pos = null;
if(txtInf.align){
wids.forEach(function(w1, i1){
/** @type {number} */
var x = opts["x"];
if(txtInf.align == 1){
// center alignment
x = (w - w1) / 2 + opts["x"];
}else{
// right alignment
x = (w - w1) + opts["x"];
}
ret = ret.concat(PDFLib.drawLinesOfText([enctxts[i1]], _this.calcTextPos(opts, areainf.wDraw, areainf.hDraw, i1, x)));
});
}else{
ret = PDFLib.drawLinesOfText(enctxts, _this.calcTextPos(opts, areainf.wDraw, areainf.hDraw));
}
return ret;
}
/**
* Convert hex string to Color
*
* @private
* @param {string=} hex
* @return {PDFLib.Color}
*/
hexToColor(hex){
/** @type {Array<number>} */
var rgb = [0,0,0];
if(hex){
if(hex.charAt(0) == "#"){
hex = hex.substring(1);
}
if(hex.length == 3){
rgb[0] = parseInt(hex.charAt(0)+hex.charAt(0), 16);
rgb[1] = parseInt(hex.charAt(1)+hex.charAt(1), 16);
rgb[2] = parseInt(hex.charAt(2)+hex.charAt(2), 16);
}else if(hex.length == 6){
rgb[0] = parseInt(hex.substring(0, 2), 16);
rgb[1] = parseInt(hex.substring(2, 4), 16);
rgb[2] = parseInt(hex.substring(4, 6), 16);
}else{
throw new Error("The hex string is not a valid color.");
}
}
return PDFLib.rgb(rgb[0]/255, rgb[1]/255, rgb[2]/255);
}
/**
* @private
* @param {string} text
* @param {number} maxWidth
* @param {function(string):number} computeWidthOfText
* @param {string=} noBreakRx
* @return {Array<string>}
*/
breakTextIntoLines(text, maxWidth, computeWidthOfText, noBreakRx){
/** @type {string} */
var ctxt = PDFLib.cleanText(text);
/** @type {string} */
var currLine = "";
/** @type {number} */
var currWidth = 0;
/** @type {Array<string>} */
var lines = [];
var nwRegexp = new RegExp(noBreakRx || "[A-Za-z0-9]");
/** @type {Array<string>} */
var words = [];
/** @type {number} */
var idx = 0;
/** @type {number} */
var len = ctxt.length;
while(idx < len){
/** @type {string} */
var c = ctxt.charAt(idx);
if(nwRegexp.test(c)){
currLine += c;
}else{
if(currLine)words.push(currLine);
currLine = "";
words.push(c);
}
if(c == "\r" && idx + 1 < len && ctxt.charAt(idx + 1) == "\n"){
idx++;
}
idx++;
}
if(currLine)words.push(currLine);
currLine = "";
idx = 0;
len = words.length;
while(idx < len){
/** @type {string} */
var word = words[idx];
if(PDFLib.isNewlineChar(word)){
lines.push(currLine);
currLine = "";
currWidth = 0;
}else{
/** @type {number} */
var width = computeWidthOfText(word);
if(width > maxWidth){
if(idx > 0){
lines.push(currLine);
currLine = "";
currWidth = 0;
}
/** @type {SplitLongWordResult} */
var slwr = this.splitLongWord(word, width, maxWidth, computeWidthOfText);
lines = lines.concat(slwr.words);
word = slwr.lastWord;
width = slwr.lastWidth;
}else if(currWidth + width > maxWidth){
lines.push(currLine);
currLine = "";
currWidth = 0;
}
currLine += word;
currWidth += width;
}
idx++;
}
if(currLine)lines.push(currLine);
return lines;
}
/**
* @private
* @param {string} word
* @param {number} wordWidth
* @param {number} maxWidth
* @param {function(string):number} computeWidthOfText
* @return {SplitLongWordResult}
*/
splitLongWord(word, wordWidth, maxWidth, computeWidthOfText){
/** @type {Array<string>} */
var splited = [];
/** @type {number} */
var wordLen = word.length;
while(wordWidth > maxWidth){
/** @type {number} */
var maxIdx = Math.floor(wordLen * maxWidth / wordWidth) - 1;
/** @type {number} */
var w = computeWidthOfText(word.substring(0, maxIdx + 1));
if(w > maxWidth){
while(w > maxWidth){
maxIdx--;
w -= computeWidthOfText(word.charAt(maxIdx));
}
maxIdx++;
}else{
while(w < maxWidth){
maxIdx++;
if(maxIdx < wordLen){
/** @type {number} */
var w2 = w + computeWidthOfText(word.charAt(maxIdx));
if(w2 > maxWidth){
break;
}else{
w = w2;
}
}else{
break;
}
}
}
splited.push(word.substring(0, maxIdx));
word = word.substring(maxIdx);
wordLen -= maxIdx;
wordWidth -= w;
}
return {
words: splited,
lastWord: word,
lastWidth: wordWidth,
};
}
/**
* @private
* @param {DrawLinesOfTextOptions} opts
* @param {number=} w // It must not be undefined, but need to suppress warning of mismatch
* @param {number=} h // It must not be undefined, but need to suppress warning of mismatch
* @param {number=} idx // line index
* @param {number=} aX // x of alignment
* @return {DrawLinesOfTextOptions} // A copy of opts, and x, y are calculated.
*/
calcTextPos(opts, w, h, idx, aX){
var newopts = /** @type {DrawLinesOfTextOptions} */(Object.assign({}, opts));
/** @type {number} */
var i = idx || 0;
/** @type {number} */
var x = aX || opts["x"];
switch(opts["rotate"].angle){
case 0:
newopts["x"] = x;
newopts["y"] = h - opts["lineHeight"] - opts["y"] - (opts["lineHeight"] * i);
break;
case 90:
newopts["x"] = opts["lineHeight"] + opts["y"] + (opts["lineHeight"] * i);
newopts["y"] = x;
break;
case 180:
case -180:
newopts["x"] = w - x;
newopts["y"] = opts["lineHeight"] + opts["y"] + (opts["lineHeight"] * i);
break;
case 270:
case -90:
newopts["x"] = h - opts["lineHeight"] - opts["y"] - (opts["lineHeight"] * i);
newopts["y"] = w - x;
break;
}
return newopts;
}
};
z.PdfFonts = class{
/**
* @private
* @param {PDFLib.PDFDocument} pdfdoc
* @param {Array<FontInfo>} fonts
*/
constructor(pdfdoc, fonts){
/** @private @type {PDFLib.PDFDocument} */
this.doc = pdfdoc;
/** @private @type {Array<FontInfo>} */
this.fonts = fonts;
}
/**
* @public
* @param {PDFLib.PDFDocument} pdfdoc
* @return {Promise<z.PdfFonts>}
*/
static async from(pdfdoc){
/**
* @param {PDFLib.PDFDict} dict
* @param {string} nm
* @return {string|undefined}
*/
var lookupName = function(dict, nm){
var pnm = /** @type {PDFLib.PDFName} */(dict.lookupMaybe(PDFLib.PDFName.of(nm), PDFLib.PDFName));
if(pnm){
return pnm.asString();
}else{
return undefined;
}
};
/** @type Array<FontInfo> */
var fonts = [];
/** @type {Array<PdfObjEntry>} */
var objs = pdfdoc.context.enumerateIndirectObjects();
/** @type {number} */
var i = 0;
while(i < objs.length){
/** @type {PdfObjEntry} */
var poe = objs[i];
i++;
if(poe[1] instanceof PDFLib.PDFDict){
/** @type {string|undefined} */
var typ = lookupName(poe[1], "Type");
if(typ !== "/Font"){
continue;
}
/** @type {string|undefined} */
var fntnm = lookupName(poe[1], "BaseFont");
if(fntnm){
fntnm = fntnm.substring(1);
if(PDFLib.isStandardFont(fntnm)){
fonts.push({
font: PDFLib.PDFFont.of(poe[0], pdfdoc, PDFLib.StandardFontEmbedder.for(fntnm)),
});
continue;
}
}else{
continue;
}
var dfnts = /** @type {PDFLib.PDFArray} */(poe[1].lookupMaybe(PDFLib.PDFName.of("DescendantFonts"), PDFLib.PDFArray));
if(dfnts && dfnts.size()){
var fntdict = /** @type {PDFLib.PDFDict} */(dfnts.lookupMaybe(0, PDFLib.PDFDict));
if(fntdict){
var fntdesc = /** @type {PDFLib.PDFDict} */(fntdict.lookupMaybe(PDFLib.PDFName.of("FontDescriptor"), PDFLib.PDFDict));
if(fntdesc){
var rstm = /** @type {PDFLib.PDFRawStream} */(fntdesc.lookupMaybe(PDFLib.PDFName.of("FontFile2"), PDFLib.PDFRawStream));
if(rstm){
/** @type {Uint8Array} */
var fdat = rstm.getContents();
/** @type {string|undefined} */
var fltr = lookupName(rstm.dict, "Filter");
if(fltr == "/FlateDecode"){
fdat = pako.inflate(fdat);
}
try{
/** @type {PDFLib.CustomFontEmbedder} */
var emdr = await PDFLib.CustomFontEmbedder.for(fontkit, fdat);
fonts.push({
font: PDFLib.PDFFont.of(poe[0], pdfdoc, emdr),
data: fdat,
});
}catch(ex){
z.log(fntnm, ex.message);
}
}
}
}
}
}
}
return new z.PdfFonts(pdfdoc, fonts);
}
/**
* @public
* @param {Array<number>|Uint8Array|ArrayBuffer|string|undefined} fontData
* @param {boolean=} subset
* @return {Promise<PDFLib.PDFFont>}
*/
async getEmbeddedFont(fontData, subset){
if(!fontData){
if(this.fonts.length){
z.log("Use existing default font.", this.fonts[0].font.name);
return this.fonts[0].font;
}else{
fontData = "Helvetica";
z.log("Use default font.", fontData);
}
}
if(typeof fontData == "string"){
return this.getStandardFont(fontData);
}else{
/** @type {Uint8Array} */
var u8dat = (fontData instanceof Uint8Array) ? fontData : new Uint8Array(fontData);
return await this.getCustomFont(u8dat, subset || false);
}
}
/**
* @private
* @param {string} fontData
* @return {PDFLib.PDFFont}
*/
getStandardFont(fontData){
/** @type {number} */
var i = 0;
while(i < this.fonts.length){
/** @type {FontInfo} */
var fi = this.fonts[i];
i++;
if(!fi.data && fi.font.name == fontData){
z.log("Existing font found.", fi.font.name);
return fi.font;
}
}
return this.doc.embedStandardFont(fontData);
}
/**
* @private
* @param {Uint8Array} fontData
* @param {boolean} subset
* @return {Promise<PDFLib.PDFFont>}
*/
async getCustomFont(fontData, subset){
/** @type {number} */
var i = 0;
while(i < this.fonts.length){
/** @type {FontInfo} */
var fi = this.fonts[i];
i++;
if(fi.data && this.isSameData(fi.data, fontData)){
z.log("Existing font found.", fi.font.name);
return fi.font;
}
}
this.doc.registerFontkit(fontkit);
return await this.doc.embedFont(fontData, {subset});
}
/**
* @private
* @param {Uint8Array} dat1
* @param {Uint8Array} dat2
* @return {boolean}
*/
isSameData(dat1, dat2){
if(dat1.length != dat2.length){
return false;
}
/** @type {number} */
var i = 0;
while(i < dat1.length){
if(dat1[i] != dat2[i]){
return false;
}
i++;
}
return true;
}
};
/**
* @typedef
* {{
* words: Array<string>,
* lastWord: string,
* lastWidth: number,
* }}
*/
var SplitLongWordResult;
/**
* @typedef
* {{
* font: PDFLib.PDFFont,
* data: (Uint8Array|undefined),
* }}
*/
var FontInfo;
} }

336
package-lock.json generated
View File

@ -1,19 +1,22 @@
{ {
"name": "zgapdfsigner", "name": "zgapdfsigner",
"version": "2.5.0", "version": "2.7.2",
"lockfileVersion": 2, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "zgapdfsigner", "name": "zgapdfsigner",
"version": "2.5.0", "version": "2.7.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "1.15.2", "follow-redirects": "1.15.6",
"node-forge": "1.3.1", "node-forge": "1.3.1",
"pdf-fontkit": "1.8.9",
"pdf-lib": "1.17.1" "pdf-lib": "1.17.1"
}, },
"devDependencies": {} "devDependencies": {
"google-closure-compiler": "^20231112.0.0"
}
}, },
"node_modules/@pdf-lib/standard-fonts": { "node_modules/@pdf-lib/standard-fonts": {
"version": "1.0.0", "version": "1.0.0",
@ -31,10 +34,100 @@
"pako": "^1.0.10" "pako": "^1.0.10"
} }
}, },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"dev": true,
"engines": {
"node": ">=0.8"
}
},
"node_modules/clone-buffer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz",
"integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==",
"dev": true,
"engines": {
"node": ">= 0.10"
}
},
"node_modules/clone-stats": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz",
"integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==",
"dev": true
},
"node_modules/cloneable-readable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz",
"integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==",
"dev": true,
"dependencies": {
"inherits": "^2.0.1",
"process-nextick-args": "^2.0.0",
"readable-stream": "^2.3.5"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true
},
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.2", "version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -50,6 +143,80 @@
} }
} }
}, },
"node_modules/google-closure-compiler": {
"version": "20231112.0.0",
"resolved": "https://registry.npmjs.org/google-closure-compiler/-/google-closure-compiler-20231112.0.0.tgz",
"integrity": "sha512-C/MPRThIxRAFomGhpEwXyVcWRIVnmqGraJ5BTJ+EQcfAiPNBvl+Q5nKU2J/lICPcx+YQ+3c+FJ/gBJsTXPjcwg==",
"dev": true,
"dependencies": {
"chalk": "4.x",
"google-closure-compiler-java": "^20231112.0.0",
"minimist": "1.x",
"vinyl": "2.x",
"vinyl-sourcemaps-apply": "^0.2.0"
},
"bin": {
"google-closure-compiler": "cli.js"
},
"engines": {
"node": ">=10"
},
"optionalDependencies": {
"google-closure-compiler-linux": "^20231112.0.0",
"google-closure-compiler-osx": "^20231112.0.0",
"google-closure-compiler-windows": "^20231112.0.0"
}
},
"node_modules/google-closure-compiler-java": {
"version": "20231112.0.0",
"resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20231112.0.0.tgz",
"integrity": "sha512-E45cJD6/xLJlL8pL6HEoxu8nEKp87CnrojUK0UuHiT7ZjCsrJfR4WhZwNNCq2+/6gYD9unGgMsunV4DDtBbvaA==",
"dev": true
},
"node_modules/google-closure-compiler-windows": {
"version": "20231112.0.0",
"resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20231112.0.0.tgz",
"integrity": "sha512-wbN5EOCGz53HVENVtOEO1brn/G3ZmCV1ULiJljNuASQc62vQ36QHA6XnAZOAGTEpAoMnYRv3dtXtBKd07wBdsA==",
"cpu": [
"x32",
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/node-forge": { "node_modules/node-forge": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@ -63,6 +230,14 @@
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
}, },
"node_modules/pdf-fontkit": {
"version": "1.8.9",
"resolved": "https://registry.npmjs.org/pdf-fontkit/-/pdf-fontkit-1.8.9.tgz",
"integrity": "sha512-TTq+umfhlFjUuQYOq6dCKT/wLslCrX4zVr5gqrIvrGHfo+vJ3ETapZTb4YLOCErohX7pF+HxlXSZuiToSRhNmA==",
"dependencies": {
"pako": "^1.0.6"
}
},
"node_modules/pdf-lib": { "node_modules/pdf-lib": {
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
@ -74,59 +249,114 @@
"tslib": "^1.11.1" "tslib": "^1.11.1"
} }
}, },
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
"integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==",
"dev": true
},
"node_modules/replace-ext": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz",
"integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==",
"dev": true,
"engines": {
"node": ">= 0.10"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}, },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"node_modules/vinyl": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz",
"integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==",
"dev": true,
"dependencies": { "dependencies": {
"@pdf-lib/standard-fonts": { "clone": "^2.1.1",
"version": "1.0.0", "clone-buffer": "^1.0.0",
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", "clone-stats": "^1.0.0",
"integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", "cloneable-readable": "^1.0.0",
"requires": { "remove-trailing-separator": "^1.0.1",
"pako": "^1.0.6" "replace-ext": "^1.0.0"
},
"engines": {
"node": ">= 0.10"
} }
}, },
"@pdf-lib/upng": { "node_modules/vinyl-sourcemaps-apply": {
"version": "1.0.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz",
"integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==",
"requires": { "dev": true,
"pako": "^1.0.10" "dependencies": {
} "source-map": "^0.5.1"
}, }
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
},
"node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
},
"pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"pdf-lib": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
"integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
"requires": {
"@pdf-lib/standard-fonts": "^1.0.0",
"@pdf-lib/upng": "^1.0.1",
"pako": "^1.0.11",
"tslib": "^1.11.1"
}
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
} }
} }
} }

View File

@ -1,20 +1,23 @@
{ {
"name": "zgapdfsigner", "name": "zgapdfsigner",
"version": "2.5.1", "version": "2.7.3",
"author": "zboris12", "author": "zboris12",
"description": "A javascript tool to sign a pdf or set protection to a pdf in web browser, Google Apps Script and nodejs.", "description": "A javascript tool to sign a pdf or set protection to a pdf in web browser, Google Apps Script and nodejs.",
"homepage": "https://github.com/zboris12/zgapdfsigner", "homepage": "https://github.com/zboris12/zgapdfsigner",
"private": false, "private": false,
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/zboris12/zgapdfsigner" "url": "git+https://github.com/zboris12/zgapdfsigner.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/zboris12/zgapdfsigner/issues" "url": "https://github.com/zboris12/zgapdfsigner/issues"
}, },
"license": "MIT", "license": "MIT",
"main": "lib/zganode.js", "main": "lib/zganode.js",
"unpkg": "dist/zgapdfsigner.min.js",
"files": [ "files": [
"dist/*.min.js",
"lib/*.d.ts",
"lib/*.js" "lib/*.js"
], ],
"keywords": [ "keywords": [
@ -28,13 +31,16 @@
"長期署名" "長期署名"
], ],
"scripts": { "scripts": {
"build": "node closure.js", "build": "./build.sh",
"test": "node test4node.js" "test": "node test4node.js ${pfxpwd}"
}, },
"dependencies": { "dependencies": {
"follow-redirects": "1.15.2", "follow-redirects": "1.15.6",
"pdf-lib": "1.17.1", "node-forge": "1.3.1",
"node-forge": "1.3.1" "pdf-fontkit": "1.8.9",
"pdf-lib": "1.17.1"
}, },
"devDependencies": {} "devDependencies": {
"google-closure-compiler": "^20231112.0.0"
}
} }

5
test-ts/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"editor.tabSize": 2,
"editor.formatOnSave": true,
"files.eol": "\n"
}

20
test-ts/package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "zgapdfsigner-test-ts",
"version": "1.0.0",
"author": "zboris12",
"description": "A typescript program to test zgapdfsigner.",
"private": false,
"license": "MIT",
"main": "index.js",
"scripts": {
"build": "tsc",
"test": "node test/index.js ${pfxpwd}"
},
"dependencies": {
"zgapdfsigner": "^2.7.1"
},
"devDependencies": {
"@types/node-forge": "^1.3.11",
"typescript": "~4.9"
}
}

149
test-ts/src/index.ts Normal file
View File

@ -0,0 +1,149 @@
import * as m_fs from "node:fs";
import * as m_path from "node:path";
import * as Zga from "zgapdfsigner";
const workpath = "./";
async function sign_protect(pdfPath: string, pfxPath: string, ps: string, perm: number, imgPath?: string, txt?: string, fontPath?: string): Promise<string> {
let pdf: Buffer = m_fs.readFileSync(pdfPath);
let pfx: Buffer = m_fs.readFileSync(pfxPath);
let img: Buffer | undefined = undefined;
let imgType: string = "";
let font: Buffer | Zga.PDFLib.StandardFonts | undefined = undefined;
if (perm == 1) {
console.log("\nTest signing pdf with full protection. (permission 1 and password encryption)");
} else {
console.log("\nTest signing pdf with permission " + perm);
}
if (imgPath) {
img = m_fs.readFileSync(imgPath);
imgType = m_path.extname(imgPath).slice(1);
}
if (fontPath) {
if (Zga.PDFLib.isStandardFont(fontPath)) {
font = fontPath as string as Zga.PDFLib.StandardFonts;
} else {
font = m_fs.readFileSync(fontPath);
}
}
let sopt: Zga.SignOption = {
p12cert: pfx,
pwd: ps,
permission: perm,
signdate: "1",
reason: "I have a test reason " + perm + ".",
location: "I am on the earth " + perm + ".",
contact: "zga" + perm + "@zga.com",
ltv: 1,
debug: true,
};
if (img || txt) {
sopt.drawinf = {
area: {
x: perm ? 25 : 200, // left
y: 50, // top
w: txt ? undefined : 60,
h: txt ? undefined : 100,
},
pageidx: "-",
imgInfo: img ? {
imgData: img,
imgType: imgType,
} : undefined,
textInfo: txt ? {
text: txt,
fontData: font,
color: "00f0f1",
lineHeight: 20,
size: 16,
align: 1,
wMax: 80,
yOffset: 10,
xOffset: 20,
noBreaks: "[あいうえおA-Za-z0-9]",
} : undefined,
};
}
let eopt: Zga.EncryptOption | undefined = undefined;
if (perm == 1) {
eopt = {
mode: Zga.Crypto.Mode.AES_256,
permissions: ["copy", "copy-extract", "print-high"],
userpwd: "123",
};
}
let ser: Zga.PdfSigner = new Zga.PdfSigner(sopt);
let u8dat: Uint8Array = await ser.sign(pdf, eopt);
let outPath: string = "";
if (u8dat) {
outPath = m_path.join(__dirname, workpath + "test_perm" + perm + m_path.basename(pdfPath));
m_fs.writeFileSync(outPath, u8dat);
console.log("Output file: " + outPath);
}
return outPath;
}
async function addtsa(pdfPath: string): Promise<string> {
console.log("\nTest signing pdf by a timestamp.");
let pdf: Buffer = m_fs.readFileSync(pdfPath);
let sopt: Zga.SignOption = {
signdate: "2",
reason: "I have a test reason tsa.",
location: "I am on the earth tsa.",
contact: "zgatsa@zga.com",
ltv: 1,
debug: true,
};
let ser: Zga.PdfSigner = new Zga.PdfSigner(sopt);
let u8dat: Uint8Array = await ser.sign(pdf);
let outPath: string = m_path.join(__dirname, workpath + "tsa_" + m_path.basename(pdfPath));
m_fs.writeFileSync(outPath, u8dat);
console.log("Output file: " + outPath);
return outPath;
}
async function main1(angle: number): Promise<void> {
let pdfPath: string = m_path.join(__dirname, workpath + "_test" + (angle ? "_" + angle : "") + ".pdf");
let pfxPath: string = m_path.join(__dirname, workpath + "_test.pfx");
let ps: string = "";
let imgPath: string = m_path.join(__dirname, workpath + "_test.png");
let fontPath: string = angle ? Zga.PDFLib.StandardFonts.TimesRomanBold : m_path.join(__dirname, workpath + "_test.ttf");
if (process.argv.length > 3) {
pfxPath = process.argv[2];
ps = process.argv[3];
} else if (process.argv[2]) {
ps = process.argv[2];
}
if (!ps) {
// throw new Error("The passphrase is not specified.");
pfxPath = "";
}
if (pfxPath) {
await sign_protect(pdfPath, pfxPath, ps, 1, imgPath, "あいうえおあいうえおか\r\n\nThis is a test of text!\n");
pdfPath = await sign_protect(pdfPath, pfxPath, ps, 2, imgPath, (angle ? "" : "ありがとうご\r\n\n") + "This is an another test of text!\n", fontPath);
pdfPath = await sign_protect(pdfPath, pfxPath, ps, 0, undefined, (angle ? "" : "たちつて得\n\n") + "This is a test for same font!\n", fontPath);
await addtsa(pdfPath);
} else {
await addtsa(pdfPath);
}
console.log("Done");
}
async function main(): Promise<void> {
let arr: Array<number> = [0, 90, 180, 270];
for (let i = 0; i < arr.length; i++) {
await main1(arr[i]);
// break;
}
}
main();

24
test-ts/tsconfig.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"newLine": "LF",
"declaration": true,
"sourceMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitThis": true,
"rootDir": "./src",
"outDir": "./test",
"typeRoots": [
"node_modules/@types"
]
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}

View File

@ -5,6 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> Test for ZgaPdfSigner </title> <title> Test for ZgaPdfSigner </title>
<script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js" type="text/javascript"></script> <script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js" type="text/javascript"></script>
<script src="https://unpkg.com/pdf-fontkit@1.8.9/dist/fontkit.umd.min.js" type="text/javascript"></script>
<script src="https://unpkg.com/pako@1.0.11/dist/pako_inflate.min.js" type="text/javascript"></script>
<script src="https://unpkg.com/node-forge@1.3.1/dist/forge.min.js" type="text/javascript"></script> <script src="https://unpkg.com/node-forge@1.3.1/dist/forge.min.js" type="text/javascript"></script>
<script src="dist/zgapdfsigner.min.js" type="text/javascript"></script> <script src="dist/zgapdfsigner.min.js" type="text/javascript"></script>
<script type="text/javascript"> <script type="text/javascript">
@ -63,6 +65,10 @@ async function testMe(){
if(img){ if(img){
imgType = getFilExt("img"); imgType = getFilExt("img");
} }
/** @type {string} */
var txt = document.getElementById("txt").value;
/** @type {ArrayBuffer} */
var font = await readFile("font");
/** @type {ArrayBuffer} */ /** @type {ArrayBuffer} */
var pubcert = await readFile("pubcert"); var pubcert = await readFile("pubcert");
@ -81,17 +87,31 @@ async function testMe(){
contact: document.getElementById("tContact").value, contact: document.getElementById("tContact").value,
debug: true, debug: true,
}; };
if(img){ if(img || txt){
sopt.drawinf = { sopt.drawinf = {
area: { area: {
x: 25, // left x: parseInt(document.getElementById("drawx").value), // left
y: 150, // top y: 150, // top
w: 60, w: txt ? undefined : 60,
h: 60, h: txt ? undefined : 100,
}, },
// pageidx: 2, pageidx: "-",
imgInfo: img ? {
imgData: img, imgData: img,
imgType: imgType, imgType: imgType,
} : undefined,
textInfo: txt ? {
text: txt,
fontData: font,
subset: true,
color: "f00",
size: 16,
align: 2,
wMax: 80,
yOffset: 10,
xOffset: 20,
noBreaks: "[あいうえおA-Za-z0-9]",
} : undefined,
}; };
} }
} }
@ -145,7 +165,7 @@ function test(){
}); });
} }
function clearFiles(){ function clearFiles(){
["fff","kkk","img","pubcert"].forEach((a_id) => { ["fff","kkk","img","font","pubcert"].forEach((a_id) => {
document.getElementById(a_id).value = ""; document.getElementById(a_id).value = "";
}); });
} }
@ -187,6 +207,9 @@ span.header {
<label>certificate </label><input type="file" id="kkk" /><br /> <label>certificate </label><input type="file" id="kkk" /><br />
<label>passphrase </label><input type="password" id="pwd" /><br /> <label>passphrase </label><input type="password" id="pwd" /><br />
<label>signature image </label><input type="file" id="img" /><br /> <label>signature image </label><input type="file" id="img" /><br />
<label>signature text </label><input type="text" id="txt" /><br />
<label>text font </label><input type="file" id="font" /><br />
<label>left </label><input type="text" id="drawx" value="25" /><br />
<label>permission </label> <label>permission </label>
<select id="sperm" onchange="changeSperm()"> <select id="sperm" onchange="changeSperm()">
<option value="0">No DocMDP</option> <option value="0">No DocMDP</option>

View File

@ -1,3 +1,10 @@
// ES Module Mode
// import * as m_fs from "node:fs";
// import * as m_path from "node:path";
// import { fileURLToPath } from "node:url";
// import { default as Zga } from "./lib/zganode.js";
// const __dirname = m_path.dirname(fileURLToPath(import.meta.url));
const m_fs = require("fs"); const m_fs = require("fs");
const m_path = require("path"); const m_path = require("path");
const Zga = require("./lib/zganode.js"); const Zga = require("./lib/zganode.js");
@ -10,9 +17,11 @@ const workpath = "test/";
* @param {string} ps * @param {string} ps
* @param {number} perm * @param {number} perm
* @param {string=} imgPath * @param {string=} imgPath
* @param {string=} txt
* @param {string=} fontPath
* @return {Promise<string>} output path * @return {Promise<string>} output path
*/ */
async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){ async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath, txt, fontPath){
/** @type {Buffer} */ /** @type {Buffer} */
var pdf = m_fs.readFileSync(pdfPath); var pdf = m_fs.readFileSync(pdfPath);
/** @type {Buffer} */ /** @type {Buffer} */
@ -21,6 +30,8 @@ async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){
var img = null; var img = null;
/** @type {string} */ /** @type {string} */
var imgType = ""; var imgType = "";
/** @type {Buffer|string} */
var font = null;
if(perm == 1){ if(perm == 1){
console.log("\nTest signing pdf with full protection. (permission 1 and password encryption)"); console.log("\nTest signing pdf with full protection. (permission 1 and password encryption)");
@ -32,6 +43,13 @@ async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){
img = m_fs.readFileSync(imgPath); img = m_fs.readFileSync(imgPath);
imgType = m_path.extname(imgPath).slice(1); imgType = m_path.extname(imgPath).slice(1);
} }
if(fontPath){
if(Zga.PDFLib.isStandardFont(fontPath)){
font = fontPath;
}else{
font = m_fs.readFileSync(fontPath);
}
}
/** @type {SignOption} */ /** @type {SignOption} */
var sopt = { var sopt = {
p12cert: pfx, p12cert: pfx,
@ -44,16 +62,31 @@ async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){
ltv: 1, ltv: 1,
debug: true, debug: true,
}; };
if(img){ if(img || txt){
sopt.drawinf = { sopt.drawinf = {
area: { area: {
x: 25, // left x: perm ? 25 : 200, // left
y: 150, // top y: 50, // top
w: 60, w: txt ? undefined : 60,
h: 60, h: txt ? undefined : 100,
}, },
pageidx: "-",
imgInfo: img ? {
imgData: img, imgData: img,
imgType: imgType, imgType: imgType,
} : undefined,
textInfo: txt ? {
text: txt,
fontData: font,
color: "00f0f1",
lineHeight: 20,
size: 16,
align: 1,
wMax: 80,
yOffset: 10,
xOffset: 20,
noBreaks: "[あいうえおA-Za-z0-9]",
} : undefined,
}; };
} }
@ -73,7 +106,7 @@ async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){
var u8dat = await ser.sign(pdf, eopt); var u8dat = await ser.sign(pdf, eopt);
if(u8dat){ if(u8dat){
/** @type {string} */ /** @type {string} */
var outPath = m_path.join(__dirname, workpath+"test_perm"+perm+".pdf"); var outPath = m_path.join(__dirname, workpath+"test_perm"+perm+m_path.basename(pdfPath));
m_fs.writeFileSync(outPath, u8dat); m_fs.writeFileSync(outPath, u8dat);
console.log("Output file: " + outPath); console.log("Output file: " + outPath);
} }
@ -103,21 +136,27 @@ async function addtsa(pdfPath){
/** @type {Uint8Array} */ /** @type {Uint8Array} */
var u8dat = await ser.sign(pdf); var u8dat = await ser.sign(pdf);
/** @type {string} */ /** @type {string} */
var outPath = m_path.join(__dirname, workpath+"test_tsa.pdf"); var outPath = m_path.join(__dirname, workpath+"tsa_"+m_path.basename(pdfPath));
m_fs.writeFileSync(outPath, u8dat); m_fs.writeFileSync(outPath, u8dat);
console.log("Output file: " + outPath); console.log("Output file: " + outPath);
return outPath; return outPath;
} }
async function main(){ /**
* @param {number} angle
*/
async function main1(angle){
/** @type {string} */ /** @type {string} */
var pdfPath = m_path.join(__dirname, workpath+"_test.pdf"); var pdfPath = m_path.join(__dirname, workpath+"_test"+(angle ? "_"+angle : "")+".pdf");
/** @type {string} */ /** @type {string} */
var pfxPath = m_path.join(__dirname, workpath+"_test.pfx"); var pfxPath = m_path.join(__dirname, workpath+"_test.pfx");
/** @type {string} */ /** @type {string} */
var ps = ""; var ps = "";
/** @type {string} */ /** @type {string} */
var imgPath = m_path.join(__dirname, workpath+"_test.png"); var imgPath = m_path.join(__dirname, workpath+"_test.png");
/** @type {string} */
var fontPath = m_path.join(__dirname, workpath+"_test.ttf");
// var fontPath = Zga.PDFLib.StandardFonts.CourierBold;
if(process.argv.length > 3){ if(process.argv.length > 3){
pfxPath = process.argv[2]; pfxPath = process.argv[2];
@ -132,8 +171,14 @@ async function main(){
} }
if(pfxPath){ if(pfxPath){
await sign_protect(pdfPath, pfxPath, ps, 1, imgPath); await sign_protect(pdfPath, pfxPath, ps, 1, imgPath, "あいうえおあいうえおか\r\n\nThis is a test of text!\n");
pdfPath = await sign_protect(pdfPath, pfxPath, ps, 2, imgPath); if(Zga.PDFLib.isStandardFont(fontPath)){
pdfPath = await sign_protect(pdfPath, pfxPath, ps, 2, imgPath, "This is an another test of text!\n", fontPath);
pdfPath = await sign_protect(pdfPath, pfxPath, ps, 0, undefined, "This is a test for same font!\n", fontPath);
}else{
pdfPath = await sign_protect(pdfPath, pfxPath, ps, 2, imgPath, "ありがとうご\r\nThis is an another test of text!\n", fontPath);
pdfPath = await sign_protect(pdfPath, pfxPath, ps, 0, undefined, "たちつてと\n\nThis is a test for same font!\n", fontPath);
}
await addtsa(pdfPath); await addtsa(pdfPath);
}else{ }else{
await addtsa(pdfPath); await addtsa(pdfPath);
@ -142,4 +187,14 @@ async function main(){
console.log("Done"); console.log("Done");
} }
async function main(){
/** @type {Array<number>} */
var arr = [0, 90, 180, 270];
/** @type {number} */
for(var i=0; i<arr.length; i++){
await main1(arr[i]);
// break;
}
}
main(); main();