Working on text signature and placing on multiple pages.

sigtext
zboris12 2024-08-09 21:57:20 +09:00
parent fd2b97d5b3
commit 0505fd1e0e
5 changed files with 583 additions and 143 deletions

View File

@ -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,9 +134,11 @@ async function sign2(pdf, cert, pwd, imgdat, imgtyp){
w: 60, // width
h: 60, // height
},
imgInfo: {
imgData: imgdat,
imgType: imgtyp,
},
},
};
var signer = new Zga.PdfSigner(sopt);
var u8arr = await signer.sign(pdf);
@ -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,
},
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,
};
}

View File

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

View File

@ -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),
* }}

View File

@ -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
// Add our signature widget to the pages
pgidxs.forEach(function(pi){
/** @const {PDFLib.PDFPage} */
var p = pdfdoc.getPages()[pi];
/** @type {PDFLib.PDFArray} */
var ans = page.node.Annots();
var ans = p.node.Annots();
if(!ans){
ans = new PDFLib.PDFArray(pdfcont);
// if(docMdp){
page.node.set(PDFLib.PDFName.Annots, ans);
p.node.set(PDFLib.PDFName.Annots, ans);
// }else{
// page.node.set(PDFLib.PDFName.Annots, pdfcont.register(ans));
// p.node.set(PDFLib.PDFName.Annots, pdfcont.register(ans));
// }
}
ans.push(widgetDictRef);
});
if(!afrm.dict.lookup(PDFLib.PDFName.of("SigFlags"))){
afrm.dict.set(PDFLib.PDFName.of("SigFlags"), PDFLib.PDFNumber.of(3));
@ -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;
}
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){
/** @type {Array<PDFLib.PDFOperator>} */
var txtOprs = [];
if(this.drawinf.textInfo){
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")));
txtOprs = this.createDrawTextOper(pdfdoc, pgrot, fontName, areainf);
}
/** @type {Array<PDFLib.PDFOperator>} */
var imgOprs = [];
if(this.drawinf.img){
rscObj["XObject"] = {
[imgName]: this.drawinf.img.ref,
};
imgOprs = PDFLib.drawImage(imgName, this.calcDrawImgInf(pgrot, this.drawinf.img.size(), areainf, txtOprs.length == 0));
}
areainf = this.calcAreaInf(pgsz, pgrot.angle, areainf);
this.rect = this.calcRect(pgrot.angle, areainf);
var frmDict = /** @type {PDFLib.PDFDict} */(pdfdoc.context.obj({
"Type": "XObject",
"Subtype": "Form",
"FormType": 1,
"BBox": [0, 0, areainf.w, areainf.h],
"BBox": [0, 0, areainf.wDraw, areainf.hDraw],
"Resources": rscObj,
}));
/** @type {PDFLib.PDFContentStream} */
var strm = PDFLib.PDFContentStream.of(frmDict, sigOprs, true);
var strm = PDFLib.PDFContentStream.of(frmDict, imgOprs.concat(txtOprs), true);
return pdfdoc.context.register(strm);
}
@ -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;
}
};

View File

@ -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,
},
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();