@@ -13,6 +13,7 @@ import {
1313} from 'pdfjs-dist/lib/shared/util' ;
1414
1515import { deflate } from 'pako' ;
16+ import * as queue from 'promise-queue' ;
1617
1718export type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array |
1819 Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array ;
@@ -21,52 +22,57 @@ export type BinaryFile = Blob | File | ArrayBuffer | TypedArray;
2122
2223export class PDFAssembler {
2324 pdfManager : any = null ;
25+ userPassword = '' ;
26+ ownerPassword = '' ;
2427 nextNodeNum = 1 ;
2528 pdfTree : any = Object . create ( null ) ;
2629 recoveryMode = false ;
2730 objCache : any = Object . create ( null ) ;
2831 objCacheQueue : any = Object . create ( null ) ;
29- promiseQueue : Promise < any > = Promise . resolve ( true ) ;
32+ pdfManagerArrays = [ ] ;
33+ pdfAssemblerArrays = [ ] ;
34+ promiseQueue : any = new queue ( 1 ) ;
3035 indent : boolean | string | number = false ;
3136 compress = true ;
3237 encrypt = false ; // not yet implemented
3338 groupPages = true ;
3439 pageGroupSize = 16 ;
3540 pdfVersion = '1.7' ;
3641
37- constructor ( inputData ?: BinaryFile | Object ) {
42+ constructor ( inputData ?: BinaryFile | Object , userPassword = '' ) {
43+ if ( userPassword . length ) { this . userPassword = userPassword ; }
3844 if ( typeof inputData === 'object' ) {
3945 if ( inputData instanceof Blob || inputData instanceof ArrayBuffer || inputData instanceof Uint8Array ) {
40- this . promiseQueue = this . toArrayBuffer ( inputData )
41- . then ( arrayBuffer => this . pdfManager = new LocalPdfManager ( 1 , arrayBuffer , '' , { } , '' ) )
46+ this . promiseQueue . add ( ( ) => this . toArrayBuffer ( inputData )
47+ . then ( arrayBuffer => this . pdfManager = new LocalPdfManager ( 1 , arrayBuffer , userPassword , { } , '' ) )
4248 . then ( ( ) => this . pdfManager . ensureDoc ( 'checkHeader' , [ ] ) )
4349 . then ( ( ) => this . pdfManager . ensureDoc ( 'parseStartXRef' , [ ] ) )
4450 . then ( ( ) => this . pdfManager . ensureDoc ( 'parse' , [ this . recoveryMode ] ) )
4551 . then ( ( ) => this . pdfManager . ensureDoc ( 'numPages' ) )
4652 . then ( ( ) => this . pdfManager . ensureDoc ( 'fingerprint' ) )
47- . then ( ( ) => this . pdfTree [ '/Root' ] = this . resolveNodeRefs ( ) )
4853 . then ( ( ) => {
54+ this . pdfTree [ '/Root' ] = this . resolveNodeRefs ( ) ;
4955 const infoDict = new Dict ( ) ;
5056 infoDict . _map = this . pdfManager . pdfDocument . documentInfo ;
5157 this . pdfTree [ '/Info' ] = this . resolveNodeRefs ( infoDict ) ;
5258 delete this . pdfTree [ '/Info' ] [ '/IsAcroFormPresent' ] ;
5359 delete this . pdfTree [ '/Info' ] [ '/IsXFAPresent' ] ;
5460 delete this . pdfTree [ '/Info' ] [ '/PDFFormatVersion' ] ;
55- this . pdfTree [ '/Info' ] [ '/Producer' ] = '(pdfAssembler — www.pdfcircus.com)' ;
61+ this . pdfTree [ '/Info' ] [ '/Producer' ] = '(PDF Assembler — www.pdfcircus.com)' ;
5662 this . pdfTree [ '/Info' ] [ '/ModDate' ] = '(' + this . toPdfDate ( ) + ')' ;
63+ this . flattenPageTree ( ) ;
5764 } )
58- . then ( ( ) => this . flattenPageTree ( ) ) ;
65+ ) ;
5966 } else {
6067 this . pdfTree = inputData ;
6168 }
6269 } else {
6370 this . pdfTree = {
6471 'documentInfo' : { } ,
6572 '/Info' : {
66- '/Producer' : '(pdfAssembler ( www.pdfcircus.com) )' ,
73+ '/Producer' : '(PDF Assembler — www.pdfcircus.com)' ,
6774 '/CreationDate' : '(' + this . toPdfDate ( ) + ')' ,
6875 '/ModDate' : '(' + this . toPdfDate ( ) + ')' ,
69- '/Trapped' : '/False' ,
7076 } ,
7177 '/Root' : {
7278 '/Type' : '/Catalog' ,
@@ -89,16 +95,15 @@ export class PDFAssembler {
8995 }
9096
9197 get pdfDocument ( ) : Promise < PDFDocument > {
92- return this . promiseQueue . then ( ( ) => this . pdfManager && this . pdfManager . pdfDocument ) ;
98+ return this . promiseQueue . add ( ( ) => Promise . resolve ( this . pdfManager && this . pdfManager . pdfDocument ) ) ;
9399 }
94100
95101 get numPages ( ) : Promise < number > {
96- return this . promiseQueue . then ( ( ) => this . pdfTree [ '/Root' ] [ '/Pages' ] [ '/Count' ] ) ;
97- // this.pdfManager && this.pdfManager.pdfDocument && this.pdfManager.pdfDocument.numPages
102+ return this . promiseQueue . add ( ( ) => Promise . resolve ( this . pdfTree [ '/Root' ] [ '/Pages' ] [ '/Count' ] ) ) ;
98103 }
99104
100105 get pdfObject ( ) {
101- return this . promiseQueue . then ( ( ) => this . pdfTree ) ;
106+ return this . promiseQueue . add ( ( ) => Promise . resolve ( this . pdfTree ) ) ;
102107 }
103108
104109 toArrayBuffer ( file : BinaryFile ) : Promise < ArrayBuffer > {
@@ -155,11 +160,18 @@ export class PDFAssembler {
155160 } else if ( typeof node === 'string' ) {
156161 return `(${ node } )` ;
157162 } else if ( node instanceof Array ) {
158- const arrayNode = [ ] ;
159- node . forEach ( ( element , index ) => arrayNode . push (
160- this . resolveNodeRefs ( element , index , arrayNode , contents )
161- ) ) ;
162- return arrayNode ;
163+ const existingArrayIndex = this . pdfManagerArrays . indexOf ( node ) ;
164+ if ( existingArrayIndex > - 1 ) {
165+ return this . pdfAssemblerArrays [ existingArrayIndex ] ;
166+ } else {
167+ const newArrayNode = [ ] ;
168+ this . pdfManagerArrays . push ( node ) ;
169+ this . pdfAssemblerArrays . push ( newArrayNode ) ;
170+ node . forEach ( ( element , index ) => newArrayNode . push (
171+ this . resolveNodeRefs ( element , index , newArrayNode , contents )
172+ ) ) ;
173+ return newArrayNode ;
174+ }
163175 } else if ( typeof node === 'object' && node !== null ) {
164176 const objectNode : any = Object . create ( null ) ;
165177 let source = null ;
@@ -183,16 +195,28 @@ export class PDFAssembler {
183195 }
184196 }
185197 if ( ! objectNode . stream ) {
186- const checkStream = ( streamSource ) => {
187- if ( streamSource instanceof Stream || streamSource instanceof DecryptStream ) {
188- source = streamSource ;
198+ for ( const checkSource of [
199+ node , node . stream , node . stream && node . stream . str ,
200+ node . str , node . str && node . str . str
201+ ] ) {
202+ if ( checkSource instanceof Stream || checkSource instanceof DecryptStream ) {
203+ source = checkSource ;
204+ break ;
189205 }
190- } ;
191- if ( ! source ) { checkStream ( node ) ; }
192- if ( ! source ) { checkStream ( node . stream ) ; }
193- if ( ! source ) { checkStream ( node . stream && node . stream . str ) ; }
194- if ( ! source ) { checkStream ( node . str ) ; }
195- if ( ! source ) { checkStream ( node . str && node . str . str ) ; }
206+ }
207+ // const checkStream = (streamSource) => {
208+ // if (!source && (
209+ // streamSource instanceof Stream ||
210+ // streamSource instanceof DecryptStream
211+ // )) {
212+ // source = streamSource;
213+ // }
214+ // };
215+ // checkStream(node);
216+ // checkStream(node.stream);
217+ // checkStream(node.stream && node.stream.str);
218+ // checkStream(node.str);
219+ // checkStream(node.str && node.str.str);
196220 if ( source ) {
197221 source . reset ( ) ;
198222 objectNode . stream = source . getBytes ( ) ;
@@ -203,17 +227,8 @@ export class PDFAssembler {
203227 if ( contents || objectNode [ '/Subtype' ] === '/XML' ||
204228 ( objectNode . stream && objectNode . stream . every ( byte => byte < 128 ) )
205229 ) {
206- // if (contents) { objectNode.contents = objectNode.stream; }
207- // TODO: remove unneeded spaces in command streams
208- // (but NOT in text strings inside command streams)
209- // --or-- split command streams into command arrays?
230+ // TODO: split command stream into array of commands?
210231 objectNode . stream = bytesToString ( objectNode . stream ) ;
211- // .replace(/\s*\n\s*/g, '\n').replace(/\s*\r\s*/g, '\n')
212- // .replace(/\s\s+/g, ' ').replace(/\s\s+/g, ' ').replace(/\s*\/\s*/g, '/')
213- // .replace(/\s*\(\s*/g, '(').replace(/\s*\)\s*/g, ')')
214- // .replace(/\s*\[\s*/g, '[').replace(/\s*\]\s*/g, ']')
215- // .replace(/\s*\{\s*/g, '{').replace(/\s*\}\s*/g, ' }')
216- // .replace(/[ \t\v\f]*<\s*/g, '<').replace(/\s*>[ \t\v\f]*/g, '>');
217232 }
218233 delete objectNode [ '/Length' ] ;
219234 }
@@ -364,7 +379,7 @@ export class PDFAssembler {
364379 }
365380
366381 assemblePdf ( nameOrOutputFormat = 'output.pdf' ) : Promise < File | ArrayBuffer | Uint8Array > {
367- return this . promiseQueue = this . promiseQueue . then ( ( ) => {
382+ return this . promiseQueue . add ( ( ) => new Promise ( ( resolve , reject ) => {
368383 const stringByteMap = [ // encodes string chars by byte code
369384 '\\000' , '\\001' , '\\002' , '\\003' , '\\004' , '\\005' , '\\006' , '\\007' ,
370385 '\\b' , '\\t' , '\\n' , '\\013' , '\\f' , '\\r' , '\\016' , '\\017' ,
@@ -390,8 +405,10 @@ export class PDFAssembler {
390405 const space = ! this . indent ? '' :
391406 typeof this . indent === 'number' ? ' ' . repeat ( this . indent ) :
392407 typeof this . indent === 'string' ? this . indent :
393- '\t' ; // this.indent = truthy
394- const newline = ! this . indent ? '' : '\n' ;
408+ '\t' ; // if this.indent == truthy
409+ // const newline = !this.indent ? '' : '\n';
410+ const newline = '\n' ;
411+ // TODO: If no indent, break lines longer than 255 characters
395412 this . flattenPageTree ( ) ;
396413 this . groupPageTree ( ) ;
397414 this . resetObjectIds ( ) ;
@@ -402,15 +419,15 @@ export class PDFAssembler {
402419 if ( nextIndent === true ) { nextIndent = newline + space . repeat ( depth ) ; }
403420 let pdfObject = '' ;
404421
405- // detect and encode names and strings
422+ // detect and encode name or string
406423 if ( typeof jsObject === 'string' ) {
407424 const firstChar = jsObject [ 0 ] , lastChar = jsObject [ jsObject . length - 1 ] ;
408- if ( firstChar === '/' ) {
425+ if ( firstChar === '/' ) { // name
409426 // encode name chars: NUL, TAB, LF, FF, CR, space, #, %, (, ), /, <, >, [, ], {, }
410427 const encodeChar = ( char : string ) => '\0\t\n\f\r #%()/<>[]{}' . indexOf ( char ) === - 1 ?
411428 char : `#${ `0${ char . charCodeAt ( 0 ) . toString ( 16 ) } ` . slice ( - 2 ) } ` ;
412429 pdfObject = `/${ jsObject . slice ( 1 ) . replace ( / ./ g, encodeChar ) } ` ;
413- } else if ( firstChar === '(' && lastChar === ')' ) {
430+ } else if ( firstChar === '(' && lastChar === ')' ) { // string
414431 const byteArray = Array . from ( arraysToBytes ( jsObject . slice ( 1 , - 1 ) ) ) ;
415432 const stringEncode = byteArray . map ( ( byte : number ) => stringByteMap [ byte ] ) . join ( '' ) ;
416433 if ( stringEncode . length < byteArray . length * 2 ) {
@@ -423,7 +440,7 @@ export class PDFAssembler {
423440 pdfObject = jsObject ;
424441 }
425442
426- // convert true, false, and null to string
443+ // convert true, false, null, or number to string
427444 } else if ( typeof jsObject !== 'object' || jsObject === null ) {
428445 pdfObject = jsObject === null || jsObject === undefined ? 'null' :
429446 jsObject === true ? 'true' :
@@ -499,7 +516,7 @@ export class PDFAssembler {
499516 // if nextIndent is set, indent item
500517 nextIndent ? nextIndent :
501518 // otherwise, check if item is first in an array, or starts with a delimiter character
502- // if not (nextIndent = ''), add a space to separate it from the previous item
519+ // if not (if nextIndent = ''), add a space to separate it from the previous item
503520 nextIndent === false || [ '/' , '[' , '(' , '<' ] . includes ( pdfObject [ 0 ] ) ? '' : ' ' ;
504521 return prefix + pdfObject ;
505522 } ;
@@ -531,13 +548,13 @@ export class PDFAssembler {
531548 `%%EOF\n` ;
532549 const pdfData = arraysToBytes ( [ header , ...indirectObjects . filter ( o => o ) , xref , trailer ] ) ;
533550 switch ( nameOrOutputFormat ) {
534- case 'ArrayBuffer' : return pdfData . buffer ;
535- case 'Uint8Array' : return pdfData ;
551+ case 'ArrayBuffer' : resolve ( pdfData . buffer ) ; break ;
552+ case 'Uint8Array' : resolve ( pdfData ) ; break ;
536553 default :
537554 if ( nameOrOutputFormat . slice ( - 4 ) !== '.pdf' ) { nameOrOutputFormat += '.pdf' ; }
538- return new File ( [ pdfData ] , nameOrOutputFormat , { type : 'application/pdf' } ) ;
555+ resolve ( new File ( [ pdfData ] , nameOrOutputFormat , { type : 'application/pdf' } ) ) ;
539556 }
540- } ) ;
557+ } ) ) ;
541558 }
542559
543560 // utility functions from js.pdf:
0 commit comments