Working on text signature and placing on multiple pages.
parent
fd2b97d5b3
commit
0505fd1e0e
73
README.md
73
README.md
|
@ -14,7 +14,8 @@ 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)
|
||||
|
@ -133,8 +134,10 @@ async function sign2(pdf, cert, pwd, imgdat, imgtyp){
|
|||
w: 60, // width
|
||||
h: 60, // height
|
||||
},
|
||||
imgData: imgdat,
|
||||
imgType: imgtyp,
|
||||
imgInfo: {
|
||||
imgData: imgdat,
|
||||
imgType: imgtyp,
|
||||
},
|
||||
},
|
||||
};
|
||||
var signer = new Zga.PdfSigner(sopt);
|
||||
|
@ -143,10 +146,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)
|
||||
|
@ -198,6 +232,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];
|
||||
|
@ -226,6 +264,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 = {
|
||||
|
@ -239,16 +282,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,
|
||||
},
|
||||
imgData: img,
|
||||
imgType: imgType,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
*/
|
||||
var PdfLoadOptions;
|
||||
|
||||
/** @const */
|
||||
var fontkit = {};
|
||||
|
||||
/** @const */
|
||||
var PDFLib = {};
|
||||
|
||||
|
@ -23,6 +26,21 @@ 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){};
|
||||
|
||||
/** @constructor */
|
||||
PDFLib.PDFDocument = function(){};
|
||||
|
@ -50,6 +68,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>}
|
||||
|
@ -82,6 +104,10 @@ PDFLib.PDFDocument.prototype.embedFont = function(font, options){};
|
|||
* @returns {PDFLib.PDFFont}
|
||||
*/
|
||||
PDFLib.PDFDocument.prototype.embedStandardFont = function(font, customName){};
|
||||
/**
|
||||
* @lends {fontkit} fkt
|
||||
*/
|
||||
PDFLib.PDFDocument.prototype.registerFontkit = function(fkt){};
|
||||
/**
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
|
@ -383,8 +409,23 @@ PDFLib.PDFFont = function(){};
|
|||
PDFLib.PDFFont.prototype.ref;
|
||||
/** @type {string} */
|
||||
PDFLib.PDFFont.prototype.name;
|
||||
/** @constructor */
|
||||
PDFLib.StandardFonts = function(){};
|
||||
/**
|
||||
* @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} */
|
||||
|
@ -416,6 +457,7 @@ PDFLib.PDFOperator = function(){};
|
|||
* rotate: (PDFLib.Rotation|undefined),
|
||||
* xSkew: (PDFLib.Rotation|undefined),
|
||||
* ySkew: (PDFLib.Rotation|undefined),
|
||||
* graphicsState: (string|undefined),
|
||||
* }}
|
||||
*/
|
||||
var PdfDrawimgOption;
|
||||
|
|
|
@ -9,24 +9,74 @@
|
|||
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),
|
||||
* 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: (Array<number>|Uint8Array|ArrayBuffer|string|undefined),
|
||||
* imgInfo: (SignImageInfo|undefined),
|
||||
* textInfo: (SignTextInfo|undefined),
|
||||
* img: (PDFLib.PDFImage|undefined),
|
||||
* font: (PDFLib.PDFFont|undefined),
|
||||
* }}
|
||||
|
|
|
@ -315,30 +315,40 @@ 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.text && _this.opt.drawinf.fontData && !_this.opt.drawinf.font){
|
||||
if(_this.opt.drawinf && _this.opt.drawinf.textInfo && !_this.opt.drawinf.font){
|
||||
/** @type {Uint8Array|ArrayBuffer|string} */
|
||||
var fontData2 = null;
|
||||
if(Array.isArray(_this.opt.drawinf.fontData)){
|
||||
fontData2 = new Uint8Array(_this.opt.drawinf.fontData);
|
||||
if(Array.isArray(_this.opt.drawinf.textInfo.fontData)){
|
||||
fontData2 = new Uint8Array(_this.opt.drawinf.textInfo.fontData);
|
||||
}else if(_this.opt.drawinf.textInfo.fontData){
|
||||
fontData2 = _this.opt.drawinf.textInfo.fontData;
|
||||
}else{
|
||||
fontData2 = _this.opt.drawinf.fontData;
|
||||
fontData2 = "Helvetica";
|
||||
}
|
||||
if(typeof fontData2 == "string"){
|
||||
_this.opt.drawinf.font = pdfdoc.embedStandardFont(fontData2);
|
||||
|
@ -719,14 +729,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>} */
|
||||
|
@ -834,18 +848,22 @@ z.PdfSigner = class{
|
|||
/** @type {PDFLib.PDFRef} */
|
||||
var widgetDictRef = pdfcont.register(pdfcont.obj(widgetObj));
|
||||
|
||||
// Add our signature widget to the page
|
||||
/** @type {PDFLib.PDFArray} */
|
||||
var ans = page.node.Annots();
|
||||
if(!ans){
|
||||
ans = new PDFLib.PDFArray(pdfcont);
|
||||
// if(docMdp){
|
||||
page.node.set(PDFLib.PDFName.Annots, ans);
|
||||
// }else{
|
||||
// page.node.set(PDFLib.PDFName.Annots, pdfcont.register(ans));
|
||||
// }
|
||||
}
|
||||
ans.push(widgetDictRef);
|
||||
// Add our signature widget to the pages
|
||||
pgidxs.forEach(function(pi){
|
||||
/** @const {PDFLib.PDFPage} */
|
||||
var p = pdfdoc.getPages()[pi];
|
||||
/** @type {PDFLib.PDFArray} */
|
||||
var ans = p.node.Annots();
|
||||
if(!ans){
|
||||
ans = new PDFLib.PDFArray(pdfcont);
|
||||
// if(docMdp){
|
||||
p.node.set(PDFLib.PDFName.Annots, ans);
|
||||
// }else{
|
||||
// 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));
|
||||
|
@ -1233,10 +1251,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} */
|
||||
|
@ -1244,18 +1263,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1289,7 +1330,7 @@ z.SignatureCreator = class{
|
|||
createStream(pdfdoc, signame){
|
||||
if(!this.drawinf){
|
||||
return null;
|
||||
}else if(!(this.drawinf.img || this.drawinf.text)){
|
||||
}else if(!(this.drawinf.img || this.drawinf.textInfo)){
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1297,8 +1338,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.");
|
||||
}
|
||||
|
@ -1309,59 +1350,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;
|
||||
}
|
||||
|
||||
/** @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,
|
||||
};
|
||||
sigOprs = sigOprs.concat(PDFLib.drawImage(imgName, this.calcDrawImgInf(pgrot, areainf)));
|
||||
}
|
||||
if(this.drawinf.text){
|
||||
/** @type {PDFLib.PDFHexString|undefined} */
|
||||
var txt = undefined;
|
||||
if(this.drawinf.font){
|
||||
rscObj["Font"] = {
|
||||
[fontName]: this.drawinf.font.ref,
|
||||
};
|
||||
txt = this.drawinf.font.encodeText(this.drawinf.text);
|
||||
}else{
|
||||
txt = PDFLib.PDFHexString.fromText(this.drawinf.text);
|
||||
}
|
||||
sigOprs = sigOprs.concat(PDFLib.drawLinesOfText([txt], this.calcDrawTextInf(pgrot, areainf, this.drawinf.font ? fontName : "Courier8")));
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1379,25 +1407,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;
|
||||
}
|
||||
|
@ -1405,7 +1433,7 @@ z.SignatureCreator = class{
|
|||
/**
|
||||
* @private
|
||||
* @param {number} angle
|
||||
* @param {SignAreaInfo} areainf // { x, y, w, h }
|
||||
* @param {SignAreaInfo} areainf
|
||||
* @return {Array<number>}
|
||||
*/
|
||||
calcRect(angle, areainf){
|
||||
|
@ -1415,22 +1443,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;
|
||||
}
|
||||
|
@ -1440,78 +1468,310 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate informations for drawing text after rotate
|
||||
* Create operations for drawing text after rotate
|
||||
*
|
||||
* @private
|
||||
* @param {PDFLib.PDFDocument} pdfdoc
|
||||
* @param {PDFLib.Rotation} rot
|
||||
* @param {SignAreaInfo} areainf // { x, y, w, h }
|
||||
* @param {string=} font
|
||||
* @return {DrawLinesOfTextOptions}
|
||||
* @param {string} fontName
|
||||
* @param {SignAreaInfo} areainf
|
||||
* @return {Array<PDFLib.PDFOperator>}
|
||||
*/
|
||||
calcDrawTextInf(rot, areainf, font){
|
||||
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 ret = {
|
||||
"x": 0,
|
||||
"y": 10,
|
||||
"color": PDFLib.rgb(0, 0, 0),
|
||||
"font": font,
|
||||
"lineHeight": 35,
|
||||
"size": 35,
|
||||
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": "",
|
||||
};
|
||||
switch(rot.angle){
|
||||
/**
|
||||
* @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(currWidth + width > maxWidth){
|
||||
lines.push(currLine);
|
||||
currLine = "";
|
||||
currWidth = 0;
|
||||
}
|
||||
currLine += word;
|
||||
currWidth += width;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
if(currLine)lines.push(currLine);
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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:
|
||||
ret["x"] = areainf.w;
|
||||
newopts["x"] = opts["lineHeight"] + opts["y"] + (opts["lineHeight"] * i);
|
||||
newopts["y"] = x;
|
||||
break;
|
||||
case 180:
|
||||
case -180:
|
||||
ret["x"] = areainf.w;
|
||||
ret["y"] = areainf.h;
|
||||
newopts["x"] = w - x;
|
||||
newopts["y"] = opts["lineHeight"] + opts["y"] + (opts["lineHeight"] * i);
|
||||
break;
|
||||
case 270:
|
||||
case -90:
|
||||
ret["y"] = areainf.h;
|
||||
newopts["x"] = h - opts["lineHeight"] - opts["y"] - (opts["lineHeight"] * i);
|
||||
newopts["y"] = w - x;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
return newopts;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
61
test4node.js
61
test4node.js
|
@ -10,9 +10,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 +23,8 @@ async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){
|
|||
var img = null;
|
||||
/** @type {string} */
|
||||
var imgType = "";
|
||||
/** @type {Buffer} */
|
||||
var font = null;
|
||||
|
||||
if(perm == 1){
|
||||
console.log("\nTest signing pdf with full protection. (permission 1 and password encryption)");
|
||||
|
@ -32,6 +36,9 @@ async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){
|
|||
img = m_fs.readFileSync(imgPath);
|
||||
imgType = m_path.extname(imgPath).slice(1);
|
||||
}
|
||||
if(fontPath){
|
||||
font = m_fs.readFileSync(fontPath);
|
||||
}
|
||||
/** @type {SignOption} */
|
||||
var sopt = {
|
||||
p12cert: pfx,
|
||||
|
@ -44,16 +51,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,
|
||||
y: 50, // top
|
||||
w: txt ? undefined : 60,
|
||||
h: txt ? undefined : 100,
|
||||
},
|
||||
imgData: img,
|
||||
imgType: imgType,
|
||||
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 +95,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);
|
||||
}
|
||||
|
@ -109,15 +131,20 @@ async function addtsa(pdfPath){
|
|||
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");
|
||||
|
||||
if(process.argv.length > 3){
|
||||
pfxPath = process.argv[2];
|
||||
|
@ -132,8 +159,8 @@ 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", fontPath);
|
||||
pdfPath = await sign_protect(pdfPath, pfxPath, ps, 2, undefined, "ありがとうご\r\n\nThis is an another test of text!\n", fontPath);
|
||||
await addtsa(pdfPath);
|
||||
}else{
|
||||
await addtsa(pdfPath);
|
||||
|
@ -142,4 +169,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();
|
||||
|
|
Loading…
Reference in New Issue