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>
# 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.
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
* 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).
* 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)
* Sign a pdf with a timestamp from [TSA](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)
* 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 :sunflower:)
* 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:
* 40bit 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,
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
* [pdf-lib](https://pdf-lib.js.org/)
* [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
:question: For more details please see the [wiki](https://github.com/zboris12/zgapdfsigner/wiki/API).
### Web Browser
Just import the dependencies and this tool.
```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/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)
Load the dependencies and this tool.
@ -52,16 +70,24 @@ function setTimeout(func, sleep){
Utilities.sleep(sleep);
func();
}
// Simulate clearTimeout function for pdf-fontkit
function clearTimeout(timeoutID){
// Do nothing
}
// Simulate window for node-forge
var window = globalThis;
// Load pdf-lib
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
eval(UrlFetchApp.fetch("https://unpkg.com/node-forge@1.3.1/dist/forge.min.js").getContentText());
// 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".
Script id: `1T0UPf50gGp2fJ4dR1rZfEFgKYC5VpCwUVooCRNySiL7klvIUVsFBCZ9m`
2. Load the library.
@ -74,9 +100,18 @@ pdfkit.loadZga(globalThis);
```
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
```js
// CommonJS Mode
const Zga = require("zgapdfsigner");
// ES Module Mode
import { default as Zga } from "zgapdfsigner";
// Typescript
import * as Zga from "zgapdfsigner";
```
## Let's sign
@ -126,9 +161,11 @@ async function sign2(pdf, cert, pwd, imgdat, imgtyp){
w: 60, // width
h: 60, // height
},
imgInfo: {
imgData: imgdat,
imgType: imgtyp,
},
},
};
var signer = new Zga.PdfSigner(sopt);
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
//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)
@ -191,6 +259,10 @@ async function main(){
var ps = "";
/** @type {string} */
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){
pfxPath = process.argv[2];
@ -219,6 +291,11 @@ async function main(){
img = m_fs.readFileSync(imgPath);
imgType = m_path.extname(imgPath).slice(1);
}
/** @type {Buffer} */
var font = null;
if(fontPath){
font = m_fs.readFileSync(fontPath);
}
/** @type {SignOption} */
var sopt = {
@ -232,16 +309,24 @@ async function main(){
ltv: 1,
debug: true,
};
if(img){
if(img || txt){
sopt.drawinf = {
area: {
x: 25, // left
y: 150, // top
w: 60,
h: 60,
y: 50, // top
w: txt ? undefined : 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,
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
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
* 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;
/** @const */
var pako = {};
/**
* @param {Uint8Array} input
* @return {Uint8Array}
*/
pako.inflate = function(input){};
/** @constructor */
var Fontkit = function(){};
/** @const {Fontkit} */
var fontkit;
/** @const */
var PDFLib = {};
@ -23,6 +36,26 @@ PDFLib.copyStringIntoBuffer = function(str, buffer, offset){};
* @return {Uint8Array}
*/
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 */
PDFLib.PDFDocument = function(){};
@ -50,6 +83,10 @@ PDFLib.PDFDocument.prototype.save = function(options){};
* @returns {Array<PDFLib.PDFPage>}
*/
PDFLib.PDFDocument.prototype.getPages = function(){};
/**
* @returns {number}
*/
PDFLib.PDFDocument.prototype.getPageCount = function(){};
/**
* @param {ArrayBuffer|Uint8Array|string} png
* @returns {Promise<PDFLib.PDFImage>}
@ -60,6 +97,32 @@ PDFLib.PDFDocument.prototype.embedPng = function(png){};
* @returns {Promise<PDFLib.PDFImage>}
*/
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>}
*/
@ -268,6 +331,8 @@ PDFLib.PDFName.Annots;
* @return {PDFLib.PDFName}
*/
PDFLib.PDFName.of = function(value){};
/** @return {string} */
PDFLib.PDFName.prototype.asString = function(){};
/** @type {string} */
PDFLib.PDFName.prototype.encodedName;
/** @type {number} */
@ -290,10 +355,20 @@ PDFLib.PDFArray.prototype.push = function(object){};
* @return {PDFLib.PDFObject}
*/
PDFLib.PDFArray.prototype.get = function(idx){};
/**
* @return {number}
*/
PDFLib.PDFArray.prototype.size = function(){};
/**
* @return {Array<PDFLib.PDFObject>}
*/
PDFLib.PDFArray.prototype.asArray = function(){};
/**
* @param {number} idx
* @param {*} typ
* @return {PDFLib.PDFObject}
*/
PDFLib.PDFArray.prototype.lookupMaybe = function(idx, typ){};
/**
* @constructor
@ -355,12 +430,55 @@ PDFLib.PDFImage.prototype.size = function(){};
/** @type {PDFLib.PDFRef} */
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 */
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} */
PDFLib.PDFFont.prototype.ref;
/** @constructor */
PDFLib.StandardFonts = function(){};
/** @type {string} */
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 = {};
/** @type {string} */
@ -392,14 +510,49 @@ PDFLib.PDFOperator = function(){};
* rotate: (PDFLib.Rotation|undefined),
* xSkew: (PDFLib.Rotation|undefined),
* ySkew: (PDFLib.Rotation|undefined),
* graphicsState: (string|undefined),
* }}
*/
var PdfDrawimgOption;
/**
* @param {string} name
* @param {PdfDrawimgOption} options
* @return {Array<PDFLib.PDFOperator>}
*/
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
@ -440,6 +593,12 @@ PDFLib.PDFContentStream.of = function(dict, operators, encode){};
* @extends {PDFLib.PDFStream}
*/
PDFLib.PDFRawStream = function(){};
/** @type {PDFLib.PDFDict} */
PDFLib.PDFRawStream.prototype.dict;
/**
* @return {Uint8Array}
*/
PDFLib.PDFRawStream.prototype.getContents = function(){};
/**
* @constructor

View File

@ -9,24 +9,75 @@
var TsaServiceInfo;
/**
* the base point of x, y is top left corner.
* wDraw, hDraw: Only for internal process.
* @typedef
* {{
* x: number,
* y: number,
* w: number,
* h: number,
* w: (number|undefined),
* h: (number|undefined),
* wDraw: (number|undefined),
* hDraw: (number|undefined),
* }}
*/
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
* {{
* area: SignAreaInfo,
* pageidx: (number|undefined),
* pageidx: (number|string|undefined),
* imgData: (Array<number>|Uint8Array|ArrayBuffer|string|undefined),
* imgType: (string|undefined),
* text: (string|undefined),
* fontData: (PDFLib.StandardFonts|Array<number>|Uint8Array|ArrayBuffer|string|undefined),
* imgInfo: (SignImageInfo|undefined),
* textInfo: (SignTextInfo|undefined),
* img: (PDFLib.PDFImage|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(){
/** @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){
console.log(msg);
console.log(...msg);
}
};
@ -42,22 +44,6 @@ function genZga(){
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;
}
@ -68,6 +54,7 @@ if(typeof exports === "object" && typeof module !== "undefined"){
//Only for nodejs End//
if(!globalThis.Zga){
globalThis.Zga = genZga();
supplyZgaUrlFetch(globalThis.Zga);
supplyZgaCertsChain(globalThis.Zga);
supplyZgaCryptor(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");
z.forge = require("node-forge");
z.PDFLib = require("pdf-lib");
/**
* @param {string} url
* @param {UrlFetchParams} params
* @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);
});
};
// z.fontkit = require("@pdf-lib/fontkit");
z.fontkit = require("pdf-fontkit");
z.pako = require("pako");
require("./zgafetch.js")(z);
require("./zgacertsutil.js")(z);
require("./zgapdfcryptor.js")(z);
require("./zgapdfsigner.js")(z);

View File

@ -12,6 +12,12 @@ if(z.forge){
if(z.PDFLib){
var PDFLib = z.PDFLib;
}
if(z.fontkit){
var fontkit = z.fontkit;
}
if(z.pako){
var pako = z.pako;
}
//Only for nodejs End//
/** @type {Object<string, TsaServiceInfo>} */
@ -240,6 +246,8 @@ z.PdfSigner = class{
this.oriU8pdf = null;
/** @private @type {Array<PdfObjEntry>} */
this.apobjs = null;
/** @private @type {z.PdfFonts} */
this.fonts = null;
if(typeof this.opt.debug == "boolean"){
z.debug = this.opt.debug;
@ -252,6 +260,9 @@ z.PdfSigner = class{
if(!(globalThis.forge || forge)){
throw new Error("node-forge is not imported.");
}
if(z.ver){
z.log("ZgaPdfSigner Version:", z.ver);
}
/** @type {?TsaServiceInfo} */
var tsainf = null;
if(signopt.signdate){
@ -265,11 +276,13 @@ z.PdfSigner = class{
}
if(tsainf){
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]){
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));
}
if(!tsainf.len){
@ -312,23 +325,36 @@ z.PdfSigner = class{
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} */
var imgData2 = null;
if(Array.isArray(_this.opt.drawinf.imgData)){
imgData2 = new Uint8Array(_this.opt.drawinf.imgData);
if(Array.isArray(_this.opt.drawinf.imgInfo.imgData)){
imgData2 = new Uint8Array(_this.opt.drawinf.imgInfo.imgData);
}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);
}else if(_this.opt.drawinf.imgType == "jpg"){
}else if(_this.opt.drawinf.imgInfo.imgType == "jpg"){
_this.opt.drawinf.img = await pdfdoc.embedJpg(imgData2);
}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} */
var cert = _this.loadP12cert(_this.opt.p12cert, _this.opt.pwd);
/** @type {Zga.CertsChain} */
@ -362,6 +388,8 @@ z.PdfSigner = class{
var dmydoc = await _this.addDss(pdfdoc);
if(dmydoc){
z.log("In order to enable LTV, DSS informations has been added to the pdf.");
}else{
await pdfdoc.flush();
}
// Clear ltv
_this.opt.ltv = 0;
@ -698,14 +726,18 @@ z.PdfSigner = class{
/** @const {PDFLib.PDFContext} */
const pdfcont = pdfdoc.context;
/** @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 page = pdfdoc.getPages()[signcrt.getPageIndex()];
const page = pdfdoc.getPages()[pgidxs[0]];
/** @type {PDFLib.PDFRef} */
var strmRef = signcrt.createStream(pdfdoc, _this.opt.signame);
if(docMdp && !strmRef){
strmRef = signcrt.createEmptyField(pdfcont);
// For invisible signature, only place on one page.
pgidxs = [pgidxs[0]];
}
/** @type {Array<string>} */
@ -813,18 +845,22 @@ z.PdfSigner = class{
/** @type {PDFLib.PDFRef} */
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} */
var ans = page.node.Annots();
var ans = p.node.Annots();
if(!ans){
ans = new PDFLib.PDFArray(pdfcont);
// if(docMdp){
page.node.set(PDFLib.PDFName.Annots, ans);
p.node.set(PDFLib.PDFName.Annots, ans);
// }else{
// page.node.set(PDFLib.PDFName.Annots, pdfcont.register(ans));
// p.node.set(PDFLib.PDFName.Annots, pdfcont.register(ans));
// }
}
ans.push(widgetDictRef);
});
if(!afrm.dict.lookup(PDFLib.PDFName.of("SigFlags"))){
afrm.dict.set(PDFLib.PDFName.of("SigFlags"), PDFLib.PDFNumber.of(3));
@ -1212,10 +1248,11 @@ z.PdfSigner = class{
z.SignatureCreator = class{
/**
* @param {SignDrawInfo=} drawinf
* @param {number=} pgcnt
*/
constructor(drawinf){
/** @private @type {number} */
this.pgidx = 0;
constructor(drawinf, pgcnt){
/** @private @type {Array<number>} */
this.pgidxs = [];
/** @private @type {Array<number>} */
this.rect = [0, 0, 0, 0];
/** @private @type {?SignDrawInfo} */
@ -1223,18 +1260,40 @@ z.SignatureCreator = class{
if(drawinf){
this.drawinf = drawinf;
if(this.drawinf.pageidx){
this.pgidx = this.drawinf.pageidx;
if(typeof this.drawinf.pageidx == "string"){
/** @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
* @return {number}
* @return {Array<number>}
*/
getPageIndex(){
return this.pgidx;
getPageIndexes(){
return this.pgidxs;
}
/**
@ -1268,7 +1327,7 @@ z.SignatureCreator = class{
createStream(pdfdoc, signame){
if(!this.drawinf){
return null;
}else if(!(this.drawinf.img || (this.drawinf.font && this.drawinf.font))){
}else if(!(this.drawinf.img || this.drawinf.textInfo)){
return null;
}
@ -1276,8 +1335,8 @@ z.SignatureCreator = class{
var pages = pdfdoc.getPages();
/** @type {PDFLib.PDFPage} */
var page = null;
if(this.pgidx < pages.length){
page = pages[this.pgidx];
if(this.pgidxs[0] < pages.length){
page = pages[this.pgidxs[0]];
}else{
throw new Error("Page index is overflow to pdf pages.");
}
@ -1288,51 +1347,46 @@ z.SignatureCreator = class{
/** @type {PdfSize} */
var pgsz = page.getSize();
/** @type {SignAreaInfo} */
var areainf = this.calcAreaInf(pgsz, pgrot.angle, this.drawinf.area);
var areainf = this.drawinf.area;
// resources object
/** @type {Object<string, *>} */
var rscObj = {};
/** @type {Array<PDFLib.PDFOperator>} */
var sigOprs = [];
/** @type {string} */
var imgName = signame ? signame.concat("Img") : "SigImg";
/** @type {string} */
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"] = {
[imgName]: this.drawinf.img.ref,
};
sigOprs = sigOprs.concat(PDFLib.drawImage(imgName, this.calcDrawImgInf(pgrot, areainf)));
}
if(this.drawinf.font){
/** @type {Array<PDFLib.PDFOperator>} */
var txtOprs = [];
if(this.drawinf.textInfo){
rscObj["Font"] = {
[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);
var frmDict = /** @type {PDFLib.PDFDict} */(pdfdoc.context.obj({
"Type": "XObject",
"Subtype": "Form",
"FormType": 1,
"BBox": [0, 0, areainf.w, areainf.h],
"BBox": [0, 0, areainf.wDraw, areainf.hDraw],
"Resources": rscObj,
}));
/** @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);
}
@ -1350,25 +1404,25 @@ z.SignatureCreator = class{
// Calculate position after rotate
switch(angle){
case 90:
ret.w = visinf.h;
ret.h = visinf.w;
ret.x = visinf.y + visinf.h;
ret.wDraw = visinf.hDraw;
ret.hDraw = visinf.wDraw;
ret.x = visinf.y + visinf.hDraw;
ret.y = visinf.x;
break;
case 180:
case -180:
ret.x = pgsz.width - visinf.x;
ret.y = visinf.y + visinf.h;
ret.y = visinf.y + visinf.hDraw;
break;
case 270:
case -90:
ret.w = visinf.h;
ret.h = visinf.w;
ret.x = pgsz.width - visinf.y - visinf.h;
ret.wDraw = visinf.hDraw;
ret.hDraw = visinf.wDraw;
ret.x = pgsz.width - visinf.y - visinf.hDraw;
ret.y = pgsz.height - visinf.x;
break;
default:
ret.y = pgsz.height - visinf.y - visinf.h;
ret.y = pgsz.height - visinf.y - visinf.hDraw;
}
return ret;
}
@ -1376,7 +1430,7 @@ z.SignatureCreator = class{
/**
* @private
* @param {number} angle
* @param {SignAreaInfo} areainf // { x, y, w, h }
* @param {SignAreaInfo} areainf
* @return {Array<number>}
*/
calcRect(angle, areainf){
@ -1386,22 +1440,22 @@ z.SignatureCreator = class{
rect[1] = areainf.y;
switch(angle){
case 90:
rect[2] = areainf.x - areainf.h;
rect[3] = areainf.y + areainf.w;
rect[2] = areainf.x - areainf.wDraw;
rect[3] = areainf.y + areainf.hDraw;
break;
case 180:
case -180:
rect[2] = areainf.x - areainf.w;
rect[3] = areainf.y - areainf.h;
rect[2] = areainf.x - areainf.wDraw;
rect[3] = areainf.y - areainf.hDraw;
break;
case 270:
case -90:
rect[2] = areainf.x + areainf.h;
rect[3] = areainf.y - areainf.w;
rect[2] = areainf.x + areainf.wDraw;
rect[3] = areainf.y - areainf.hDraw;
break;
default:
rect[2] = areainf.x + areainf.w;
rect[3] = areainf.y + areainf.h;
rect[2] = areainf.x + areainf.wDraw;
rect[3] = areainf.y + areainf.hDraw;
}
return rect;
}
@ -1411,41 +1465,580 @@ z.SignatureCreator = class{
*
* @private
* @param {PDFLib.Rotation} rot
* @param {SignAreaInfo} areainf // { x, y, w, h }
* @param {PdfSize} imgsz
* @param {SignAreaInfo} areainf
* @param {boolean} canResize
* @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} */
var ret = {
"x": 0,
"y": 0,
"width": areainf.w,
"height": areainf.h,
"width": wImg,
"height": hImg,
"rotate": rot,
"xSkew": PDFLib.degrees(0),
"ySkew": PDFLib.degrees(0),
// "graphicsState": "",
};
switch(rot.angle){
case 0:
ret["y"] = areainf.hDraw - hImg - ret["y"];
break;
case 90:
ret["x"] = areainf.w;
ret["width"] = areainf.h;
ret["height"] = areainf.w;
ret["x"] += hImg;
break;
case 180:
case -180:
ret["x"] = areainf.w;
ret["y"] = areainf.h;
ret["x"] = areainf.wDraw - ret["x"];
ret["y"] += hImg;
break;
case 270:
case -90:
ret["y"] = areainf.h;
ret["width"] = areainf.h;
ret["height"] = areainf.w;
ret["x"] = areainf.hDraw - hImg - ret["x"];
ret["y"] = areainf.wDraw - ret["y"];
break;
}
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",
"version": "2.5.0",
"lockfileVersion": 2,
"version": "2.7.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "zgapdfsigner",
"version": "2.5.0",
"version": "2.7.2",
"license": "MIT",
"dependencies": {
"follow-redirects": "1.15.2",
"follow-redirects": "1.15.6",
"node-forge": "1.3.1",
"pdf-fontkit": "1.8.9",
"pdf-lib": "1.17.1"
},
"devDependencies": {}
"devDependencies": {
"google-closure-compiler": "^20231112.0.0"
}
},
"node_modules/@pdf-lib/standard-fonts": {
"version": "1.0.0",
@ -31,10 +34,100 @@
"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": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"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": {
"version": "1.3.1",
"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",
"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": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
@ -74,59 +249,114 @@
"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": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"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": {
"@pdf-lib/standard-fonts": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
"integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
"requires": {
"pako": "^1.0.6"
"clone": "^2.1.1",
"clone-buffer": "^1.0.0",
"clone-stats": "^1.0.0",
"cloneable-readable": "^1.0.0",
"remove-trailing-separator": "^1.0.1",
"replace-ext": "^1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"@pdf-lib/upng": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
"integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
"requires": {
"pako": "^1.0.10"
}
},
"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=="
"node_modules/vinyl-sourcemaps-apply": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz",
"integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==",
"dev": true,
"dependencies": {
"source-map": "^0.5.1"
}
}
}
}

View File

@ -1,20 +1,23 @@
{
"name": "zgapdfsigner",
"version": "2.5.1",
"version": "2.7.3",
"author": "zboris12",
"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",
"private": false,
"repository": {
"type": "git",
"url": "https://github.com/zboris12/zgapdfsigner"
"url": "git+https://github.com/zboris12/zgapdfsigner.git"
},
"bugs": {
"url": "https://github.com/zboris12/zgapdfsigner/issues"
},
"license": "MIT",
"main": "lib/zganode.js",
"unpkg": "dist/zgapdfsigner.min.js",
"files": [
"dist/*.min.js",
"lib/*.d.ts",
"lib/*.js"
],
"keywords": [
@ -28,13 +31,16 @@
"長期署名"
],
"scripts": {
"build": "node closure.js",
"test": "node test4node.js"
"build": "./build.sh",
"test": "node test4node.js ${pfxpwd}"
},
"dependencies": {
"follow-redirects": "1.15.2",
"pdf-lib": "1.17.1",
"node-forge": "1.3.1"
"follow-redirects": "1.15.6",
"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">
<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-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="dist/zgapdfsigner.min.js" type="text/javascript"></script>
<script type="text/javascript">
@ -63,6 +65,10 @@ async function testMe(){
if(img){
imgType = getFilExt("img");
}
/** @type {string} */
var txt = document.getElementById("txt").value;
/** @type {ArrayBuffer} */
var font = await readFile("font");
/** @type {ArrayBuffer} */
var pubcert = await readFile("pubcert");
@ -81,17 +87,31 @@ async function testMe(){
contact: document.getElementById("tContact").value,
debug: true,
};
if(img){
if(img || txt){
sopt.drawinf = {
area: {
x: 25, // left
x: parseInt(document.getElementById("drawx").value), // left
y: 150, // top
w: 60,
h: 60,
w: txt ? undefined : 60,
h: txt ? undefined : 100,
},
// pageidx: 2,
pageidx: "-",
imgInfo: img ? {
imgData: img,
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(){
["fff","kkk","img","pubcert"].forEach((a_id) => {
["fff","kkk","img","font","pubcert"].forEach((a_id) => {
document.getElementById(a_id).value = "";
});
}
@ -187,6 +207,9 @@ span.header {
<label>certificate </label><input type="file" id="kkk" /><br />
<label>passphrase </label><input type="password" id="pwd" /><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>
<select id="sperm" onchange="changeSperm()">
<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_path = require("path");
const Zga = require("./lib/zganode.js");
@ -10,9 +17,11 @@ const workpath = "test/";
* @param {string} ps
* @param {number} perm
* @param {string=} imgPath
* @param {string=} txt
* @param {string=} fontPath
* @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} */
var pdf = m_fs.readFileSync(pdfPath);
/** @type {Buffer} */
@ -21,6 +30,8 @@ async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){
var img = null;
/** @type {string} */
var imgType = "";
/** @type {Buffer|string} */
var font = null;
if(perm == 1){
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);
imgType = m_path.extname(imgPath).slice(1);
}
if(fontPath){
if(Zga.PDFLib.isStandardFont(fontPath)){
font = fontPath;
}else{
font = m_fs.readFileSync(fontPath);
}
}
/** @type {SignOption} */
var sopt = {
p12cert: pfx,
@ -44,16 +62,31 @@ async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){
ltv: 1,
debug: true,
};
if(img){
if(img || txt){
sopt.drawinf = {
area: {
x: 25, // left
y: 150, // top
w: 60,
h: 60,
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,
};
}
@ -73,7 +106,7 @@ async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){
var u8dat = await ser.sign(pdf, eopt);
if(u8dat){
/** @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);
console.log("Output file: " + outPath);
}
@ -103,21 +136,27 @@ async function addtsa(pdfPath){
/** @type {Uint8Array} */
var u8dat = await ser.sign(pdf);
/** @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);
console.log("Output file: " + outPath);
return outPath;
}
async function main(){
/**
* @param {number} angle
*/
async function main1(angle){
/** @type {string} */
var pdfPath = m_path.join(__dirname, workpath+"_test.pdf");
var pdfPath = m_path.join(__dirname, workpath+"_test"+(angle ? "_"+angle : "")+".pdf");
/** @type {string} */
var pfxPath = m_path.join(__dirname, workpath+"_test.pfx");
/** @type {string} */
var ps = "";
/** @type {string} */
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){
pfxPath = process.argv[2];
@ -132,8 +171,14 @@ async function main(){
}
if(pfxPath){
await sign_protect(pdfPath, pfxPath, ps, 1, imgPath);
pdfPath = await sign_protect(pdfPath, pfxPath, ps, 2, imgPath);
await sign_protect(pdfPath, pfxPath, ps, 1, imgPath, "あいうえおあいうえおか\r\n\nThis is a test of text!\n");
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);
}else{
await addtsa(pdfPath);
@ -142,4 +187,14 @@ async function main(){
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();