2022-09-18 09:49:56 +02:00
'use strict' ;
2022-09-17 14:55:09 +02:00
2022-10-02 14:45:06 +02:00
/ * *
* @ param { Object < string , * > } z
* /
function supplyZgaSigner ( z ) {
2022-09-18 09:49:56 +02:00
2022-09-24 06:59:30 +02:00
/** @type {Object<string, TsaServiceInfo>} */
2022-10-02 14:45:06 +02:00
z . TSAURLS = {
"1" : { url : "http://ts.ssl.com" , len : 15600 } ,
"2" : { url : "http://timestamp.digicert.com" , len : 15400 } ,
"3" : { url : "http://timestamp.sectigo.com" , len : 13400 } ,
"4" : { url : "http://timestamp.entrust.net/TSS/RFC3161sha2TS" , len : 14400 } ,
"5" : { url : "http://timestamp.apple.com/ts01" , len : 12100 } ,
"6" : { url : "http://www.langedge.jp/tsa" , len : 9200 } ,
"7" : { url : "https://freetsa.org/tsr" , len : 14500 } ,
} ;
2022-10-22 11:42:13 +02:00
// Google Apps Script
if ( globalThis . UrlFetchApp ) {
z . UrlFetchApp = { } ;
/ * *
* @ param { string } url
* @ param { UrlFetchParams } params
* @ return { Promise < Uint8Array > }
* /
z . UrlFetchApp . fetch = function ( url , params ) {
return new Promise ( function ( resolve ) {
/** @type {GBlob} */
var tblob = UrlFetchApp . fetch ( url , params ) . getBlob ( ) ;
resolve ( new Uint8Array ( tblob . getBytes ( ) ) ) ;
} ) ;
} ;
}
2022-10-14 14:12:43 +02:00
z . NewRef = class {
/ * *
* @ param { PDFLib . PDFRef } ref
* @ param { number = } num
* @ param { string = } nm
* /
constructor ( ref , num , nm ) {
/** @private @type {number} */
this . oriNumber = ref . objectNumber ;
/** @private @type {number} */
this . oriGeneration = ref . generationNumber ;
/** @private @type {string} */
this . name = nm ? nm : "" ;
/** @private @type {number} */
this . newNumber = num ? num : 0 ;
}
/ * *
* @ public
* @ param { number } num
* /
setNewNumber ( num ) {
this . newNumber = num ;
}
/ * *
* @ public
* @ param { boolean = } restore
* /
changeNumber ( restore ) {
if ( ! this . newNumber ) {
if ( restore ) {
return ;
} else {
throw new Error ( "Can NOT change number since new number is not set." ) ;
}
}
/** @type {PDFLib.PDFRef} */
var ref = PDFLib . PDFRef . of ( this . oriNumber , this . oriGeneration ) ;
ref . objectNumber = restore ? this . oriNumber : this . newNumber ;
ref . tag = ref . objectNumber + " " + this . oriGeneration + " R" ;
}
/ * *
* @ public
* @ return { string }
* /
toString ( ) {
return this . name + " -> old:" + this . oriNumber + ", new:" + this . newNumber ;
}
} ;
z . NewRefMap = class extends Map {
constructor ( ) {
super ( ) ;
/** @private @type {number} */
this . idx = 0 ;
/** @private @type {PDFLib.PDFContext} */
this . pdfcont = null ;
2022-10-20 14:28:27 +02:00
/** @private @type {number} */
this . oriLastOnum = 0 ;
2022-10-14 14:12:43 +02:00
}
/ * *
* @ public
* @ param { PDFLib . PDFDocument } pdfdoc
* @ param { boolean = } enc
* @ return { PDFLib . PDFRef } If enc is true , the return value is the unique reference reserved for encrypting information .
* /
reorderPdfRefs ( pdfdoc , enc ) {
2022-10-20 14:28:27 +02:00
/** @type {z.NewRefMap} */
const _this = this ;
_this . pdfcont = pdfdoc . context ;
2022-10-14 14:12:43 +02:00
/** @type {PDFLib.PDFRef} */
2022-10-20 14:28:27 +02:00
var encref = enc ? _this . pdfcont . nextRef ( ) : null ;
2022-10-14 14:12:43 +02:00
pdfdoc . getPages ( ) . forEach ( function ( /** @type {PDFLib.PDFPage} */ a _pg ) {
2022-10-20 14:28:27 +02:00
_this . addAndFindRelates ( a _pg . ref , "Page" ) ;
} ) ;
_this . addAndFindRelates ( _this . pdfcont . trailerInfo . Root , "Catalog" ) ;
2022-10-14 14:12:43 +02:00
if ( encref ) {
2022-10-20 14:28:27 +02:00
_this . addAndFindRelates ( encref , "Encrypt" ) ;
2022-10-14 14:12:43 +02:00
}
2022-10-20 14:28:27 +02:00
_this . pdfcont . enumerateIndirectObjects ( ) . forEach ( function ( /** @type {PdfObjEntry} */ a _oety ) {
2022-10-14 14:12:43 +02:00
/** @type {string} */
var a _tag = a _oety [ 0 ] . tag ;
/** @type {z.NewRef} */
2022-10-20 14:28:27 +02:00
var a _new = _this . get ( a _tag ) ;
2022-10-14 14:12:43 +02:00
if ( ! a _new ) {
2022-10-20 14:28:27 +02:00
a _new = new z . NewRef ( a _oety [ 0 ] , ++ _this . idx ) ;
_this . set ( a _tag , a _new ) ;
2022-10-14 14:12:43 +02:00
}
2022-10-20 14:28:27 +02:00
} ) ;
_this . changeAll ( ) ;
_this . oriLastOnum = _this . pdfcont . largestObjectNumber ;
_this . pdfcont . largestObjectNumber = _this . idx ;
2022-10-14 14:12:43 +02:00
return encref ;
}
/ * *
* @ public
* /
restoreAll ( ) {
2022-10-15 15:06:13 +02:00
this . changeAll ( true ) ;
2022-10-20 14:28:27 +02:00
this . pdfcont . largestObjectNumber = this . oriLastOnum ;
2022-10-14 14:12:43 +02:00
this . clear ( ) ;
this . idx = 0 ;
2022-10-20 14:28:27 +02:00
this . oriLastOnum = 0 ;
2022-10-14 14:12:43 +02:00
this . pdfcont = null ;
}
/ * *
* @ private
* @ param { PDFLib . PDFRef } a _ref
* @ param { string = } a _nm
* /
addAndFindRelates ( a _ref , a _nm ) {
if ( ! this . get ( a _ref . tag ) ) {
this . set ( a _ref . tag , new z . NewRef ( a _ref , ++ this . idx , a _nm ) ) ;
this . findRefs ( this . pdfcont . lookup ( a _ref ) , a _nm ) ;
}
}
/ * *
* @ private
2022-10-20 14:28:27 +02:00
* @ param { PDFLib . PDFObject | Array < PDFLib . PDFObject > | Map } a _val
2022-10-14 14:12:43 +02:00
* @ param { string = } a _nm
* /
findRefs ( a _val , a _nm ) {
if ( ! a _val || a _nm == "/Parent" ) {
return ;
}
if ( a _val instanceof PDFLib . PDFRef ) {
this . addAndFindRelates ( a _val , a _nm ) ;
return ;
}
if ( a _val . array ) {
a _val = a _val . array ;
}
if ( Array . isArray ( a _val ) ) {
a _val . forEach ( function ( /** @type {PDFLib.PDFObject} */ b _val ) {
this . findRefs ( b _val , a _nm ) ;
} . bind ( this ) ) ;
return ;
}
if ( a _val instanceof PDFLib . PDFPage ) {
a _val = a _val . node ;
}
while ( a _val . dict && ! ( a _val instanceof Map ) ) {
a _val = a _val . dict ;
}
if ( a _val instanceof Map ) {
/** @type {Iterator} */
var a _es = a _val . entries ( ) ;
/** @type {IIterableResult<PdfObjEntry>} */
var a _result = a _es . next ( ) ;
while ( ! a _result . done ) {
this . findRefs ( a _result . value [ 1 ] , a _result . value [ 0 ] . encodedName ) ;
a _result = a _es . next ( ) ;
}
return ;
}
}
2022-10-15 15:06:13 +02:00
/ * *
* @ private
* @ param { boolean = } restore
* /
changeAll ( restore ) {
/** @type {Iterator} */
var es = this . entries ( ) ;
/** @type {IIterableResult} */
var result = es . next ( ) ;
while ( ! result . done ) {
result . value [ 1 ] . changeNumber ( restore ) ;
result = es . next ( ) ;
}
}
2022-10-14 14:12:43 +02:00
} ;
/** @type {z.NewRefMap<string, z.NewRef>} */
z . newRefs = new z . NewRefMap ( ) ;
2022-10-02 14:45:06 +02:00
z . PdfSigner = class {
2022-09-17 14:55:09 +02:00
/ * *
* @ param { SignOption } signopt
* /
constructor ( signopt ) {
2022-10-02 14:45:06 +02:00
/** @private @const {string} */
this . DEFAULT _BYTE _RANGE _PLACEHOLDER = "**********" ;
2022-10-20 14:28:27 +02:00
/** @private @const {number} */
this . NEWLINE = 10 ;
2022-09-17 14:55:09 +02:00
/** @private @type {SignOption} */
this . opt = signopt ;
2022-10-06 15:02:11 +02:00
/** @type {forge_key} */
this . privateKey = null ;
/** @type {Array<forge_cert>} */
this . certs = [ ] ;
/** @type {number} */
this . certIdx = 0 ;
2022-10-04 14:37:17 +02:00
/** @private @type {?TsaServiceInfo} */
2022-09-24 06:59:30 +02:00
this . tsainf = null ;
2022-10-02 14:45:06 +02:00
/** @private @type {number} */
this . siglen = 0 ;
/** @private @type {PDFLib.PDFHexString} */
this . sigContents = null ;
2022-10-20 14:28:27 +02:00
/** @private @type {Uint8Array} */
this . oriU8pdf = null ;
/** @type {Array<PdfObjEntry>} */
this . apobjs = [ ] ;
2022-09-24 06:59:30 +02:00
/** @private @type {boolean} */
this . debug = false ;
if ( ! globalThis . PDFLib ) {
throw new Error ( "pdf-lib is not imported." ) ;
}
if ( ! globalThis . forge ) {
throw new Error ( "node-forge is not imported." ) ;
}
if ( signopt . signdate ) {
if ( typeof signopt . signdate == "string" ) {
this . tsainf = {
url : signopt . signdate ,
} ;
} else if ( signopt . signdate . url ) {
2022-10-04 14:37:17 +02:00
this . tsainf = /** @type {TsaServiceInfo} */ ( Object . assign ( { } , signopt . signdate ) ) ;
2022-09-24 06:59:30 +02:00
}
}
if ( this . tsainf ) {
2022-10-22 11:42:13 +02:00
if ( ! z . UrlFetchApp ) {
2022-09-24 06:59:30 +02:00
throw new Error ( "Because of the CORS security restrictions, signing with TSA is not supported in web browser." ) ;
}
2022-10-02 14:45:06 +02:00
if ( z . TSAURLS [ this . tsainf . url ] ) {
Object . assign ( this . tsainf , z . TSAURLS [ this . tsainf . url ] ) ;
2022-09-24 06:59:30 +02:00
} else if ( ! ( new RegExp ( "^https?://" ) ) . test ( this . tsainf . url ) ) {
throw new Error ( "Unknown tsa data. " + JSON . stringify ( this . tsainf ) ) ;
}
if ( ! this . tsainf . len ) {
this . tsainf . len = 16000 ;
}
}
if ( typeof this . opt . debug == "boolean" ) {
this . debug = this . opt . debug ;
} else if ( globalThis . debug ) {
this . debug = true ;
}
2022-09-17 14:55:09 +02:00
}
/ * *
* @ public
2022-09-24 06:59:30 +02:00
* @ param { PDFLib . PDFDocument | Array < number > | Uint8Array | ArrayBuffer | string } pdf
2022-10-02 14:45:06 +02:00
* @ param { EncryptOption = } cypopt
2022-09-18 09:49:56 +02:00
* @ return { Promise < Uint8Array > }
2022-09-17 14:55:09 +02:00
* /
2022-10-02 14:45:06 +02:00
async sign ( pdf , cypopt ) {
if ( cypopt && ! z . PdfCryptor ) {
throw new Error ( "ZgaPdfCryptor is not imported." ) ;
2022-09-17 14:55:09 +02:00
}
2022-10-20 14:28:27 +02:00
/** @const {z.PdfSigner} */
const _this = this ;
2022-10-02 14:45:06 +02:00
/** @type {PDFLib.PDFDocument} */
2022-10-20 14:28:27 +02:00
var pdfdoc = null ;
if ( pdf . addPage ) {
pdfdoc = /** @type {PDFLib.PDFDocument} */ ( pdf ) ;
} else {
if ( Array . isArray ( pdf ) ) {
_this . oriU8pdf = new Uint8Array ( pdf ) ;
} else {
_this . oriU8pdf = PDFLib . toUint8Array ( /** @type {(ArrayBuffer|Uint8Array|string)} */ ( pdf ) ) ;
}
pdfdoc = await PDFLib . PDFDocument . load ( _this . oriU8pdf ) ;
}
2022-10-02 14:45:06 +02:00
2022-10-20 14:28:27 +02:00
if ( _this . opt . drawinf && _this . opt . drawinf . imgData && ! _this . opt . drawinf . img ) {
2022-09-24 06:59:30 +02:00
/** @type {Uint8Array|ArrayBuffer|string} */
var imgData2 = null ;
2022-10-20 14:28:27 +02:00
if ( Array . isArray ( _this . opt . drawinf . imgData ) ) {
imgData2 = new Uint8Array ( _this . opt . drawinf . imgData ) ;
2022-09-24 06:59:30 +02:00
} else {
2022-10-20 14:28:27 +02:00
imgData2 = _this . opt . drawinf . imgData ;
2022-09-24 06:59:30 +02:00
}
2022-10-20 14:28:27 +02:00
if ( _this . opt . drawinf . imgType == "png" ) {
_this . opt . drawinf . img = await pdfdoc . embedPng ( imgData2 ) ;
} else if ( _this . opt . drawinf . imgType == "jpg" ) {
_this . opt . drawinf . img = await pdfdoc . embedJpg ( imgData2 ) ;
2022-09-17 14:55:09 +02:00
} else {
2022-10-20 14:28:27 +02:00
throw new Error ( "Unkown image type. " + _this . opt . drawinf . imgType ) ;
2022-09-17 14:55:09 +02:00
}
}
2022-10-20 14:28:27 +02:00
/** @type {boolean} */ //append mode or not
var apmode = _this . addSignHolder ( pdfdoc ) ;
await pdfdoc . flush ( ) ;
_this . log ( "A signature holder has been added to the pdf." ) ;
2022-09-24 06:59:30 +02:00
2022-10-06 15:02:11 +02:00
/** @type {forge_cert} */
2022-10-20 14:28:27 +02:00
var cert = _this . loadP12cert ( _this . opt . p12cert , _this . opt . pwd ) ;
2022-10-06 15:02:11 +02:00
if ( cert ) {
z . fixCertAttributes ( cert ) ;
}
2022-10-20 14:28:27 +02:00
if ( apmode ) {
if ( _this . oriU8pdf ) {
_this . log ( "The pdf has been signed already, so we add a new signature to it." ) ;
} else {
throw new Error ( "When adding a new signature to a signed pdf, the original literal datas are necessary." ) ;
}
// Find the changed objects
/** @type {PDFLib.PDFDocument} */
var oriPdfdoc = await PDFLib . PDFDocument . load ( _this . oriU8pdf ) ;
pdfdoc . context . enumerateIndirectObjects ( ) . forEach ( function ( /** @type {PdfObjEntry} */ a _ele ) {
/** @type {PDFLib.PDFObject} */
var a _obj = oriPdfdoc . context . lookup ( a _ele [ 0 ] ) ;
if ( ! ( a _obj && _this . isamePdfObject ( a _ele [ 1 ] , a _obj ) ) ) {
_this . apobjs . push ( a _ele ) ;
}
} ) ;
} else {
// If the definitions of references are too chaotic, a signature contains DocMDP or after adding a new signature,
// this signature may be invalid. So we make the order of references more neet.
/** @type {PDFLib.PDFRef} */
var encref = z . newRefs . reorderPdfRefs ( pdfdoc , cypopt ? true : false ) ;
if ( cypopt ) {
if ( cypopt . pubkeys ) {
if ( cypopt . pubkeys . length == 0 ) {
cypopt . pubkeys . push ( {
c : cert ,
} ) ;
} else {
cypopt . pubkeys . forEach ( function ( /** @type {PubKeyInfo} */ a _pubkey ) {
// If there is no c in the PubKeyInfo, set cert to it.
if ( ! a _pubkey . c ) {
a _pubkey . c = cert ;
}
} ) ;
}
2022-10-06 15:02:11 +02:00
}
2022-10-20 14:28:27 +02:00
/** @type {Zga.PdfCryptor} */
var cypt = new z . PdfCryptor ( cypopt ) ;
await cypt . encryptPdf ( pdfdoc , encref ) ;
_this . log ( "Pdf data has been encrypted." ) ;
2022-10-06 15:02:11 +02:00
}
2022-10-02 14:45:06 +02:00
}
2022-09-24 06:59:30 +02:00
/** @type {Uint8Array} */
2022-10-20 14:28:27 +02:00
var ret = await _this . saveAndSign ( pdfdoc ) ;
2022-10-02 14:45:06 +02:00
if ( ! ret ) {
2022-10-20 14:28:27 +02:00
_this . log ( "Change size of signature's placeholder and retry." ) ;
_this . sigContents . value = "0" . repeat ( _this . siglen ) ;
ret = await _this . saveAndSign ( pdfdoc ) ;
2022-10-02 14:45:06 +02:00
}
if ( ret ) {
2022-10-20 14:28:27 +02:00
_this . log ( "Signing pdf accomplished." ) ;
2022-10-02 14:45:06 +02:00
} else {
throw new Error ( "Failed to sign the pdf." ) ;
}
2022-10-14 14:12:43 +02:00
// Because PDFRefs in PDFLib are stored staticly,
// we need to restore all changed PDFRefs
// for preparing the next execution.
if ( z . newRefs . size > 0 ) {
z . newRefs . restoreAll ( ) ;
}
2022-09-24 06:59:30 +02:00
return ret ;
2022-09-17 14:55:09 +02:00
}
/ * *
* @ private
* @ param { PDFLib . PDFDocument } pdfdoc
2022-10-02 14:45:06 +02:00
* @ return { Promise < Uint8Array > }
2022-09-17 14:55:09 +02:00
* /
2022-10-02 14:45:06 +02:00
async saveAndSign ( pdfdoc ) {
/** @type {Uint8Array} */
2022-10-20 14:28:27 +02:00
var uarr = null ;
if ( this . apobjs . length > 0 ) {
uarr = this . appendSignature ( pdfdoc ) ;
} else {
uarr = await pdfdoc . save ( { "useObjectStreams" : false } ) ;
}
2022-10-02 14:45:06 +02:00
/** @type {string} */
2022-10-20 14:28:27 +02:00
var pdfstr = z . u8arrToRaw ( uarr ) + String . fromCharCode ( this . NEWLINE ) ;
2022-10-22 11:42:13 +02:00
return await this . signPdf ( pdfstr ) ;
2022-10-02 14:45:06 +02:00
}
2022-09-17 14:55:09 +02:00
2022-10-02 14:45:06 +02:00
/ * *
* @ private
* @ param { PDFLib . PDFDocument } pdfdoc
2022-10-20 14:28:27 +02:00
* @ return { Uint8Array }
* /
appendSignature ( pdfdoc ) {
/** @const {z.PdfSigner} */
const _this = this ;
/** @type {PDFLib.PDFCrossRefSection} */
var xref = PDFLib . PDFCrossRefSection . create ( ) ;
/** @type {number} */
var stpos = _this . oriU8pdf . length ;
/** @type {Array<number>} */
var buff = [ ] ;
buff [ 0 ] = _this . NEWLINE ;
stpos ++ ;
_this . apobjs . forEach ( function ( /** @type {PdfObjEntry} */ a _ele ) {
/** @type {number} */
var a _len = _this . objEntryToBytes ( a _ele , buff ) ;
xref . addEntry ( a _ele [ 0 ] , stpos ) ;
stpos += a _len ;
} ) ;
xref . copyBytesInto ( buff , buff . length ) ;
/** @type {PDFLib.PDFDict} */
var tdic = PDFLib . PDFWriter . forContext ( pdfdoc . context , 0 ) . createTrailerDict ( ) ;
tdic . set ( PDFLib . PDFName . of ( "Prev" ) , PDFLib . PDFNumber . of ( _this . findPrev ( _this . oriU8pdf ) ) ) ;
/** @type {PDFLib.PDFTrailerDict} */
var ptdic = PDFLib . PDFTrailerDict . of ( tdic ) ;
ptdic . copyBytesInto ( buff , buff . length ) ;
buff . push ( _this . NEWLINE ) ;
/** @type {PDFLib.PDFTrailer} */
var ptlr = PDFLib . PDFTrailer . forLastCrossRefSectionOffset ( stpos ) ;
ptlr . copyBytesInto ( buff , buff . length ) ;
/** @type {Uint8Array} */
var ret = new Uint8Array ( _this . oriU8pdf . length + buff . length ) ;
ret . set ( /** @type {!ArrayBufferView} */ ( _this . oriU8pdf ) ) ;
ret . set ( /** @type {!ArrayBufferView} */ ( new Uint8Array ( buff ) ) , _this . oriU8pdf . length ) ;
return ret ;
}
/ * *
* @ private
* @ param { Uint8Array } u8pdf
* @ return { number }
* /
findPrev ( u8pdf ) {
/** @const {Uint8Array} */
const eof = Zga . rawToU8arr ( "%%EOF" ) ;
/** @const {number} */
const c0 = "0" . charCodeAt ( 0 ) ;
/** @const {number} */
const c9 = "9" . charCodeAt ( 0 ) ;
/** @type {number} */
var step = 0 ;
/** @type {string} */
var num = "" ;
for ( var i = u8pdf . length - eof . length ; i >= 0 ; i -- ) {
switch ( step ) {
case 0 :
/** @type {boolean} */
var flg = true ;
for ( var j = 0 ; j < eof . length ; j ++ ) {
if ( u8pdf [ i + j ] != eof [ j ] ) {
flg = false ;
break ;
}
}
if ( flg ) {
step = 1 ;
}
break ;
case 1 :
if ( u8pdf [ i ] >= c0 && u8pdf [ i ] <= c9 ) {
num = String . fromCharCode ( u8pdf [ i ] ) ;
step = 2 ;
}
break ;
case 2 :
if ( u8pdf [ i ] >= c0 && u8pdf [ i ] <= c9 ) {
num = String . fromCharCode ( u8pdf [ i ] ) + num ;
} else {
step = 9 ;
}
break ;
}
if ( step >= 9 ) {
break ;
}
}
return parseInt ( num , 10 ) ;
}
/ * *
* @ private
* @ param { PDFLib . PDFObject } obj1
* @ param { PDFLib . PDFObject } obj2
* @ return { boolean }
* /
isamePdfObject ( obj1 , obj2 ) {
/** @type {Array<number>} */
var buff1 = [ ] ;
obj1 . copyBytesInto ( buff1 , 0 ) ;
/** @type {Array<number>} */
var buff2 = [ ] ;
obj2 . copyBytesInto ( buff2 , 0 ) ;
if ( buff1 . length != buff2 . length ) {
return false ;
}
for ( var i = 0 ; i < buff1 . length ; i ++ ) {
if ( buff1 [ i ] != buff2 [ i ] ) {
return false ;
}
}
return true ;
}
/ * *
* @ private
* @ param { PdfObjEntry } objety
* @ param { Array < number > } buff
* @ return { number }
* /
objEntryToBytes ( objety , buff ) {
/** @type {number} */
var before = buff . length ;
objety [ 0 ] . copyBytesInto ( buff , buff . length ) ;
PDFLib . copyStringIntoBuffer ( "obj" , buff , buff . length - 1 ) ;
buff [ buff . length ] = this . NEWLINE ;
objety [ 1 ] . copyBytesInto ( buff , buff . length ) ;
buff [ buff . length ] = this . NEWLINE ;
PDFLib . copyStringIntoBuffer ( "endobj" , buff , buff . length ) ;
buff [ buff . length ] = this . NEWLINE ;
return buff . length - before ;
}
/ * *
* @ private
* @ param { PDFLib . PDFDocument } pdfdoc
* @ return { boolean } append mode or not
2022-10-02 14:45:06 +02:00
* /
addSignHolder ( pdfdoc ) {
2022-10-14 14:12:43 +02:00
/** @const {number} */
const docMdp = ( this . opt . permission >= 1 && this . opt . permission <= 3 ) ? this . opt . permission : 0 ;
/** @const {PDFLib.PDFContext} */
const pdfcont = pdfdoc . context ;
/** @const {z.SignatureCreator} */
const signcrt = new z . SignatureCreator ( this . opt . drawinf ) ;
2022-09-17 14:55:09 +02:00
/** @const {PDFLib.PDFPage} */
2022-10-14 14:12:43 +02:00
const page = pdfdoc . getPages ( ) [ signcrt . getPageIndex ( ) ] ;
/** @type {PDFLib.PDFRef} */
var strmRef = signcrt . createStream ( pdfdoc , this . opt . signame ) ;
if ( docMdp && ! strmRef ) {
strmRef = signcrt . createEmptyField ( pdfcont ) ;
}
2022-09-17 14:55:09 +02:00
2022-10-20 14:28:27 +02:00
/** @type {Array<string>} */
var oldSigs = [ ] ;
/** @type {PDFLib.PDFAcroForm} */
var afrm = pdfdoc . catalog . getOrCreateAcroForm ( ) ;
afrm . getAllFields ( ) . forEach ( function ( /** @type {PdfFieldInfo} */ a _finf ) {
if ( a _finf [ 0 ] instanceof PDFLib . PDFAcroSignature ) {
/** @type {PDFLib.PDFString|PDFLib.PDFHexString} */
var a _t = a _finf [ 0 ] . T ( ) ;
if ( a _t instanceof PDFLib . PDFString ) {
oldSigs . push ( a _t . asString ( ) ) ;
} else if ( a _t instanceof PDFLib . PDFHexString ) {
oldSigs . push ( a _t . decodeText ( ) ) ;
}
}
} ) ;
if ( oldSigs . length > 0 && docMdp ) {
throw new Error ( "Since the pdf has been signed, can NOT sign with DocMDP. Because the signature field that contains DocMDP must be the first signed field in the document." ) ;
}
/** @type {string} */
var signm = this . fixSigName ( oldSigs , this . opt . signame ) ;
2022-09-24 06:59:30 +02:00
/** @type {Date} */
var signdate = new Date ( ) ;
if ( this . opt . signdate instanceof Date && ! this . tsainf ) {
signdate = this . opt . signdate ;
2022-09-17 14:55:09 +02:00
}
/** @type {PDFLib.PDFArray} */
2022-10-14 14:12:43 +02:00
var bytrng = new PDFLib . PDFArray ( pdfcont ) ;
2022-09-17 14:55:09 +02:00
bytrng . push ( PDFLib . PDFNumber . of ( 0 ) ) ;
2022-10-02 14:45:06 +02:00
bytrng . push ( PDFLib . PDFName . of ( this . DEFAULT _BYTE _RANGE _PLACEHOLDER ) ) ;
bytrng . push ( PDFLib . PDFName . of ( this . DEFAULT _BYTE _RANGE _PLACEHOLDER ) ) ;
bytrng . push ( PDFLib . PDFName . of ( this . DEFAULT _BYTE _RANGE _PLACEHOLDER ) ) ;
2022-10-04 14:37:17 +02:00
this . siglen = /** @type {number} */ ( this . tsainf ? this . tsainf . len : 3322 ) ;
2022-10-02 14:45:06 +02:00
this . sigContents = PDFLib . PDFHexString . of ( "0" . repeat ( this . siglen ) ) ;
2022-09-17 14:55:09 +02:00
/** @type {Object<string, *>} */
var signObj = {
"Type" : "Sig" ,
"Filter" : "Adobe.PPKLite" ,
"SubFilter" : "adbe.pkcs7.detached" ,
"ByteRange" : bytrng ,
2022-10-02 14:45:06 +02:00
"Contents" : this . sigContents ,
2022-09-24 06:59:30 +02:00
"M" : PDFLib . PDFString . fromDate ( signdate ) ,
2022-10-14 14:12:43 +02:00
"Prop_Build" : pdfcont . obj ( {
"App" : pdfcont . obj ( {
2022-09-18 09:49:56 +02:00
"Name" : "ZgaPdfSinger" ,
2022-09-17 14:55:09 +02:00
} ) ,
} ) ,
} ;
2022-10-14 14:12:43 +02:00
if ( docMdp ) {
/** @type {PDFLib.PDFArray} */
var rfrc = new PDFLib . PDFArray ( pdfcont ) ;
rfrc . push ( pdfcont . obj ( {
"Type" : "SigRef" ,
"TransformMethod" : "DocMDP" ,
"TransformParams" : pdfcont . obj ( {
"Type" : "TransformParams" ,
"P" : docMdp ,
"V" : "1.2" ,
} ) ,
} ) ) ;
signObj [ "Reference" ] = rfrc ;
}
2022-09-17 14:55:09 +02:00
if ( this . opt . reason ) {
2022-09-19 04:53:54 +02:00
signObj [ "Reason" ] = this . convToPDFString ( this . opt . reason ) ;
2022-09-17 14:55:09 +02:00
}
if ( this . opt . location ) {
2022-09-19 04:53:54 +02:00
signObj [ "Location" ] = this . convToPDFString ( this . opt . location ) ;
2022-09-17 14:55:09 +02:00
}
if ( this . opt . contact ) {
2022-09-19 04:53:54 +02:00
signObj [ "ContactInfo" ] = this . convToPDFString ( this . opt . contact ) ;
2022-09-17 14:55:09 +02:00
}
2022-10-14 14:12:43 +02:00
/** @type {PDFLib.PDFRef} */
var signatureDictRef = pdfcont . register ( pdfcont . obj ( signObj ) ) ;
2022-09-17 14:55:09 +02:00
/** @type {Object<string, *>} */
var widgetObj = {
"Type" : "Annot" ,
"Subtype" : "Widget" ,
"FT" : "Sig" ,
2022-10-14 14:12:43 +02:00
"Rect" : signcrt . getSignRect ( ) ,
2022-09-17 14:55:09 +02:00
"V" : signatureDictRef ,
2022-10-20 14:28:27 +02:00
"T" : this . convToPDFString ( signm ) ,
2022-09-17 14:55:09 +02:00
"F" : 132 ,
"P" : page . ref ,
} ;
if ( strmRef ) {
2022-10-14 14:12:43 +02:00
widgetObj [ "AP" ] = pdfcont . obj ( {
2022-09-17 14:55:09 +02:00
"N" : strmRef ,
} ) ;
}
2022-10-14 14:12:43 +02:00
/** @type {PDFLib.PDFRef} */
var widgetDictRef = pdfcont . register ( pdfcont . obj ( widgetObj ) ) ;
2022-09-17 14:55:09 +02:00
// Add our signature widget to the page
2022-10-20 14:28:27 +02:00
/** @type {PDFLib.PDFArray} */
var ans = page . node . Annots ( ) ;
if ( ! ans ) {
ans = new PDFLib . PDFArray ( pdfcont ) ;
page . node . set ( PDFLib . PDFName . Annots , ans ) ;
}
ans . push ( widgetDictRef ) ;
if ( ! afrm . dict . lookup ( PDFLib . PDFName . of ( "SigFlags" ) ) ) {
afrm . dict . set ( PDFLib . PDFName . of ( "SigFlags" ) , PDFLib . PDFNumber . of ( 3 ) ) ;
}
afrm . addField ( widgetDictRef ) ;
2022-10-14 14:12:43 +02:00
if ( docMdp ) {
pdfdoc . catalog . set (
PDFLib . PDFName . of ( "Perms" ) ,
pdfcont . obj ( {
"DocMDP" : signatureDictRef ,
} ) ,
) ;
2022-10-20 14:28:27 +02:00
}
2022-09-17 14:55:09 +02:00
2022-10-20 14:28:27 +02:00
return ( oldSigs . length > 0 ) ;
}
/ * *
* @ private
* @ param { Array < string > } oldSigs
* @ param { string = } signm
* @ param { number = } idx
* @ return { string }
* /
fixSigName ( oldSigs , signm , idx ) {
if ( ! signm ) {
signm = "Signature" ;
idx = 1 ;
}
/** @type {string} */
var nm = signm ;
if ( idx ) {
nm += idx ;
2022-10-14 14:12:43 +02:00
} else {
2022-10-20 14:28:27 +02:00
idx = 0 ;
}
if ( oldSigs . indexOf ( nm ) >= 0 ) {
return this . fixSigName ( oldSigs , signm , idx + 1 ) ;
} else {
return nm ;
2022-10-02 14:45:06 +02:00
}
}
2022-10-06 15:02:11 +02:00
/ * *
* @ private
* @ param { Array < number > | Uint8Array | ArrayBuffer | string } p12cert
* @ param { string } pwd
* @ return { forge _cert }
* /
loadP12cert ( p12cert , pwd ) {
// load P12 certificate
if ( typeof p12cert !== "string" ) {
p12cert = z . u8arrToRaw ( new Uint8Array ( p12cert ) ) ;
}
// Convert Buffer P12 to a forge implementation.
/** @type {forge.asn1} */
var p12Asn1 = forge . asn1 . fromDer ( p12cert ) ;
/** @type {forge.pkcs12} */
var p12 = forge . pkcs12 . pkcs12FromAsn1 ( p12Asn1 , true , pwd ) ;
// Extract safe bags by type.
// We will need all the certificates and the private key.
/** @type {Object<string|number, P12Bag>} */
var certBags = p12 . getBags ( {
"bagType" : forge . pki . oids . certBag ,
} ) [ forge . pki . oids . certBag ] ;
/** @type {Object<string|number, P12Bag>} */
var keyBags = p12 . getBags ( {
"bagType" : forge . pki . oids . pkcs8ShroudedKeyBag ,
} ) [ forge . pki . oids . pkcs8ShroudedKeyBag ] ;
this . privateKey = keyBags [ 0 ] . key ;
if ( certBags ) {
// Get all the certificates (-cacerts & -clcerts)
// Keep track of the last found client certificate.
// This will be the public key that will be bundled in the signature.
Object . keys ( certBags ) . forEach ( function ( a _ele ) {
/** @type {forge_cert} */
var a _cert = certBags [ a _ele ] . cert ;
this . certs . push ( a _cert ) ;
// Try to find the certificate that matches the private key.
if ( this . privateKey . n . compareTo ( a _cert . publicKey . n ) === 0
&& this . privateKey . e . compareTo ( a _cert . publicKey . e ) === 0 ) {
this . certIdx = this . certs . length ;
}
} . bind ( this ) ) ;
}
if ( this . certIdx > 0 ) {
return this . certs [ -- this . certIdx ] ;
// z.fixCertAttributes(this.certs[this.certIdx]);
} else {
throw new Error ( "Failed to find a certificate." ) ;
}
}
2022-09-17 14:55:09 +02:00
/ * *
* @ private
* @ param { string } pdfstr
2022-10-22 11:42:13 +02:00
* @ return { Promise < Uint8Array > }
2022-09-17 14:55:09 +02:00
* /
2022-10-22 11:42:13 +02:00
async signPdf ( pdfstr ) {
2022-09-24 06:59:30 +02:00
/** @type {Date} */
var signdate = new Date ( ) ;
if ( this . opt . signdate instanceof Date && ! this . tsainf ) {
signdate = this . opt . signdate ;
2022-09-17 14:55:09 +02:00
}
// Finds ByteRange information within a given PDF Buffer if one exists
2022-10-04 14:37:17 +02:00
/** @type {Array<string>} */
2022-09-17 14:55:09 +02:00
var byteRangeStrings = pdfstr . match ( /\/ByteRange\s*\[{1}\s*(?:(?:\d*|\/\*{10})\s+){3}(?:\d+|\/\*{10}){1}\s*]{1}/g ) ;
2022-10-04 14:37:17 +02:00
/** @type {string|undefined} */
2022-09-17 14:55:09 +02:00
var byteRangePlaceholder = byteRangeStrings . find ( function ( a _str ) {
2022-10-02 14:45:06 +02:00
return a _str . includes ( "/" + this . DEFAULT _BYTE _RANGE _PLACEHOLDER ) ;
} . bind ( this ) ) ;
2022-09-17 14:55:09 +02:00
if ( ! byteRangePlaceholder ) {
throw new Error ( "no signature placeholder" ) ;
}
2022-10-04 14:37:17 +02:00
/** @type {number} */
2022-09-17 14:55:09 +02:00
var byteRangePos = pdfstr . indexOf ( byteRangePlaceholder ) ;
2022-10-04 14:37:17 +02:00
/** @type {number} */
2022-09-17 14:55:09 +02:00
var byteRangeEnd = byteRangePos + byteRangePlaceholder . length ;
2022-10-04 14:37:17 +02:00
/** @type {number} */
2022-09-17 14:55:09 +02:00
var contentsTagPos = pdfstr . indexOf ( '/Contents ' , byteRangeEnd ) ;
2022-10-04 14:37:17 +02:00
/** @type {number} */
2022-09-17 14:55:09 +02:00
var placeholderPos = pdfstr . indexOf ( '<' , contentsTagPos ) ;
2022-10-04 14:37:17 +02:00
/** @type {number} */
2022-09-17 14:55:09 +02:00
var placeholderEnd = pdfstr . indexOf ( '>' , placeholderPos ) ;
2022-10-04 14:37:17 +02:00
/** @type {number} */
2022-09-17 14:55:09 +02:00
var placeholderLengthWithBrackets = placeholderEnd + 1 - placeholderPos ;
2022-10-04 14:37:17 +02:00
/** @type {number} */
2022-09-17 14:55:09 +02:00
var placeholderLength = placeholderLengthWithBrackets - 2 ;
2022-10-04 14:37:17 +02:00
/** @type {Array<number>} */
2022-09-17 14:55:09 +02:00
var byteRange = [ 0 , 0 , 0 , 0 ] ;
byteRange [ 1 ] = placeholderPos ;
byteRange [ 2 ] = byteRange [ 1 ] + placeholderLengthWithBrackets ;
byteRange [ 3 ] = pdfstr . length - byteRange [ 2 ] ;
2022-10-04 14:37:17 +02:00
/** @type {string} */
2022-09-17 14:55:09 +02:00
var actualByteRange = "/ByteRange [" + byteRange . join ( " " ) + "]" ;
actualByteRange += ' ' . repeat ( byteRangePlaceholder . length - actualByteRange . length ) ;
// Replace the /ByteRange placeholder with the actual ByteRange
pdfstr = pdfstr . slice ( 0 , byteRangePos ) + actualByteRange + pdfstr . slice ( byteRangeEnd ) ;
// Remove the placeholder signature
pdfstr = pdfstr . slice ( 0 , byteRange [ 1 ] ) + pdfstr . slice ( byteRange [ 2 ] , byteRange [ 2 ] + byteRange [ 3 ] ) ;
// Here comes the actual PKCS#7 signing.
2022-10-04 14:37:17 +02:00
/** @type {forge.pkcs7} */
2022-09-17 14:55:09 +02:00
var p7 = forge . pkcs7 . createSignedData ( ) ;
// Start off by setting the content.
p7 . content = forge . util . createBuffer ( pdfstr ) ;
2022-10-06 15:02:11 +02:00
// Add all the certificates (-cacerts & -clcerts) to p7
this . certs . forEach ( function ( a _cert ) {
p7 . addCertificate ( a _cert ) ;
} ) ;
2022-09-17 14:55:09 +02:00
// Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects.
p7 . addSigner ( {
2022-10-06 15:02:11 +02:00
key : this . privateKey ,
certificate : this . certs [ this . certIdx ] ,
2022-09-17 14:55:09 +02:00
digestAlgorithm : forge . pki . oids . sha256 ,
authenticatedAttributes : [
{
"type" : forge . pki . oids . contentType ,
"value" : forge . pki . oids . data ,
} , {
"type" : forge . pki . oids . messageDigest ,
} , {
"type" : forge . pki . oids . signingTime ,
2022-09-24 06:59:30 +02:00
"value" : signdate ,
2022-09-17 14:55:09 +02:00
} ,
] ,
} ) ;
// Sign in detached mode.
p7 . sign ( { "detached" : true } ) ;
2022-09-24 06:59:30 +02:00
if ( this . tsainf ) {
2022-10-04 14:37:17 +02:00
/** @type {forge.asn1} */
2022-10-22 11:42:13 +02:00
var tsatoken = await this . queryTsa ( p7 . signers [ 0 ] . signature ) ;
2022-10-14 14:12:43 +02:00
p7 . signerInfos [ 0 ] . value . push ( tsatoken ) ;
2022-09-24 06:59:30 +02:00
this . log ( "Timestamp from " + this . tsainf . url + " has been added to the signature." ) ;
}
2022-09-17 14:55:09 +02:00
// Check if the PDF has a good enough placeholder to fit the signature.
2022-10-04 14:37:17 +02:00
/** @type {string} */
2022-09-17 14:55:09 +02:00
var sighex = forge . asn1 . toDer ( p7 . toAsn1 ( ) ) . toHex ( ) ;
// placeholderLength represents the length of the HEXified symbols but we're
// checking the actual lengths.
2022-09-24 06:59:30 +02:00
this . log ( "Size of signature is " + sighex . length + "/" + placeholderLength ) ;
2022-09-17 14:55:09 +02:00
if ( sighex . length > placeholderLength ) {
2022-10-02 14:45:06 +02:00
// throw new Error("Signature is too big. Needs: " + sighex.length);
this . siglen = sighex . length ;
return null ;
2022-09-17 14:55:09 +02:00
} else {
// Pad the signature with zeroes so the it is the same length as the placeholder
sighex += "0" . repeat ( placeholderLength - sighex . length ) ;
}
// Place it in the document.
pdfstr = pdfstr . slice ( 0 , byteRange [ 1 ] ) + "<" + sighex + ">" + pdfstr . slice ( byteRange [ 1 ] ) ;
2022-10-02 14:45:06 +02:00
return z . rawToU8arr ( pdfstr ) ;
2022-09-17 14:55:09 +02:00
}
2022-09-19 04:53:54 +02:00
/ * *
* @ private
* @ param { string } str
* @ return { PDFLib . PDFString | PDFLib . PDFHexString }
* /
convToPDFString ( str ) {
// Check if there is a multi-bytes char in the string.
2022-10-04 14:37:17 +02:00
/** @type {boolean} */
2022-09-19 04:53:54 +02:00
var flg = false ;
for ( var i = 0 ; i < str . length ; i ++ ) {
if ( str . charCodeAt ( i ) > 0xFF ) {
flg = true ;
break ;
}
}
if ( flg ) {
return PDFLib . PDFHexString . fromText ( str ) ;
} else {
return PDFLib . PDFString . of ( str ) ;
}
}
2022-09-24 06:59:30 +02:00
/ * *
* @ private
2022-10-04 14:37:17 +02:00
* @ param { string = } signature
2022-09-24 06:59:30 +02:00
* @ return { string }
* /
genTsrData ( signature ) {
// Generate SHA256 hash from signature content for TSA
2022-10-04 14:37:17 +02:00
/** @type {forge.md.digest} */
2022-09-24 06:59:30 +02:00
var md = forge . md . sha256 . create ( ) ;
md . update ( signature ) ;
// Generate TSA request
2022-10-04 14:37:17 +02:00
/** @type {forge.asn1} */
2022-09-24 06:59:30 +02:00
var asn1Req = forge . asn1 . create (
forge . asn1 . Class . UNIVERSAL ,
forge . asn1 . Type . SEQUENCE ,
true ,
[
// Version
{
composed : false ,
constructed : false ,
tagClass : forge . asn1 . Class . UNIVERSAL ,
type : forge . asn1 . Type . INTEGER ,
value : forge . asn1 . integerToDer ( 1 ) . data ,
} ,
{
composed : true ,
constructed : true ,
tagClass : forge . asn1 . Class . UNIVERSAL ,
type : forge . asn1 . Type . SEQUENCE ,
value : [
{
composed : true ,
constructed : true ,
tagClass : forge . asn1 . Class . UNIVERSAL ,
type : forge . asn1 . Type . SEQUENCE ,
value : [
{
composed : false ,
constructed : false ,
tagClass : forge . asn1 . Class . UNIVERSAL ,
type : forge . asn1 . Type . OID ,
value : forge . asn1 . oidToDer ( forge . oids . sha256 ) . data ,
} , {
composed : false ,
constructed : false ,
tagClass : forge . asn1 . Class . UNIVERSAL ,
type : forge . asn1 . Type . NULL ,
value : ""
}
]
} , { // Message imprint
composed : false ,
constructed : false ,
tagClass : forge . asn1 . Class . UNIVERSAL ,
type : forge . asn1 . Type . OCTETSTRING ,
value : md . digest ( ) . data ,
}
]
} , {
composed : false ,
constructed : false ,
tagClass : forge . asn1 . Class . UNIVERSAL ,
type : forge . asn1 . Type . BOOLEAN ,
value : 1 , // Get REQ certificates
}
]
) ;
return forge . asn1 . toDer ( asn1Req ) . data ;
}
/ * *
* @ private
2022-10-04 14:37:17 +02:00
* @ param { string = } signature
2022-10-22 11:42:13 +02:00
* @ return { Promise < forge . asn1 > }
2022-09-24 06:59:30 +02:00
* /
2022-10-22 11:42:13 +02:00
async queryTsa ( signature ) {
2022-10-14 14:12:43 +02:00
/** @lends {forge.asn1} */
var asn1 = forge . asn1 ;
2022-10-04 14:37:17 +02:00
/** @type {string} */
2022-09-24 06:59:30 +02:00
var tsr = this . genTsrData ( signature ) ;
2022-10-04 14:37:17 +02:00
/** @type {Uint8Array} */
2022-10-02 14:45:06 +02:00
var tu8s = z . rawToU8arr ( tsr ) ;
2022-10-04 14:37:17 +02:00
/** @type {UrlFetchParams} */
2022-09-24 06:59:30 +02:00
var options = {
"method" : "POST" ,
"headers" : { "Content-Type" : "application/timestamp-query" } ,
"payload" : tu8s ,
} ;
2022-10-22 11:42:13 +02:00
/** @type {Uint8Array} */
var tesp = await z . UrlFetchApp . fetch ( this . tsainf . url , options ) ;
2022-10-04 14:37:17 +02:00
/** @type {string} */
2022-10-22 11:42:13 +02:00
var tstr = z . u8arrToRaw ( tesp ) ;
2022-10-04 14:37:17 +02:00
/** @type {forge.asn1} */
2022-10-14 14:12:43 +02:00
var token = asn1 . fromDer ( tstr ) . value [ 1 ] ;
// create the asn1 to append to the signature
/** @type {string} */ //forge.pki.oids.timeStampToken
var typstr = asn1 . oidToDer ( "1.2.840.113549.1.9.16.2.14" ) . getBytes ( ) ;
return asn1 . create ( asn1 . Class . CONTEXT _SPECIFIC , 1 , true , [
asn1 . create ( asn1 . Class . UNIVERSAL , asn1 . Type . SEQUENCE , true , [
// Attribute Type
asn1 . create ( asn1 . Class . UNIVERSAL , asn1 . Type . OID , false , typstr ) ,
// Attribute Value
asn1 . create ( asn1 . Class . UNIVERSAL , asn1 . Type . SET , true , [ token ] ) ,
] ) ,
] ) ;
2022-09-24 06:59:30 +02:00
}
/ * *
* @ private
* @ param { string } msg
* /
log ( msg ) {
if ( this . debug ) {
console . log ( msg ) ;
}
}
2022-10-02 14:45:06 +02:00
} ;
2022-09-17 14:55:09 +02:00
2022-10-14 14:12:43 +02:00
z . SignatureCreator = class {
2022-09-17 14:55:09 +02:00
/ * *
* @ param { SignDrawInfo = } drawinf
* /
constructor ( drawinf ) {
/** @private @type {number} */
this . pgidx = 0 ;
/** @private @type {Array<number>} */
this . rect = [ 0 , 0 , 0 , 0 ] ;
2022-10-04 14:37:17 +02:00
/** @private @type {?SignDrawInfo} */
2022-09-17 14:55:09 +02:00
this . drawinf = null ;
if ( drawinf ) {
this . drawinf = drawinf ;
if ( this . drawinf . pageidx ) {
this . pgidx = this . drawinf . pageidx ;
}
}
}
/ * *
* @ public
* @ return { number }
* /
getPageIndex ( ) {
return this . pgidx ;
}
/ * *
* @ public
* @ return { Array < number > }
* /
getSignRect ( ) {
return this . rect ;
}
2022-10-14 14:12:43 +02:00
/ * *
* @ public
* @ param { PDFLib . PDFContext } pdfcont
* @ return { PDFLib . PDFRef }
* /
createEmptyField ( pdfcont ) {
return pdfcont . register ( pdfcont . obj ( {
"Type" : "XObject" ,
"Subtype" : "Form" ,
"FormType" : 1 ,
"BBox" : [ 0 , 0 , 0 , 0 ] ,
} ) ) ;
}
2022-09-17 14:55:09 +02:00
/ * *
* @ public
* @ param { PDFLib . PDFDocument } pdfdoc
* @ param { string = } signame
* @ return { PDFLib . PDFRef } The unique reference assigned to the signature stream
* /
createStream ( pdfdoc , signame ) {
if ( ! this . drawinf ) {
return null ;
} else if ( ! ( this . drawinf . img || ( this . drawinf . font && this . drawinf . font ) ) ) {
return null ;
}
/** @type {Array<PDFLib.PDFPage>} */
var pages = pdfdoc . getPages ( ) ;
/** @type {PDFLib.PDFPage} */
var page = null ;
if ( this . pgidx < pages . length ) {
page = pages [ this . pgidx ] ;
} else {
throw new Error ( "Page index is overflow to pdf pages." ) ;
}
2022-10-04 14:37:17 +02:00
/** @type {PDFLib.Rotation} */
2022-09-17 14:55:09 +02:00
var pgrot = page . getRotation ( ) ;
pgrot . angle = PDFLib . toDegrees ( pgrot ) % 360 ;
pgrot . type = PDFLib . RotationTypes . Degrees ;
2022-10-04 14:37:17 +02:00
/** @type {PdfSize} */
2022-09-17 14:55:09 +02:00
var pgsz = page . getSize ( ) ;
2022-10-04 14:37:17 +02:00
/** @type {SignAreaInfo} */
2022-09-17 14:55:09 +02:00
var areainf = this . calcAreaInf ( pgsz , pgrot . angle , this . drawinf . area ) ;
// resources object
2022-10-04 14:37:17 +02:00
/** @type {Object<string, *>} */
2022-09-17 14:55:09 +02:00
var rscObj = { } ;
/** @type {Array<PDFLib.PDFOperator>} */
var sigOprs = [ ] ;
2022-10-04 14:37:17 +02:00
/** @type {string} */
2022-09-17 14:55:09 +02:00
var imgName = signame ? signame . concat ( "Img" ) : "SigImg" ;
2022-10-04 14:37:17 +02:00
/** @type {string} */
2022-09-17 14:55:09 +02:00
var fontName = signame ? signame . concat ( "Font" ) : "SigFont" ;
if ( this . drawinf . img ) {
// Get scaled image size
2022-10-04 14:37:17 +02:00
/** @type {PdfSize} */
2022-09-17 14:55:09 +02:00
var imgsz = this . drawinf . img . size ( ) ;
2022-10-04 14:37:17 +02:00
/** @type {number} */
2022-09-17 14:55:09 +02:00
var tmp = areainf . w * imgsz . height / imgsz . width ;
if ( tmp <= areainf . h ) {
areainf . h = tmp ;
} else {
areainf . w = areainf . h * imgsz . width / imgsz . height ;
}
rscObj [ "XObject" ] = {
[ imgName ] : this . drawinf . img . ref ,
} ;
sigOprs = sigOprs . concat ( PDFLib . drawImage ( imgName , this . calcDrawImgInf ( pgrot , areainf ) ) ) ;
}
if ( this . drawinf . font ) {
rscObj [ "Font" ] = {
[ fontName ] : this . drawinf . font . ref ,
} ;
}
this . rect = this . calcRect ( pgrot . angle , areainf ) ;
2022-10-20 14:28:27 +02:00
var frmDict = /** @type {PDFLib.PDFDict} */ ( pdfdoc . context . obj ( {
2022-09-17 14:55:09 +02:00
"Type" : "XObject" ,
"Subtype" : "Form" ,
"FormType" : 1 ,
"BBox" : [ 0 , 0 , areainf . w , areainf . h ] ,
"Resources" : rscObj ,
2022-10-20 14:28:27 +02:00
} ) ) ;
2022-10-04 14:37:17 +02:00
/** @type {PDFLib.PDFContentStream} */
2022-10-14 14:12:43 +02:00
var strm = PDFLib . PDFContentStream . of ( frmDict , sigOprs , true ) ;
2022-09-17 14:55:09 +02:00
return pdfdoc . context . register ( strm ) ;
}
/ * *
* Calculate area informations for drawing signature after rotate
*
* @ private
2022-10-04 14:37:17 +02:00
* @ param { PdfSize } pgsz
2022-09-17 14:55:09 +02:00
* @ param { number } angle
* @ param { SignAreaInfo } visinf
* @ return { SignAreaInfo }
* /
calcAreaInf ( pgsz , angle , visinf ) {
2022-10-04 14:37:17 +02:00
var ret = /** @type {SignAreaInfo} */ ( Object . assign ( { } , visinf ) ) ;
2022-09-17 14:55:09 +02:00
// Calculate position after rotate
switch ( angle ) {
case 90 :
ret . w = visinf . h ;
ret . h = visinf . w ;
2022-10-01 10:20:53 +02:00
ret . x = visinf . y + visinf . h ;
2022-09-17 14:55:09 +02:00
ret . y = visinf . x ;
break ;
case 180 :
case - 180 :
ret . x = pgsz . width - visinf . x ;
2022-10-01 10:20:53 +02:00
ret . y = visinf . y + visinf . h ;
2022-09-17 14:55:09 +02:00
break ;
case 270 :
case - 90 :
ret . w = visinf . h ;
ret . h = visinf . w ;
2022-10-01 10:20:53 +02:00
ret . x = pgsz . width - visinf . y - visinf . h ;
2022-09-17 14:55:09 +02:00
ret . y = pgsz . height - visinf . x ;
break ;
default :
2022-10-01 10:20:53 +02:00
ret . y = pgsz . height - visinf . y - visinf . h ;
2022-09-17 14:55:09 +02:00
}
return ret ;
}
/ * *
* @ private
* @ param { number } angle
* @ param { SignAreaInfo } areainf // { x, y, w, h }
* @ return { Array < number > }
* /
calcRect ( angle , areainf ) {
2022-10-04 14:37:17 +02:00
/** @type {Array<number>} */
2022-09-17 14:55:09 +02:00
var rect = [ 0 , 0 , 0 , 0 ] ;
rect [ 0 ] = areainf . x ;
rect [ 1 ] = areainf . y ;
switch ( angle ) {
case 90 :
rect [ 2 ] = areainf . x - areainf . h ;
rect [ 3 ] = areainf . y + areainf . w ;
break ;
case 180 :
case - 180 :
rect [ 2 ] = areainf . x - areainf . w ;
rect [ 3 ] = areainf . y - areainf . h ;
break ;
case 270 :
case - 90 :
rect [ 2 ] = areainf . x + areainf . h ;
rect [ 3 ] = areainf . y - areainf . w ;
break ;
default :
rect [ 2 ] = areainf . x + areainf . w ;
rect [ 3 ] = areainf . y + areainf . h ;
}
return rect ;
}
/ * *
* Calculate informations for drawing image after rotate
*
* @ private
* @ param { PDFLib . Rotation } rot
* @ param { SignAreaInfo } areainf // { x, y, w, h }
2022-10-04 14:37:17 +02:00
* @ return { PdfDrawimgOption }
2022-09-17 14:55:09 +02:00
* /
calcDrawImgInf ( rot , areainf ) {
2022-10-04 14:37:17 +02:00
/** @type {PdfDrawimgOption} */
2022-09-17 14:55:09 +02:00
var ret = {
"x" : 0 ,
"y" : 0 ,
"width" : areainf . w ,
"height" : areainf . h ,
"rotate" : rot ,
"xSkew" : PDFLib . degrees ( 0 ) ,
"ySkew" : PDFLib . degrees ( 0 ) ,
} ;
switch ( rot . angle ) {
case 90 :
ret [ "x" ] = areainf . w ;
ret [ "width" ] = areainf . h ;
ret [ "height" ] = areainf . w ;
break ;
case 180 :
case - 180 :
ret [ "x" ] = areainf . w ;
ret [ "y" ] = areainf . h ;
break ;
case 270 :
case - 90 :
ret [ "y" ] = areainf . h ;
ret [ "width" ] = areainf . h ;
ret [ "height" ] = areainf . w ;
break ;
}
return ret ;
}
2022-10-02 14:45:06 +02:00
} ;
2022-09-18 09:49:56 +02:00
2022-10-02 14:45:06 +02:00
}
2022-09-18 09:49:56 +02:00
2022-10-02 14:45:06 +02:00
if ( ! globalThis . Zga ) {
globalThis . Zga = { } ;
}
supplyZgaSigner ( globalThis . Zga ) ;