Compare commits

..

11 Commits
2.5.2 ... main

17 changed files with 1586 additions and 529 deletions

104
README.md
View File

@ -14,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
@ -32,12 +33,16 @@ 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) * [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
@ -48,8 +53,14 @@ 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.
@ -59,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.
@ -81,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
@ -133,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);
@ -143,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)
@ -198,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];
@ -226,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 = {
@ -239,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,
}; };
} }

View File

@ -9,6 +9,8 @@ else
mkdir ${OUTFLDR} mkdir ${OUTFLDR}
fi fi
VER=$(sed -n -r "s/^.*\"version\": ?\"([0-9.]+)\".*$/\1/p" package.json)
GCCOPT="--charset UTF-8 --compilation_level SIMPLE_OPTIMIZATIONS --warning_level VERBOSE" 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" GCCEXT="--externs closure/google-ext.js --externs closure/forge-ext.js --externs closure/pdflib-ext.js --externs closure/zb-externs.js"
jss="" jss=""
@ -20,7 +22,12 @@ do
if [ "$c" != "#" ] if [ "$c" != "#" ]
then then
outf="${OUTFLDR}/_${js}" 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}" sed -e "s/\/\/Only for nodejs Start\/\//\/*/g" -e "s/\/\/Only for nodejs End\/\//*\//g" "lib/${js}" > "${outf}"
fi
if [ $? -eq 0 ] if [ $? -eq 0 ]
then then
echo "Created js file: ${outf}" echo "Created js file: ${outf}"
@ -32,6 +39,7 @@ do
fi fi
fi fi
done <<EOF done <<EOF
zgafetch.js
zgacertsutil.js zgacertsutil.js
zgapdfcryptor.js zgapdfcryptor.js
zgapdfsigner.js zgapdfsigner.js

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;
} }

321
package-lock.json generated
View File

@ -1,16 +1,17 @@
{ {
"name": "zgapdfsigner", "name": "zgapdfsigner",
"version": "2.5.2", "version": "2.7.2",
"lockfileVersion": 2, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "zgapdfsigner", "name": "zgapdfsigner",
"version": "2.5.2", "version": "2.7.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "1.15.6", "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": {
@ -172,35 +173,6 @@
"integrity": "sha512-E45cJD6/xLJlL8pL6HEoxu8nEKp87CnrojUK0UuHiT7ZjCsrJfR4WhZwNNCq2+/6gYD9unGgMsunV4DDtBbvaA==", "integrity": "sha512-E45cJD6/xLJlL8pL6HEoxu8nEKp87CnrojUK0UuHiT7ZjCsrJfR4WhZwNNCq2+/6gYD9unGgMsunV4DDtBbvaA==",
"dev": true "dev": true
}, },
"node_modules/google-closure-compiler-linux": {
"version": "20231112.0.0",
"resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20231112.0.0.tgz",
"integrity": "sha512-qi2DbqQ+OuZ4Mcp1EttmL4j3oqvXLPl8XREekkfdV651PXBNenoBG6EnzQIius7ESYVgmpbQE4Pw2wNhPudBBQ==",
"cpu": [
"x32",
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/google-closure-compiler-osx": {
"version": "20231112.0.0",
"resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20231112.0.0.tgz",
"integrity": "sha512-gNnlnVH4rVO5TyDhvqELzRc9Oydaxincj0QLsAQQkM0btBTUEEyFL1ACT00RgJKxrCTb5Lfa83DfU8ICJi5Ptw==",
"cpu": [
"x32",
"x64",
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/google-closure-compiler-windows": { "node_modules/google-closure-compiler-windows": {
"version": "20231112.0.0", "version": "20231112.0.0",
"resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20231112.0.0.tgz", "resolved": "https://registry.npmjs.org/google-closure-compiler-windows/-/google-closure-compiler-windows-20231112.0.0.tgz",
@ -258,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",
@ -378,282 +358,5 @@
"source-map": "^0.5.1" "source-map": "^0.5.1"
} }
} }
},
"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"
}
},
"@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"
}
},
"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,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"dev": true
},
"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
},
"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
},
"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,
"requires": {
"inherits": "^2.0.1",
"process-nextick-args": "^2.0.0",
"readable-stream": "^2.3.5"
}
},
"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,
"requires": {
"color-name": "~1.1.4"
}
},
"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
},
"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
},
"follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
},
"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,
"requires": {
"chalk": "4.x",
"google-closure-compiler-java": "^20231112.0.0",
"google-closure-compiler-linux": "^20231112.0.0",
"google-closure-compiler-osx": "^20231112.0.0",
"google-closure-compiler-windows": "^20231112.0.0",
"minimist": "1.x",
"vinyl": "2.x",
"vinyl-sourcemaps-apply": "^0.2.0"
}
},
"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
},
"google-closure-compiler-linux": {
"version": "20231112.0.0",
"resolved": "https://registry.npmjs.org/google-closure-compiler-linux/-/google-closure-compiler-linux-20231112.0.0.tgz",
"integrity": "sha512-qi2DbqQ+OuZ4Mcp1EttmL4j3oqvXLPl8XREekkfdV651PXBNenoBG6EnzQIius7ESYVgmpbQE4Pw2wNhPudBBQ==",
"dev": true,
"optional": true
},
"google-closure-compiler-osx": {
"version": "20231112.0.0",
"resolved": "https://registry.npmjs.org/google-closure-compiler-osx/-/google-closure-compiler-osx-20231112.0.0.tgz",
"integrity": "sha512-gNnlnVH4rVO5TyDhvqELzRc9Oydaxincj0QLsAQQkM0btBTUEEyFL1ACT00RgJKxrCTb5Lfa83DfU8ICJi5Ptw==",
"dev": true,
"optional": true
},
"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==",
"dev": true,
"optional": true
},
"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
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true
},
"minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true
},
"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"
}
},
"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
},
"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,
"requires": {
"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"
}
},
"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
},
"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
},
"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
},
"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
},
"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,
"requires": {
"safe-buffer": "~5.1.0"
}
},
"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,
"requires": {
"has-flag": "^4.0.0"
}
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"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
},
"vinyl": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz",
"integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==",
"dev": true,
"requires": {
"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"
}
},
"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,
"requires": {
"source-map": "^0.5.1"
}
}
} }
} }

View File

@ -1,21 +1,23 @@
{ {
"name": "zgapdfsigner", "name": "zgapdfsigner",
"version": "2.5.2", "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", "dist/*.min.js",
"lib/*.d.ts",
"lib/*.js" "lib/*.js"
], ],
"keywords": [ "keywords": [
@ -30,12 +32,13 @@
], ],
"scripts": { "scripts": {
"build": "./build.sh", "build": "./build.sh",
"test": "node test4node.js" "test": "node test4node.js ${pfxpwd}"
}, },
"dependencies": { "dependencies": {
"follow-redirects": "1.15.6", "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" "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();