3030; ( function ( jsPDFAPI ) {
3131'use strict'
3232
33- var namespace = 'addImage_'
33+ var namespace = 'addImage_' ,
34+ supported_image_types = [ 'jpg' , 'jpeg' , 'png' ] ,
35+ color_spaces = [ 'DeviceRGB' , 'DeviceGray' , 'DeviceCMYK' , 'Indexed' ] ,
36+ filter_methods = [ 'DCTDecode' , 'FlateDecode' , 'LZWDecode' ] ;
37+
3438
3539// takes a string imgData containing the raw bytes of
3640// a jpeg image and returns [width, height]
@@ -48,7 +52,7 @@ var getJpegSize = function(imgData) {
4852 ! imgData . charCodeAt ( 8 ) === 'I' . charCodeAt ( 0 ) ||
4953 ! imgData . charCodeAt ( 9 ) === 'F' . charCodeAt ( 0 ) ||
5054 ! imgData . charCodeAt ( 10 ) === 0x00 ) {
51- throw new Error ( 'getJpegSize requires a binary jpeg file' )
55+ throw new Error ( 'getJpegSize requires a binary string jpeg file' )
5256 }
5357 var blockLength = imgData . charCodeAt ( 4 ) * 256 + imgData . charCodeAt ( 5 ) ;
5458 var i = 4 , len = imgData . length ;
@@ -76,6 +80,7 @@ var getJpegSize = function(imgData) {
7680}
7781// Image functionality ported from pdf.js
7882, putImage = function ( img ) {
83+ //console.log(img);
7984 var objectNumber = this . internal . newObject ( )
8085 , out = this . internal . write
8186 , putStream = this . internal . putStream
@@ -106,7 +111,7 @@ var getJpegSize = function(imgData) {
106111 if ( 'trns' in img && img [ 'trns' ] . constructor == Array ) {
107112 var trns = '' ;
108113 for ( var i = 0 ; i < img [ 'trns' ] . length ; i ++ ) {
109- trns += ( img [ trns ] [ i ] + ' ' + img [ 'trns' ] [ i ] + ' ' ) ;
114+ trns += ( img [ ' trns' ] [ i ] + ' ' + img [ 'trns' ] [ i ] + ' ' ) ;
110115 out ( '/Mask [' + trns + ']' ) ;
111116 }
112117 }
@@ -118,6 +123,26 @@ var getJpegSize = function(imgData) {
118123 putStream ( img [ 'data' ] ) ;
119124
120125 out ( 'endobj' ) ;
126+
127+ // Soft mask
128+ if ( 'smask' in img ) {
129+ var dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' + img [ 'w' ] ;
130+ var smask = { 'w' : img [ 'w' ] , 'h' : img [ 'h' ] , 'cs' : 'DeviceGray' , 'bpc' : 8 , 'dp' : dp , 'data' : img [ 'smask' ] } ;
131+ if ( 'f' in img )
132+ smask . f = img [ 'f' ] ;
133+ putImage . call ( this , smask ) ;
134+ }
135+
136+ //Palette
137+ if ( img [ 'cs' ] === 'Indexed' ) {
138+
139+ this . internal . newObject ( ) ;
140+ //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
141+ //putStream(zlib.compress(img['pal']));
142+ out ( '<< /Length ' + img [ 'pal' ] . length + '>>' ) ;
143+ putStream ( img [ 'pal' ] ) ;
144+ out ( 'endobj' ) ;
145+ }
121146}
122147, putResourcesCallback = function ( ) {
123148 var images = this . internal . collections [ namespace + 'images' ]
@@ -139,24 +164,118 @@ var getJpegSize = function(imgData) {
139164 )
140165 }
141166}
167+ , extractBase64Info = function ( dataURL ) {
168+ return / ^ ( d a t a : i m a g e \/ ( [ \w ] + ?) ; b a s e 6 4 , ) ( .+ ?) $ / g. exec ( dataURL ) ;
169+ }
170+ , supportsArrayBuffer = function ( ) {
171+ return typeof ArrayBuffer == 'function' ;
172+ }
173+ , isArrayBuffer = function ( object ) {
174+ if ( ! supportsArrayBuffer ( ) )
175+ return false ;
176+ return object instanceof ArrayBuffer ;
177+ }
178+ , isArrayBufferView = function ( object ) {
179+ if ( ! supportsArrayBuffer ( ) )
180+ return false ;
181+ return ( object instanceof Int8Array ||
182+ object instanceof Uint8Array ||
183+ object instanceof Uint8ClampedArray ||
184+ object instanceof Int16Array ||
185+ object instanceof Uint16Array ||
186+ object instanceof Int32Array ||
187+ object instanceof Uint32Array ||
188+ object instanceof Float32Array ||
189+ object instanceof Float64Array ) ;
190+ }
191+ , binaryStringToUint8Array = function ( binary_string ) {
192+ var len = binary_string . length ;
193+ var bytes = new Uint8Array ( len ) ;
194+ for ( var i = 0 ; i < len ; i ++ ) {
195+ bytes [ i ] = binary_string . charCodeAt ( i ) ;
196+ }
197+ return bytes ;
198+ }
199+ , arrayBufferToBinaryString = function ( array_buffer ) {
200+ /*var binary_string = '';
201+ //var bytes = new Uint8Array( array_buffer );
202+ var bytes = array_buffer;
203+ var len = bytes.byteLength;
204+ for (var i = 0; i < len; i++) {
205+ binary_string += String.fromCharCode( bytes[ i ] );
206+ }
207+ return binary_string;*/
208+
209+ if ( isArrayBufferView ( ) )
210+ array_buffer = array_buffer . buffer ;
211+
212+ var base64 = base64ArrayBuffer ( array_buffer ) ;
213+
214+ return window . atob ( base64 ) ;
215+ }
216+ , createImageInfo = function ( data , wd , ht , cs , bpc , f , imageIndex , alias , dp , trns , pal , smask ) {
217+ var info = {
218+ alias :alias ,
219+ w : wd ,
220+ h : ht ,
221+ cs : cs ,
222+ bpc : bpc ,
223+ i : imageIndex ,
224+ data : data
225+ // n: objectNumber will be added by putImage code
226+ } ;
227+
228+ if ( f )
229+ info . f = f ;
230+
231+ if ( dp )
232+ info . dp = dp ;
233+
234+ if ( trns )
235+ info . trns = trns ;
236+
237+ if ( pal )
238+ info . pal = pal ;
239+
240+ if ( smask )
241+ info . smask = smask ;
242+
243+ return info ;
244+ } ;
142245
143246jsPDFAPI . addImage = function ( imageData , format , x , y , w , h , alias ) {
144247 'use strict'
145248 var images = this . internal . collections [ namespace + 'images' ] ,
146- cached_info ;
249+ cached_info ,
250+ binaryStringData ;
147251
148252 if ( typeof format === 'number' ) {
149253 var tmp = h ;
150254 h = w ;
151255 w = y ;
152256 y = x ;
153257 x = format ;
154- format = tmp || 'JPEG ' ;
258+ format = tmp || 'jpeg ' ;
155259 }
260+
156261 if ( typeof alias === 'undefined' ) {
157262 // TODO: Alias dynamic generation from imageData's checksum/hash
158263 }
159264
265+ if ( typeof imageData === 'object' && imageData . nodeType === 1 ) {
266+ var canvas = document . createElement ( 'canvas' ) ;
267+ canvas . width = imageData . clientWidth || imageData . width ;
268+ canvas . height = imageData . clientHeight || imageData . height ;
269+
270+ var ctx = canvas . getContext ( '2d' ) ;
271+ if ( ! ctx ) {
272+ throw ( 'addImage requires canvas to be supported by browser.' ) ;
273+ }
274+ ctx . drawImage ( imageData , 0 , 0 , canvas . width , canvas . height ) ;
275+ imageData = canvas . toDataURL ( ) ;
276+ format = "png" ;
277+ }
278+
160279 if ( typeof imageData === 'string' ) {
161280 if ( imageData . charCodeAt ( 0 ) !== 0xff && imageData . substr ( 0 , 5 ) !== 'data:' ) {
162281 // This is neither raw jpeg-data nor a data uri; alias?
@@ -171,36 +290,35 @@ jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias) {
171290 }
172291 }
173292
174- } else if ( imageData . substr ( 0 , 14 ) === 'data:image/jpg' ) {
175- imageData = imageData . replace ( 'data:image/jpg' , 'data:image/jpeg' ) ;
293+ } else {
294+
295+ var base64Info = extractBase64Info ( imageData ) ;
296+
297+ if ( base64Info ) {
298+
299+ format = base64Info [ 2 ] ;
300+ imageData = atob ( base64Info [ 3 ] ) ; //convert to binary string
301+
302+ /*
303+ * need to test if it's more efficent to convert all binary strings
304+ * to TypedArray - or should we just leave and process as string?
305+ */
306+ if ( supportsArrayBuffer ( ) ) {
307+ binaryStringData = imageData ;
308+ imageData = binaryStringToUint8Array ( imageData ) ;
309+ }
310+
311+ }
176312 }
177313 }
178- if ( typeof imageData === 'object' && imageData . nodeType === 1 ) {
179- var canvas = document . createElement ( 'canvas' ) ;
180- canvas . width = imageData . clientWidth || imageData . width ;
181- canvas . height = imageData . clientHeight || imageData . height ;
182-
183- var ctx = canvas . getContext ( '2d' ) ;
184- if ( ! ctx ) {
185- throw ( 'addImage requires canvas to be supported by browser.' ) ;
186- }
187- ctx . drawImage ( imageData , 0 , 0 , canvas . width , canvas . height ) ;
188- imageData = canvas . toDataURL ( 'image/jpeg' ) ;
189- format = "JPEG" ;
190- }
191- if ( format . toUpperCase ( ) !== 'JPEG' ) {
192- throw new Error ( 'addImage currently only supports format \'JPEG\', not \'' + format + '\'' ) ;
193- }
314+
315+ if ( supported_image_types . indexOf ( format . toLowerCase ( ) ) == - 1 )
316+ throw new Error ( 'addImage currently only supports formats ' + supported_image_types + ', not \'' + format + '\'' ) ;
194317
195318 var imageIndex
196319 , coord = this . internal . getCoordinateString
197320 , vcoord = this . internal . getVerticalCoordinateString ;
198321
199- // Detect if the imageData is raw binary or Data URL
200- if ( imageData . substring ( 0 , 23 ) === 'data:image/jpeg;base64,' ) {
201- imageData = atob ( imageData . replace ( 'data:image/jpeg;base64,' , '' ) ) ;
202- }
203-
204322 if ( images ) {
205323 // this is NOT the first time this method is ran on this instance of jsPDF object.
206324 imageIndex = Object . keys ?
@@ -218,20 +336,128 @@ jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias) {
218336 this . internal . events . subscribe ( 'putXobjectDict' , putXObjectsDictCallback )
219337 }
220338
221- var info = cached_info || ( function ( dims ) {
222- return images [ imageIndex ] = {
223- alias :alias ,
224- w : dims [ 0 ] ,
225- h : dims [ 1 ] ,
226- cs : 'DeviceRGB' ,
227- bpc : 8 ,
228- f : 'DCTDecode' ,
229- i : imageIndex ,
230- data : imageData
231- // n: objectNumber will be added by putImage code
232- } ;
233- } ) ( getJpegSize ( imageData ) ) ;
234-
339+ var info = cached_info ;
340+
341+ if ( ! info ) {
342+
343+ var img ,
344+ dims ,
345+ dp ,
346+ trns ,
347+ colorSpace = color_spaces [ 0 ] ,
348+ filter = filter_methods [ 0 ] ,
349+ bpc = 8 ,
350+ colors ,
351+ pal ,
352+ smask ;
353+
354+ format = format . toLocaleLowerCase ( ) ;
355+
356+ /*
357+ * we have a binary string
358+ */
359+ if ( typeof imageData === 'string' ) {
360+
361+ if ( [ 'jpeg' , 'jpg' ] . indexOf ( format ) !== - 1 ) {
362+
363+ dims = getJpegSize ( imageData ) ;
364+ info = createImageInfo ( imageData , dims [ 0 ] , dims [ 1 ] , colorSpace , bpc , filter , imageIndex , alias ) ;
365+ }
366+
367+ if ( format === 'png' ) {
368+
369+ }
370+ }
371+
372+ if ( isArrayBuffer ( imageData ) )
373+ imageData = new Uint8Array ( imageData ) ;
374+
375+ if ( isArrayBufferView ( imageData ) ) {
376+
377+ if ( [ 'jpeg' , 'jpg' ] . indexOf ( format ) !== - 1 ) {
378+ img = new JpegImage ( ) ;
379+ img . parse ( imageData ) ;
380+
381+ /*
382+ * check if already have a stored binary string rep
383+ */
384+ imageData = binaryStringData || arrayBufferToBinaryString ( imageData ) ;
385+
386+ info = createImageInfo ( imageData , img . width , img . height , colorSpace , bpc , filter , imageIndex , alias ) ;
387+ }
388+
389+ if ( format === 'png' ) {
390+
391+ img = new PNG ( imageData ) ;
392+ imageData = img . imgData ;
393+ bpc = img . bits ;
394+ colorSpace = img . colorSpace ;
395+ colors = img . colors ;
396+ filter = filter_methods [ 1 ] ; //FlateDecode as png pixels are compressed
397+
398+ /*
399+ * we need to extract alpha layer and add it as a smask
400+ */
401+ if ( img . colorType === 6 &&
402+ img . pixelBitlength === 32 ) {
403+
404+ var //pixels = new DataView(img.decode().buffer),
405+ pixels = new Uint32Array ( img . decode ( ) . buffer ) ,
406+ //len = pixels.byteLength / 4,
407+ len = pixels . length ,
408+ colorData = new Uint8Array ( len * 3 ) ,
409+ transData = new Uint8Array ( len ) ,
410+ pixel ,
411+ i = 0 ,
412+ n = 0 ;
413+
414+ for ( ; i < len ; i ++ ) {
415+ //pixel = pixels.getInt32(i);
416+ pixel = pixels [ i ] ;
417+
418+ colorData [ n ++ ] = ( pixel >> 0 ) & 0xff ;
419+ colorData [ n ++ ] = ( pixel >> 8 ) & 0xff ;
420+ colorData [ n ++ ] = ( pixel >> 16 ) & 0xff ;
421+
422+ transData [ i ] = ( pixel >> 24 ) & 0xff ;
423+ }
424+
425+ imageData = colorData ;
426+ smask = arrayBufferToBinaryString ( transData ) ;
427+ filter = null ;
428+ }
429+
430+ if ( filter === filter_methods [ 1 ] )
431+ dp = '/Predictor 15 /Colors ' + colors + ' /BitsPerComponent ' + bpc + ' /Columns ' + img . width ;
432+
433+ if ( img . palette && img . palette . length > 0 )
434+ pal = img . palette ;
435+
436+ if ( img . transparency . indexed )
437+ trns = img . transparency . indexed ;
438+
439+ info = createImageInfo ( arrayBufferToBinaryString ( imageData ) ,
440+ img . width ,
441+ img . height ,
442+ colorSpace ,
443+ bpc ,
444+ filter ,
445+ imageIndex ,
446+ alias ,
447+ dp ,
448+ trns ,
449+ pal ,
450+ smask ) ;
451+ }
452+ }
453+
454+ }
455+
456+ if ( ! info )
457+ throw new Error ( 'Something went wrong, theres no image info' ) ;
458+
459+ images [ imageIndex ] = info ;
460+
235461 if ( ! w && ! h ) {
236462 w = - 96 ;
237463 h = - 96 ;
0 commit comments