| 1 |
/** |
| 2 |
* Dom-To-Image 2.6.0 |
| 3 |
* https://github.com/tsayen/dom-to-image |
| 4 |
* |
| 5 |
* Released under the MIT license |
| 6 |
* https://github.com/tsayen/dom-to-image/blob/master/LICENSE |
| 7 |
*/ |
| 8 |
|
| 9 |
(function ( global ) { |
| 10 |
'use strict'; |
| 11 |
|
| 12 |
var util = newUtil(); |
| 13 |
var inliner = newInliner(); |
| 14 |
var fontFaces = newFontFaces(); |
| 15 |
var images = newImages(); |
| 16 |
|
| 17 |
// Default impl options |
| 18 |
var defaultOptions = { |
| 19 |
// Default is to fail on error, no placeholder |
| 20 |
imagePlaceholder: undefined, |
| 21 |
// Default cache bust is false, it will use the cache |
| 22 |
cacheBust: false |
| 23 |
}; |
| 24 |
|
| 25 |
var domtoimage = { |
| 26 |
toSvg: toSvg, |
| 27 |
toPng: toPng, |
| 28 |
toJpeg: toJpeg, |
| 29 |
toBlob: toBlob, |
| 30 |
toPixelData: toPixelData, |
| 31 |
impl: { |
| 32 |
fontFaces: fontFaces, |
| 33 |
images: images, |
| 34 |
util: util, |
| 35 |
inliner: inliner, |
| 36 |
options: {} |
| 37 |
} |
| 38 |
}; |
| 39 |
|
| 40 |
if ( typeof module !== 'undefined' ) |
| 41 |
module.exports = domtoimage; |
| 42 |
else |
| 43 |
global.domtoimage = domtoimage; |
| 44 |
|
| 45 |
|
| 46 |
/** |
| 47 |
* @param {Node} node - The DOM Node object to render |
| 48 |
* @param {Object} options - Rendering options |
| 49 |
* @param {Function} options.filter - Should return true if passed node should be included in the output |
| 50 |
* (excluding node means excluding it's children as well). Not called on the root node. |
| 51 |
* @param {String} options.bgcolor - color for the background, any valid CSS color value. |
| 52 |
* @param {Number} options.width - width to be applied to node before rendering. |
| 53 |
* @param {Number} options.height - height to be applied to node before rendering. |
| 54 |
* @param {Object} options.style - an object whose properties to be copied to node's style before rendering. |
| 55 |
* @param {Number} options.quality - a Number between 0 and 1 indicating image quality (applicable to JPEG only), |
| 56 |
defaults to 1.0. |
| 57 |
* @param {String} options.imagePlaceholder - dataURL to use as a placeholder for failed images, default behaviour is to fail fast on images we can't fetch |
| 58 |
* @param {Boolean} options.cacheBust - set to true to cache bust by appending the time to the request url |
| 59 |
* @return {Promise} - A promise that is fulfilled with a SVG image data URL |
| 60 |
* */ |
| 61 |
function toSvg( node, options ) { |
| 62 |
options = options || {}; |
| 63 |
copyOptions( options ); |
| 64 |
return Promise.resolve( node ) |
| 65 |
.then( embedFonts ) |
| 66 |
.then( function ( node ) { |
| 67 |
return cloneNode( node, options.filter, true ); |
| 68 |
} ) |
| 69 |
.then( inlineImages ) |
| 70 |
.then( applyOptions ) |
| 71 |
.then( function ( clone ) { |
| 72 |
return makeSvgDataUri( clone, |
| 73 |
options.width || util.width( node ), |
| 74 |
options.height || util.height( node ) |
| 75 |
); |
| 76 |
} ); |
| 77 |
|
| 78 |
function applyOptions( clone ) { |
| 79 |
if ( options.bgcolor ) clone.style.backgroundColor = options.bgcolor; |
| 80 |
|
| 81 |
if ( options.width ) clone.style.width = options.width + 'px'; |
| 82 |
if ( options.height ) clone.style.height = options.height + 'px'; |
| 83 |
|
| 84 |
if ( options.style ) |
| 85 |
Object.keys( options.style ).forEach( function ( property ) { |
| 86 |
clone.style[ property ] = options.style[ property ]; |
| 87 |
} ); |
| 88 |
|
| 89 |
return clone; |
| 90 |
} |
| 91 |
} |
| 92 |
|
| 93 |
/** |
| 94 |
* @param {Node} node - The DOM Node object to render |
| 95 |
* @param {Object} options - Rendering options, @see {@link toSvg} |
| 96 |
* @return {Promise} - A promise that is fulfilled with a Uint8Array containing RGBA pixel data. |
| 97 |
* */ |
| 98 |
function toPixelData( node, options ) { |
| 99 |
return draw( node, options || {} ) |
| 100 |
.then( function ( canvas ) { |
| 101 |
return canvas.getContext( '2d' ).getImageData( |
| 102 |
0, |
| 103 |
0, |
| 104 |
util.width( node ), |
| 105 |
util.height( node ) |
| 106 |
).data; |
| 107 |
} ); |
| 108 |
} |
| 109 |
|
| 110 |
/** |
| 111 |
* @param {Node} node - The DOM Node object to render |
| 112 |
* @param {Object} options - Rendering options, @see {@link toSvg} |
| 113 |
* @return {Promise} - A promise that is fulfilled with a PNG image data URL |
| 114 |
* */ |
| 115 |
function toPng( node, options ) { |
| 116 |
return draw( node, options || {} ) |
| 117 |
.then( function ( canvas ) { |
| 118 |
return canvas.toDataURL(); |
| 119 |
} ); |
| 120 |
} |
| 121 |
|
| 122 |
/** |
| 123 |
* @param {Node} node - The DOM Node object to render |
| 124 |
* @param {Object} options - Rendering options, @see {@link toSvg} |
| 125 |
* @return {Promise} - A promise that is fulfilled with a JPEG image data URL |
| 126 |
* */ |
| 127 |
function toJpeg( node, options ) { |
| 128 |
options = options || {}; |
| 129 |
return draw( node, options ) |
| 130 |
.then( function ( canvas ) { |
| 131 |
return canvas.toDataURL( 'image/jpeg', options.quality || 1.0 ); |
| 132 |
} ); |
| 133 |
} |
| 134 |
|
| 135 |
/** |
| 136 |
* @param {Node} node - The DOM Node object to render |
| 137 |
* @param {Object} options - Rendering options, @see {@link toSvg} |
| 138 |
* @return {Promise} - A promise that is fulfilled with a PNG image blob |
| 139 |
* */ |
| 140 |
function toBlob( node, options ) { |
| 141 |
return draw( node, options || {} ) |
| 142 |
.then( util.canvasToBlob ); |
| 143 |
} |
| 144 |
|
| 145 |
function copyOptions( options ) { |
| 146 |
// Copy options to impl options for use in impl |
| 147 |
if ( typeof (options.imagePlaceholder) === 'undefined' ) { |
| 148 |
domtoimage.impl.options.imagePlaceholder = defaultOptions.imagePlaceholder; |
| 149 |
} else { |
| 150 |
domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder; |
| 151 |
} |
| 152 |
|
| 153 |
if ( typeof (options.cacheBust) === 'undefined' ) { |
| 154 |
domtoimage.impl.options.cacheBust = defaultOptions.cacheBust; |
| 155 |
} else { |
| 156 |
domtoimage.impl.options.cacheBust = options.cacheBust; |
| 157 |
} |
| 158 |
} |
| 159 |
|
| 160 |
function draw( domNode, options ) { |
| 161 |
return toSvg( domNode, options ) |
| 162 |
.then( util.makeImage ) |
| 163 |
.then( util.delay( 100 ) ) |
| 164 |
.then( function ( image ) { |
| 165 |
var canvas = newCanvas( domNode ); |
| 166 |
canvas.getContext( '2d' ).drawImage( image, 0, 0 ); |
| 167 |
return canvas; |
| 168 |
} ); |
| 169 |
|
| 170 |
function newCanvas( domNode ) { |
| 171 |
var canvas = document.createElement( 'canvas' ); |
| 172 |
canvas.width = options.width || util.width( domNode ); |
| 173 |
canvas.height = options.height || util.height( domNode ); |
| 174 |
|
| 175 |
if ( options.bgcolor ) { |
| 176 |
var ctx = canvas.getContext( '2d' ); |
| 177 |
ctx.fillStyle = options.bgcolor; |
| 178 |
ctx.fillRect( 0, 0, canvas.width, canvas.height ); |
| 179 |
} |
| 180 |
|
| 181 |
return canvas; |
| 182 |
} |
| 183 |
} |
| 184 |
|
| 185 |
function cloneNode( node, filter, root ) { |
| 186 |
if ( ! root && filter && ! filter( node ) ) return Promise.resolve(); |
| 187 |
|
| 188 |
return Promise.resolve( node ) |
| 189 |
.then( makeNodeCopy ) |
| 190 |
.then( function ( clone ) { |
| 191 |
return cloneChildren( node, clone, filter ); |
| 192 |
} ) |
| 193 |
.then( function ( clone ) { |
| 194 |
return processClone( node, clone ); |
| 195 |
} ); |
| 196 |
|
| 197 |
function makeNodeCopy( node ) { |
| 198 |
if ( node instanceof HTMLCanvasElement ) return util.makeImage( node.toDataURL() ); |
| 199 |
return node.cloneNode( false ); |
| 200 |
} |
| 201 |
|
| 202 |
function cloneChildren( original, clone, filter ) { |
| 203 |
var children = original.childNodes; |
| 204 |
if ( children.length === 0 ) return Promise.resolve( clone ); |
| 205 |
|
| 206 |
return cloneChildrenInOrder( clone, util.asArray( children ), filter ) |
| 207 |
.then( function () { |
| 208 |
return clone; |
| 209 |
} ); |
| 210 |
|
| 211 |
function cloneChildrenInOrder( parent, children, filter ) { |
| 212 |
var done = Promise.resolve(); |
| 213 |
children.forEach( function ( child ) { |
| 214 |
done = done |
| 215 |
.then( function () { |
| 216 |
return cloneNode( child, filter ); |
| 217 |
} ) |
| 218 |
.then( function ( childClone ) { |
| 219 |
if ( childClone ) parent.appendChild( childClone ); |
| 220 |
} ); |
| 221 |
} ); |
| 222 |
return done; |
| 223 |
} |
| 224 |
} |
| 225 |
|
| 226 |
function processClone( original, clone ) { |
| 227 |
if ( ! (clone instanceof Element) ) return clone; |
| 228 |
|
| 229 |
return Promise.resolve() |
| 230 |
.then( cloneStyle ) |
| 231 |
.then( clonePseudoElements ) |
| 232 |
.then( copyUserInput ) |
| 233 |
.then( fixSvg ) |
| 234 |
.then( function () { |
| 235 |
return clone; |
| 236 |
} ); |
| 237 |
|
| 238 |
function cloneStyle() { |
| 239 |
copyStyle( window.getComputedStyle( original ), clone.style ); |
| 240 |
|
| 241 |
function copyStyle( source, target ) { |
| 242 |
if ( source.cssText ) target.cssText = source.cssText; |
| 243 |
else copyProperties( source, target ); |
| 244 |
|
| 245 |
function copyProperties( source, target ) { |
| 246 |
util.asArray( source ).forEach( function ( name ) { |
| 247 |
target.setProperty( |
| 248 |
name, |
| 249 |
source.getPropertyValue( name ), |
| 250 |
source.getPropertyPriority( name ) |
| 251 |
); |
| 252 |
} ); |
| 253 |
} |
| 254 |
} |
| 255 |
} |
| 256 |
|
| 257 |
function clonePseudoElements() { |
| 258 |
[':before', ':after'].forEach( function ( element ) { |
| 259 |
clonePseudoElement( element ); |
| 260 |
} ); |
| 261 |
|
| 262 |
function clonePseudoElement( element ) { |
| 263 |
var style = window.getComputedStyle( original, element ); |
| 264 |
var content = style.getPropertyValue( 'content' ); |
| 265 |
|
| 266 |
if ( content === '' || content === 'none' ) return; |
| 267 |
|
| 268 |
var className = util.uid(); |
| 269 |
clone.className = clone.className + ' ' + className; |
| 270 |
var styleElement = document.createElement( 'style' ); |
| 271 |
styleElement.appendChild( formatPseudoElementStyle( className, element, style ) ); |
| 272 |
clone.appendChild( styleElement ); |
| 273 |
|
| 274 |
function formatPseudoElementStyle( className, element, style ) { |
| 275 |
var selector = '.' + className + ':' + element; |
| 276 |
var cssText = style.cssText ? formatCssText( style ) : formatCssProperties( style ); |
| 277 |
return document.createTextNode( selector + '{' + cssText + '}' ); |
| 278 |
|
| 279 |
function formatCssText( style ) { |
| 280 |
var content = style.getPropertyValue( 'content' ); |
| 281 |
return style.cssText + ' content: ' + content + ';'; |
| 282 |
} |
| 283 |
|
| 284 |
function formatCssProperties( style ) { |
| 285 |
|
| 286 |
return util.asArray( style ) |
| 287 |
.map( formatProperty ) |
| 288 |
.join( '; ' ) + ';'; |
| 289 |
|
| 290 |
function formatProperty( name ) { |
| 291 |
return name + ': ' + |
| 292 |
style.getPropertyValue( name ) + |
| 293 |
(style.getPropertyPriority( name ) ? ' !important' : ''); |
| 294 |
} |
| 295 |
} |
| 296 |
} |
| 297 |
} |
| 298 |
} |
| 299 |
|
| 300 |
function copyUserInput() { |
| 301 |
if ( original instanceof HTMLTextAreaElement ) clone.innerHTML = original.value; |
| 302 |
if ( original instanceof HTMLInputElement ) clone.setAttribute( "value", original.value ); |
| 303 |
} |
| 304 |
|
| 305 |
function fixSvg() { |
| 306 |
if ( ! (clone instanceof SVGElement) ) return; |
| 307 |
clone.setAttribute( 'xmlns', 'http://www.w3.org/2000/svg' ); |
| 308 |
|
| 309 |
if ( ! (clone instanceof SVGRectElement) ) return; |
| 310 |
['width', 'height'].forEach( function ( attribute ) { |
| 311 |
var value = clone.getAttribute( attribute ); |
| 312 |
if ( ! value ) return; |
| 313 |
|
| 314 |
clone.style.setProperty( attribute, value ); |
| 315 |
} ); |
| 316 |
} |
| 317 |
} |
| 318 |
} |
| 319 |
|
| 320 |
function embedFonts( node ) { |
| 321 |
return fontFaces.resolveAll() |
| 322 |
.then( function ( cssText ) { |
| 323 |
var styleNode = document.createElement( 'style' ); |
| 324 |
node.appendChild( styleNode ); |
| 325 |
styleNode.appendChild( document.createTextNode( cssText ) ); |
| 326 |
return node; |
| 327 |
} ); |
| 328 |
} |
| 329 |
|
| 330 |
function inlineImages( node ) { |
| 331 |
return images.inlineAll( node ) |
| 332 |
.then( function () { |
| 333 |
return node; |
| 334 |
} ); |
| 335 |
} |
| 336 |
|
| 337 |
function makeSvgDataUri( node, width, height ) { |
| 338 |
return Promise.resolve( node ) |
| 339 |
.then( function ( node ) { |
| 340 |
node.setAttribute( 'xmlns', 'http://www.w3.org/1999/xhtml' ); |
| 341 |
return new XMLSerializer().serializeToString( node ); |
| 342 |
} ) |
| 343 |
.then( util.escapeXhtml ) |
| 344 |
.then( function ( xhtml ) { |
| 345 |
return '<foreignObject x="0" y="0" width="100%" height="100%">' + xhtml + '</foreignObject>'; |
| 346 |
} ) |
| 347 |
.then( function ( foreignObject ) { |
| 348 |
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '">' + |
| 349 |
foreignObject + '</svg>'; |
| 350 |
} ) |
| 351 |
.then( function ( svg ) { |
| 352 |
return 'data:image/svg+xml;charset=utf-8,' + svg; |
| 353 |
} ); |
| 354 |
} |
| 355 |
|
| 356 |
function newUtil() { |
| 357 |
return { |
| 358 |
escape: escape, |
| 359 |
parseExtension: parseExtension, |
| 360 |
mimeType: mimeType, |
| 361 |
dataAsUrl: dataAsUrl, |
| 362 |
isDataUrl: isDataUrl, |
| 363 |
canvasToBlob: canvasToBlob, |
| 364 |
resolveUrl: resolveUrl, |
| 365 |
getAndEncode: getAndEncode, |
| 366 |
uid: uid(), |
| 367 |
delay: delay, |
| 368 |
asArray: asArray, |
| 369 |
escapeXhtml: escapeXhtml, |
| 370 |
makeImage: makeImage, |
| 371 |
width: width, |
| 372 |
height: height |
| 373 |
}; |
| 374 |
|
| 375 |
function mimes() { |
| 376 |
/* |
| 377 |
* Only WOFF and EOT mime types for fonts are 'real' |
| 378 |
* see http://www.iana.org/assignments/media-types/media-types.xhtml |
| 379 |
*/ |
| 380 |
var WOFF = 'application/font-woff'; |
| 381 |
var JPEG = 'image/jpeg'; |
| 382 |
|
| 383 |
return { |
| 384 |
'woff': WOFF, |
| 385 |
'woff2': WOFF, |
| 386 |
'ttf': 'application/font-truetype', |
| 387 |
'eot': 'application/vnd.ms-fontobject', |
| 388 |
'png': 'image/png', |
| 389 |
'jpg': JPEG, |
| 390 |
'jpeg': JPEG, |
| 391 |
'gif': 'image/gif', |
| 392 |
'tiff': 'image/tiff', |
| 393 |
'svg': 'image/svg+xml' |
| 394 |
}; |
| 395 |
} |
| 396 |
|
| 397 |
function parseExtension( url ) { |
| 398 |
var match = /\.([^\.\/]*?)$/g.exec( url ); |
| 399 |
if ( match ) return match[ 1 ]; |
| 400 |
else return ''; |
| 401 |
} |
| 402 |
|
| 403 |
function mimeType( url ) { |
| 404 |
var extension = parseExtension( url ).toLowerCase(); |
| 405 |
return mimes()[ extension ] || ''; |
| 406 |
} |
| 407 |
|
| 408 |
function isDataUrl( url ) { |
| 409 |
return url.search( /^(data:)/ ) !== -1; |
| 410 |
} |
| 411 |
|
| 412 |
function toBlob( canvas ) { |
| 413 |
return new Promise( function ( resolve ) { |
| 414 |
var binaryString = window.atob( canvas.toDataURL().split( ',' )[ 1 ] ); |
| 415 |
var length = binaryString.length; |
| 416 |
var binaryArray = new Uint8Array( length ); |
| 417 |
|
| 418 |
for ( var i = 0; i < length; i++ ) |
| 419 |
binaryArray[ i ] = binaryString.charCodeAt( i ); |
| 420 |
|
| 421 |
resolve( new Blob( [binaryArray], { |
| 422 |
type: 'image/png' |
| 423 |
} ) ); |
| 424 |
} ); |
| 425 |
} |
| 426 |
|
| 427 |
function canvasToBlob( canvas ) { |
| 428 |
if ( canvas.toBlob ) |
| 429 |
return new Promise( function ( resolve ) { |
| 430 |
canvas.toBlob( resolve ); |
| 431 |
} ); |
| 432 |
|
| 433 |
return toBlob( canvas ); |
| 434 |
} |
| 435 |
|
| 436 |
function resolveUrl( url, baseUrl ) { |
| 437 |
var doc = document.implementation.createHTMLDocument(); |
| 438 |
var base = doc.createElement( 'base' ); |
| 439 |
doc.head.appendChild( base ); |
| 440 |
var a = doc.createElement( 'a' ); |
| 441 |
doc.body.appendChild( a ); |
| 442 |
base.href = baseUrl; |
| 443 |
a.href = url; |
| 444 |
return a.href; |
| 445 |
} |
| 446 |
|
| 447 |
function uid() { |
| 448 |
var index = 0; |
| 449 |
|
| 450 |
return function () { |
| 451 |
return 'u' + fourRandomChars() + index++; |
| 452 |
|
| 453 |
function fourRandomChars() { |
| 454 |
/* see http://stackoverflow.com/a/6248722/2519373 */ |
| 455 |
return ('0000' + (Math.random() * Math.pow( 36, 4 ) << 0).toString( 36 )).slice( -4 ); |
| 456 |
} |
| 457 |
}; |
| 458 |
} |
| 459 |
|
| 460 |
function makeImage( uri ) { |
| 461 |
return new Promise( function ( resolve, reject ) { |
| 462 |
var image = new Image(); |
| 463 |
image.onload = function () { |
| 464 |
resolve( image ); |
| 465 |
}; |
| 466 |
image.onerror = reject; |
| 467 |
image.src = uri; |
| 468 |
} ); |
| 469 |
} |
| 470 |
|
| 471 |
function getAndEncode( url ) { |
| 472 |
var TIMEOUT = 30000; |
| 473 |
if ( domtoimage.impl.options.cacheBust ) { |
| 474 |
// Cache bypass so we dont have CORS issues with cached images |
| 475 |
// Source: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache |
| 476 |
url += ((/\?/).test( url ) ? "&" : "?") + (new Date()).getTime(); |
| 477 |
} |
| 478 |
|
| 479 |
return new Promise( function ( resolve ) { |
| 480 |
var request = new XMLHttpRequest(); |
| 481 |
|
| 482 |
request.onreadystatechange = done; |
| 483 |
request.ontimeout = timeout; |
| 484 |
request.responseType = 'blob'; |
| 485 |
request.timeout = TIMEOUT; |
| 486 |
request.open( 'GET', url, true ); |
| 487 |
request.send(); |
| 488 |
|
| 489 |
var placeholder; |
| 490 |
if ( domtoimage.impl.options.imagePlaceholder ) { |
| 491 |
var split = domtoimage.impl.options.imagePlaceholder.split( /,/ ); |
| 492 |
if ( split && split[ 1 ] ) { |
| 493 |
placeholder = split[ 1 ]; |
| 494 |
} |
| 495 |
} |
| 496 |
|
| 497 |
function done() { |
| 498 |
if ( request.readyState !== 4 ) return; |
| 499 |
|
| 500 |
if ( request.status !== 200 ) { |
| 501 |
if ( placeholder ) { |
| 502 |
resolve( placeholder ); |
| 503 |
} else { |
| 504 |
fail( 'cannot fetch resource: ' + url + ', status: ' + request.status ); |
| 505 |
} |
| 506 |
|
| 507 |
return; |
| 508 |
} |
| 509 |
|
| 510 |
var encoder = new FileReader(); |
| 511 |
encoder.onloadend = function () { |
| 512 |
var content = encoder.result.split( /,/ )[ 1 ]; |
| 513 |
resolve( content ); |
| 514 |
}; |
| 515 |
encoder.readAsDataURL( request.response ); |
| 516 |
} |
| 517 |
|
| 518 |
function timeout() { |
| 519 |
if ( placeholder ) { |
| 520 |
resolve( placeholder ); |
| 521 |
} else { |
| 522 |
fail( 'timeout of ' + TIMEOUT + 'ms occured while fetching resource: ' + url ); |
| 523 |
} |
| 524 |
} |
| 525 |
|
| 526 |
function fail( message ) { |
| 527 |
console.error( message ); |
| 528 |
resolve( '' ); |
| 529 |
} |
| 530 |
} ); |
| 531 |
} |
| 532 |
|
| 533 |
function dataAsUrl( content, type ) { |
| 534 |
return 'data:' + type + ';base64,' + content; |
| 535 |
} |
| 536 |
|
| 537 |
function escape( string ) { |
| 538 |
return string.replace( /([.*+?^${}()|\[\]\/\\])/g, '\\$1' ); |
| 539 |
} |
| 540 |
|
| 541 |
function delay( ms ) { |
| 542 |
return function ( arg ) { |
| 543 |
return new Promise( function ( resolve ) { |
| 544 |
setTimeout( function () { |
| 545 |
resolve( arg ); |
| 546 |
}, ms ); |
| 547 |
} ); |
| 548 |
}; |
| 549 |
} |
| 550 |
|
| 551 |
function asArray( arrayLike ) { |
| 552 |
var array = []; |
| 553 |
var length = arrayLike.length; |
| 554 |
for ( var i = 0; i < length; i++ ) array.push( arrayLike[ i ] ); |
| 555 |
return array; |
| 556 |
} |
| 557 |
|
| 558 |
function escapeXhtml( string ) { |
| 559 |
return string.replace( /#/g, '%23' ).replace( /\n/g, '%0A' ); |
| 560 |
} |
| 561 |
|
| 562 |
function width( node ) { |
| 563 |
var leftBorder = px( node, 'border-left-width' ); |
| 564 |
var rightBorder = px( node, 'border-right-width' ); |
| 565 |
return node.scrollWidth + leftBorder + rightBorder; |
| 566 |
} |
| 567 |
|
| 568 |
function height( node ) { |
| 569 |
var topBorder = px( node, 'border-top-width' ); |
| 570 |
var bottomBorder = px( node, 'border-bottom-width' ); |
| 571 |
return node.scrollHeight + topBorder + bottomBorder; |
| 572 |
} |
| 573 |
|
| 574 |
function px( node, styleProperty ) { |
| 575 |
var value = window.getComputedStyle( node ).getPropertyValue( styleProperty ); |
| 576 |
return parseFloat( value.replace( 'px', '' ) ); |
| 577 |
} |
| 578 |
} |
| 579 |
|
| 580 |
function newInliner() { |
| 581 |
var URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/g; |
| 582 |
|
| 583 |
return { |
| 584 |
inlineAll: inlineAll, |
| 585 |
shouldProcess: shouldProcess, |
| 586 |
impl: { |
| 587 |
readUrls: readUrls, |
| 588 |
inline: inline |
| 589 |
} |
| 590 |
}; |
| 591 |
|
| 592 |
function shouldProcess( string ) { |
| 593 |
return string.search( URL_REGEX ) !== -1; |
| 594 |
} |
| 595 |
|
| 596 |
function readUrls( string ) { |
| 597 |
var result = []; |
| 598 |
var match; |
| 599 |
while ( (match = URL_REGEX.exec( string )) !== null ) { |
| 600 |
result.push( match[ 1 ] ); |
| 601 |
} |
| 602 |
return result.filter( function ( url ) { |
| 603 |
return ! util.isDataUrl( url ); |
| 604 |
} ); |
| 605 |
} |
| 606 |
|
| 607 |
function inline( string, url, baseUrl, get ) { |
| 608 |
return Promise.resolve( url ) |
| 609 |
.then( function ( url ) { |
| 610 |
return baseUrl ? util.resolveUrl( url, baseUrl ) : url; |
| 611 |
} ) |
| 612 |
.then( get || util.getAndEncode ) |
| 613 |
.then( function ( data ) { |
| 614 |
return util.dataAsUrl( data, util.mimeType( url ) ); |
| 615 |
} ) |
| 616 |
.then( function ( dataUrl ) { |
| 617 |
return string.replace( urlAsRegex( url ), '$1' + dataUrl + '$3' ); |
| 618 |
} ); |
| 619 |
|
| 620 |
function urlAsRegex( url ) { |
| 621 |
return new RegExp( '(url\\([\'"]?)(' + util.escape( url ) + ')([\'"]?\\))', 'g' ); |
| 622 |
} |
| 623 |
} |
| 624 |
|
| 625 |
function inlineAll( string, baseUrl, get ) { |
| 626 |
if ( nothingToInline() ) return Promise.resolve( string ); |
| 627 |
|
| 628 |
return Promise.resolve( string ) |
| 629 |
.then( readUrls ) |
| 630 |
.then( function ( urls ) { |
| 631 |
var done = Promise.resolve( string ); |
| 632 |
urls.forEach( function ( url ) { |
| 633 |
done = done.then( function ( string ) { |
| 634 |
return inline( string, url, baseUrl, get ); |
| 635 |
} ); |
| 636 |
} ); |
| 637 |
return done; |
| 638 |
} ); |
| 639 |
|
| 640 |
function nothingToInline() { |
| 641 |
return ! shouldProcess( string ); |
| 642 |
} |
| 643 |
} |
| 644 |
} |
| 645 |
|
| 646 |
function newFontFaces() { |
| 647 |
return { |
| 648 |
resolveAll: resolveAll, |
| 649 |
impl: { |
| 650 |
readAll: readAll |
| 651 |
} |
| 652 |
}; |
| 653 |
|
| 654 |
function resolveAll() { |
| 655 |
return readAll( document ) |
| 656 |
.then( function ( webFonts ) { |
| 657 |
return Promise.all( |
| 658 |
webFonts.map( function ( webFont ) { |
| 659 |
return webFont.resolve(); |
| 660 |
} ) |
| 661 |
); |
| 662 |
} ) |
| 663 |
.then( function ( cssStrings ) { |
| 664 |
return cssStrings.join( '\n' ); |
| 665 |
} ); |
| 666 |
} |
| 667 |
|
| 668 |
function readAll() { |
| 669 |
return Promise.resolve( util.asArray( document.styleSheets ) ) |
| 670 |
.then( getCssRules ) |
| 671 |
.then( selectWebFontRules ) |
| 672 |
.then( function ( rules ) { |
| 673 |
return rules.map( newWebFont ); |
| 674 |
} ); |
| 675 |
|
| 676 |
function selectWebFontRules( cssRules ) { |
| 677 |
return cssRules |
| 678 |
.filter( function ( rule ) { |
| 679 |
return rule.type === CSSRule.FONT_FACE_RULE; |
| 680 |
} ) |
| 681 |
.filter( function ( rule ) { |
| 682 |
return inliner.shouldProcess( rule.style.getPropertyValue( 'src' ) ); |
| 683 |
} ); |
| 684 |
} |
| 685 |
|
| 686 |
function getCssRules( styleSheets ) { |
| 687 |
var cssRules = []; |
| 688 |
styleSheets.forEach( function ( sheet ) { |
| 689 |
try { |
| 690 |
util.asArray( sheet.cssRules || [] ).forEach( cssRules.push.bind( cssRules ) ); |
| 691 |
} catch ( e ) { |
| 692 |
console.log( 'Error while reading CSS rules from ' + sheet.href, e.toString() ); |
| 693 |
} |
| 694 |
} ); |
| 695 |
return cssRules; |
| 696 |
} |
| 697 |
|
| 698 |
function newWebFont( webFontRule ) { |
| 699 |
return { |
| 700 |
resolve: function resolve() { |
| 701 |
var baseUrl = (webFontRule.parentStyleSheet || {}).href; |
| 702 |
return inliner.inlineAll( webFontRule.cssText, baseUrl ); |
| 703 |
}, |
| 704 |
src: function () { |
| 705 |
return webFontRule.style.getPropertyValue( 'src' ); |
| 706 |
} |
| 707 |
}; |
| 708 |
} |
| 709 |
} |
| 710 |
} |
| 711 |
|
| 712 |
function newImages() { |
| 713 |
return { |
| 714 |
inlineAll: inlineAll, |
| 715 |
impl: { |
| 716 |
newImage: newImage |
| 717 |
} |
| 718 |
}; |
| 719 |
|
| 720 |
function newImage( element ) { |
| 721 |
return { |
| 722 |
inline: inline |
| 723 |
}; |
| 724 |
|
| 725 |
function inline( get ) { |
| 726 |
if ( util.isDataUrl( element.src ) ) return Promise.resolve(); |
| 727 |
|
| 728 |
return Promise.resolve( element.src ) |
| 729 |
.then( get || util.getAndEncode ) |
| 730 |
.then( function ( data ) { |
| 731 |
return util.dataAsUrl( data, util.mimeType( element.src ) ); |
| 732 |
} ) |
| 733 |
.then( function ( dataUrl ) { |
| 734 |
return new Promise( function ( resolve, reject ) { |
| 735 |
element.onload = resolve; |
| 736 |
element.onerror = reject; |
| 737 |
element.src = dataUrl; |
| 738 |
} ); |
| 739 |
} ); |
| 740 |
} |
| 741 |
} |
| 742 |
|
| 743 |
function inlineAll( node ) { |
| 744 |
if ( ! (node instanceof Element) ) return Promise.resolve( node ); |
| 745 |
|
| 746 |
return inlineBackground( node ) |
| 747 |
.then( function () { |
| 748 |
if ( node instanceof HTMLImageElement ) |
| 749 |
return newImage( node ).inline(); |
| 750 |
else |
| 751 |
return Promise.all( |
| 752 |
util.asArray( node.childNodes ).map( function ( child ) { |
| 753 |
return inlineAll( child ); |
| 754 |
} ) |
| 755 |
); |
| 756 |
} ); |
| 757 |
|
| 758 |
function inlineBackground( node ) { |
| 759 |
var background = node.style.getPropertyValue( 'background' ); |
| 760 |
|
| 761 |
if ( ! background ) return Promise.resolve( node ); |
| 762 |
|
| 763 |
return inliner.inlineAll( background ) |
| 764 |
.then( function ( inlined ) { |
| 765 |
node.style.setProperty( |
| 766 |
'background', |
| 767 |
inlined, |
| 768 |
node.style.getPropertyPriority( 'background' ) |
| 769 |
); |
| 770 |
} ) |
| 771 |
.then( function () { |
| 772 |
return node; |
| 773 |
} ); |
| 774 |
} |
| 775 |
} |
| 776 |
} |
| 777 |
})( this ); |
| 778 |
|