778 lines
21 KiB
JavaScript
778 lines
21 KiB
JavaScript
|
/**
|
||
|
* Dom-To-Image 2.6.0
|
||
|
* https://github.com/tsayen/dom-to-image
|
||
|
*
|
||
|
* Released under the MIT license
|
||
|
* https://github.com/tsayen/dom-to-image/blob/master/LICENSE
|
||
|
*/
|
||
|
|
||
|
(function ( global ) {
|
||
|
'use strict';
|
||
|
|
||
|
var util = newUtil();
|
||
|
var inliner = newInliner();
|
||
|
var fontFaces = newFontFaces();
|
||
|
var images = newImages();
|
||
|
|
||
|
// Default impl options
|
||
|
var defaultOptions = {
|
||
|
// Default is to fail on error, no placeholder
|
||
|
imagePlaceholder: undefined,
|
||
|
// Default cache bust is false, it will use the cache
|
||
|
cacheBust: false
|
||
|
};
|
||
|
|
||
|
var domtoimage = {
|
||
|
toSvg: toSvg,
|
||
|
toPng: toPng,
|
||
|
toJpeg: toJpeg,
|
||
|
toBlob: toBlob,
|
||
|
toPixelData: toPixelData,
|
||
|
impl: {
|
||
|
fontFaces: fontFaces,
|
||
|
images: images,
|
||
|
util: util,
|
||
|
inliner: inliner,
|
||
|
options: {}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if ( typeof module !== 'undefined' )
|
||
|
module.exports = domtoimage;
|
||
|
else
|
||
|
global.domtoimage = domtoimage;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @param {Node} node - The DOM Node object to render
|
||
|
* @param {Object} options - Rendering options
|
||
|
* @param {Function} options.filter - Should return true if passed node should be included in the output
|
||
|
* (excluding node means excluding it's children as well). Not called on the root node.
|
||
|
* @param {String} options.bgcolor - color for the background, any valid CSS color value.
|
||
|
* @param {Number} options.width - width to be applied to node before rendering.
|
||
|
* @param {Number} options.height - height to be applied to node before rendering.
|
||
|
* @param {Object} options.style - an object whose properties to be copied to node's style before rendering.
|
||
|
* @param {Number} options.quality - a Number between 0 and 1 indicating image quality (applicable to JPEG only),
|
||
|
defaults to 1.0.
|
||
|
* @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
|
||
|
* @param {Boolean} options.cacheBust - set to true to cache bust by appending the time to the request url
|
||
|
* @return {Promise} - A promise that is fulfilled with a SVG image data URL
|
||
|
* */
|
||
|
function toSvg( node, options ) {
|
||
|
options = options || {};
|
||
|
copyOptions( options );
|
||
|
return Promise.resolve( node )
|
||
|
.then( embedFonts )
|
||
|
.then( function ( node ) {
|
||
|
return cloneNode( node, options.filter, true );
|
||
|
} )
|
||
|
.then( inlineImages )
|
||
|
.then( applyOptions )
|
||
|
.then( function ( clone ) {
|
||
|
return makeSvgDataUri( clone,
|
||
|
options.width || util.width( node ),
|
||
|
options.height || util.height( node )
|
||
|
);
|
||
|
} );
|
||
|
|
||
|
function applyOptions( clone ) {
|
||
|
if ( options.bgcolor ) clone.style.backgroundColor = options.bgcolor;
|
||
|
|
||
|
if ( options.width ) clone.style.width = options.width + 'px';
|
||
|
if ( options.height ) clone.style.height = options.height + 'px';
|
||
|
|
||
|
if ( options.style )
|
||
|
Object.keys( options.style ).forEach( function ( property ) {
|
||
|
clone.style[ property ] = options.style[ property ];
|
||
|
} );
|
||
|
|
||
|
return clone;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {Node} node - The DOM Node object to render
|
||
|
* @param {Object} options - Rendering options, @see {@link toSvg}
|
||
|
* @return {Promise} - A promise that is fulfilled with a Uint8Array containing RGBA pixel data.
|
||
|
* */
|
||
|
function toPixelData( node, options ) {
|
||
|
return draw( node, options || {} )
|
||
|
.then( function ( canvas ) {
|
||
|
return canvas.getContext( '2d' ).getImageData(
|
||
|
0,
|
||
|
0,
|
||
|
util.width( node ),
|
||
|
util.height( node )
|
||
|
).data;
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {Node} node - The DOM Node object to render
|
||
|
* @param {Object} options - Rendering options, @see {@link toSvg}
|
||
|
* @return {Promise} - A promise that is fulfilled with a PNG image data URL
|
||
|
* */
|
||
|
function toPng( node, options ) {
|
||
|
return draw( node, options || {} )
|
||
|
.then( function ( canvas ) {
|
||
|
return canvas.toDataURL();
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {Node} node - The DOM Node object to render
|
||
|
* @param {Object} options - Rendering options, @see {@link toSvg}
|
||
|
* @return {Promise} - A promise that is fulfilled with a JPEG image data URL
|
||
|
* */
|
||
|
function toJpeg( node, options ) {
|
||
|
options = options || {};
|
||
|
return draw( node, options )
|
||
|
.then( function ( canvas ) {
|
||
|
return canvas.toDataURL( 'image/jpeg', options.quality || 1.0 );
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {Node} node - The DOM Node object to render
|
||
|
* @param {Object} options - Rendering options, @see {@link toSvg}
|
||
|
* @return {Promise} - A promise that is fulfilled with a PNG image blob
|
||
|
* */
|
||
|
function toBlob( node, options ) {
|
||
|
return draw( node, options || {} )
|
||
|
.then( util.canvasToBlob );
|
||
|
}
|
||
|
|
||
|
function copyOptions( options ) {
|
||
|
// Copy options to impl options for use in impl
|
||
|
if ( typeof (options.imagePlaceholder) === 'undefined' ) {
|
||
|
domtoimage.impl.options.imagePlaceholder = defaultOptions.imagePlaceholder;
|
||
|
} else {
|
||
|
domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder;
|
||
|
}
|
||
|
|
||
|
if ( typeof (options.cacheBust) === 'undefined' ) {
|
||
|
domtoimage.impl.options.cacheBust = defaultOptions.cacheBust;
|
||
|
} else {
|
||
|
domtoimage.impl.options.cacheBust = options.cacheBust;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function draw( domNode, options ) {
|
||
|
return toSvg( domNode, options )
|
||
|
.then( util.makeImage )
|
||
|
.then( util.delay( 100 ) )
|
||
|
.then( function ( image ) {
|
||
|
var canvas = newCanvas( domNode );
|
||
|
canvas.getContext( '2d' ).drawImage( image, 0, 0 );
|
||
|
return canvas;
|
||
|
} );
|
||
|
|
||
|
function newCanvas( domNode ) {
|
||
|
var canvas = document.createElement( 'canvas' );
|
||
|
canvas.width = options.width || util.width( domNode );
|
||
|
canvas.height = options.height || util.height( domNode );
|
||
|
|
||
|
if ( options.bgcolor ) {
|
||
|
var ctx = canvas.getContext( '2d' );
|
||
|
ctx.fillStyle = options.bgcolor;
|
||
|
ctx.fillRect( 0, 0, canvas.width, canvas.height );
|
||
|
}
|
||
|
|
||
|
return canvas;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function cloneNode( node, filter, root ) {
|
||
|
if ( ! root && filter && ! filter( node ) ) return Promise.resolve();
|
||
|
|
||
|
return Promise.resolve( node )
|
||
|
.then( makeNodeCopy )
|
||
|
.then( function ( clone ) {
|
||
|
return cloneChildren( node, clone, filter );
|
||
|
} )
|
||
|
.then( function ( clone ) {
|
||
|
return processClone( node, clone );
|
||
|
} );
|
||
|
|
||
|
function makeNodeCopy( node ) {
|
||
|
if ( node instanceof HTMLCanvasElement ) return util.makeImage( node.toDataURL() );
|
||
|
return node.cloneNode( false );
|
||
|
}
|
||
|
|
||
|
function cloneChildren( original, clone, filter ) {
|
||
|
var children = original.childNodes;
|
||
|
if ( children.length === 0 ) return Promise.resolve( clone );
|
||
|
|
||
|
return cloneChildrenInOrder( clone, util.asArray( children ), filter )
|
||
|
.then( function () {
|
||
|
return clone;
|
||
|
} );
|
||
|
|
||
|
function cloneChildrenInOrder( parent, children, filter ) {
|
||
|
var done = Promise.resolve();
|
||
|
children.forEach( function ( child ) {
|
||
|
done = done
|
||
|
.then( function () {
|
||
|
return cloneNode( child, filter );
|
||
|
} )
|
||
|
.then( function ( childClone ) {
|
||
|
if ( childClone ) parent.appendChild( childClone );
|
||
|
} );
|
||
|
} );
|
||
|
return done;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function processClone( original, clone ) {
|
||
|
if ( ! (clone instanceof Element) ) return clone;
|
||
|
|
||
|
return Promise.resolve()
|
||
|
.then( cloneStyle )
|
||
|
.then( clonePseudoElements )
|
||
|
.then( copyUserInput )
|
||
|
.then( fixSvg )
|
||
|
.then( function () {
|
||
|
return clone;
|
||
|
} );
|
||
|
|
||
|
function cloneStyle() {
|
||
|
copyStyle( window.getComputedStyle( original ), clone.style );
|
||
|
|
||
|
function copyStyle( source, target ) {
|
||
|
if ( source.cssText ) target.cssText = source.cssText;
|
||
|
else copyProperties( source, target );
|
||
|
|
||
|
function copyProperties( source, target ) {
|
||
|
util.asArray( source ).forEach( function ( name ) {
|
||
|
target.setProperty(
|
||
|
name,
|
||
|
source.getPropertyValue( name ),
|
||
|
source.getPropertyPriority( name )
|
||
|
);
|
||
|
} );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function clonePseudoElements() {
|
||
|
[':before', ':after'].forEach( function ( element ) {
|
||
|
clonePseudoElement( element );
|
||
|
} );
|
||
|
|
||
|
function clonePseudoElement( element ) {
|
||
|
var style = window.getComputedStyle( original, element );
|
||
|
var content = style.getPropertyValue( 'content' );
|
||
|
|
||
|
if ( content === '' || content === 'none' ) return;
|
||
|
|
||
|
var className = util.uid();
|
||
|
clone.className = clone.className + ' ' + className;
|
||
|
var styleElement = document.createElement( 'style' );
|
||
|
styleElement.appendChild( formatPseudoElementStyle( className, element, style ) );
|
||
|
clone.appendChild( styleElement );
|
||
|
|
||
|
function formatPseudoElementStyle( className, element, style ) {
|
||
|
var selector = '.' + className + ':' + element;
|
||
|
var cssText = style.cssText ? formatCssText( style ) : formatCssProperties( style );
|
||
|
return document.createTextNode( selector + '{' + cssText + '}' );
|
||
|
|
||
|
function formatCssText( style ) {
|
||
|
var content = style.getPropertyValue( 'content' );
|
||
|
return style.cssText + ' content: ' + content + ';';
|
||
|
}
|
||
|
|
||
|
function formatCssProperties( style ) {
|
||
|
|
||
|
return util.asArray( style )
|
||
|
.map( formatProperty )
|
||
|
.join( '; ' ) + ';';
|
||
|
|
||
|
function formatProperty( name ) {
|
||
|
return name + ': ' +
|
||
|
style.getPropertyValue( name ) +
|
||
|
(style.getPropertyPriority( name ) ? ' !important' : '');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function copyUserInput() {
|
||
|
if ( original instanceof HTMLTextAreaElement ) clone.innerHTML = original.value;
|
||
|
if ( original instanceof HTMLInputElement ) clone.setAttribute( "value", original.value );
|
||
|
}
|
||
|
|
||
|
function fixSvg() {
|
||
|
if ( ! (clone instanceof SVGElement) ) return;
|
||
|
clone.setAttribute( 'xmlns', 'http://www.w3.org/2000/svg' );
|
||
|
|
||
|
if ( ! (clone instanceof SVGRectElement) ) return;
|
||
|
['width', 'height'].forEach( function ( attribute ) {
|
||
|
var value = clone.getAttribute( attribute );
|
||
|
if ( ! value ) return;
|
||
|
|
||
|
clone.style.setProperty( attribute, value );
|
||
|
} );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function embedFonts( node ) {
|
||
|
return fontFaces.resolveAll()
|
||
|
.then( function ( cssText ) {
|
||
|
var styleNode = document.createElement( 'style' );
|
||
|
node.appendChild( styleNode );
|
||
|
styleNode.appendChild( document.createTextNode( cssText ) );
|
||
|
return node;
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
function inlineImages( node ) {
|
||
|
return images.inlineAll( node )
|
||
|
.then( function () {
|
||
|
return node;
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
function makeSvgDataUri( node, width, height ) {
|
||
|
return Promise.resolve( node )
|
||
|
.then( function ( node ) {
|
||
|
node.setAttribute( 'xmlns', 'http://www.w3.org/1999/xhtml' );
|
||
|
return new XMLSerializer().serializeToString( node );
|
||
|
} )
|
||
|
.then( util.escapeXhtml )
|
||
|
.then( function ( xhtml ) {
|
||
|
return '<foreignObject x="0" y="0" width="100%" height="100%">' + xhtml + '</foreignObject>';
|
||
|
} )
|
||
|
.then( function ( foreignObject ) {
|
||
|
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '">' +
|
||
|
foreignObject + '</svg>';
|
||
|
} )
|
||
|
.then( function ( svg ) {
|
||
|
return 'data:image/svg+xml;charset=utf-8,' + svg;
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
function newUtil() {
|
||
|
return {
|
||
|
escape: escape,
|
||
|
parseExtension: parseExtension,
|
||
|
mimeType: mimeType,
|
||
|
dataAsUrl: dataAsUrl,
|
||
|
isDataUrl: isDataUrl,
|
||
|
canvasToBlob: canvasToBlob,
|
||
|
resolveUrl: resolveUrl,
|
||
|
getAndEncode: getAndEncode,
|
||
|
uid: uid(),
|
||
|
delay: delay,
|
||
|
asArray: asArray,
|
||
|
escapeXhtml: escapeXhtml,
|
||
|
makeImage: makeImage,
|
||
|
width: width,
|
||
|
height: height
|
||
|
};
|
||
|
|
||
|
function mimes() {
|
||
|
/*
|
||
|
* Only WOFF and EOT mime types for fonts are 'real'
|
||
|
* see http://www.iana.org/assignments/media-types/media-types.xhtml
|
||
|
*/
|
||
|
var WOFF = 'application/font-woff';
|
||
|
var JPEG = 'image/jpeg';
|
||
|
|
||
|
return {
|
||
|
'woff': WOFF,
|
||
|
'woff2': WOFF,
|
||
|
'ttf': 'application/font-truetype',
|
||
|
'eot': 'application/vnd.ms-fontobject',
|
||
|
'png': 'image/png',
|
||
|
'jpg': JPEG,
|
||
|
'jpeg': JPEG,
|
||
|
'gif': 'image/gif',
|
||
|
'tiff': 'image/tiff',
|
||
|
'svg': 'image/svg+xml'
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function parseExtension( url ) {
|
||
|
var match = /\.([^\.\/]*?)$/g.exec( url );
|
||
|
if ( match ) return match[ 1 ];
|
||
|
else return '';
|
||
|
}
|
||
|
|
||
|
function mimeType( url ) {
|
||
|
var extension = parseExtension( url ).toLowerCase();
|
||
|
return mimes()[ extension ] || '';
|
||
|
}
|
||
|
|
||
|
function isDataUrl( url ) {
|
||
|
return url.search( /^(data:)/ ) !== -1;
|
||
|
}
|
||
|
|
||
|
function toBlob( canvas ) {
|
||
|
return new Promise( function ( resolve ) {
|
||
|
var binaryString = window.atob( canvas.toDataURL().split( ',' )[ 1 ] );
|
||
|
var length = binaryString.length;
|
||
|
var binaryArray = new Uint8Array( length );
|
||
|
|
||
|
for ( var i = 0; i < length; i++ )
|
||
|
binaryArray[ i ] = binaryString.charCodeAt( i );
|
||
|
|
||
|
resolve( new Blob( [binaryArray], {
|
||
|
type: 'image/png'
|
||
|
} ) );
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
function canvasToBlob( canvas ) {
|
||
|
if ( canvas.toBlob )
|
||
|
return new Promise( function ( resolve ) {
|
||
|
canvas.toBlob( resolve );
|
||
|
} );
|
||
|
|
||
|
return toBlob( canvas );
|
||
|
}
|
||
|
|
||
|
function resolveUrl( url, baseUrl ) {
|
||
|
var doc = document.implementation.createHTMLDocument();
|
||
|
var base = doc.createElement( 'base' );
|
||
|
doc.head.appendChild( base );
|
||
|
var a = doc.createElement( 'a' );
|
||
|
doc.body.appendChild( a );
|
||
|
base.href = baseUrl;
|
||
|
a.href = url;
|
||
|
return a.href;
|
||
|
}
|
||
|
|
||
|
function uid() {
|
||
|
var index = 0;
|
||
|
|
||
|
return function () {
|
||
|
return 'u' + fourRandomChars() + index++;
|
||
|
|
||
|
function fourRandomChars() {
|
||
|
/* see http://stackoverflow.com/a/6248722/2519373 */
|
||
|
return ('0000' + (Math.random() * Math.pow( 36, 4 ) << 0).toString( 36 )).slice( -4 );
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function makeImage( uri ) {
|
||
|
return new Promise( function ( resolve, reject ) {
|
||
|
var image = new Image();
|
||
|
image.onload = function () {
|
||
|
resolve( image );
|
||
|
};
|
||
|
image.onerror = reject;
|
||
|
image.src = uri;
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
function getAndEncode( url ) {
|
||
|
var TIMEOUT = 30000;
|
||
|
if ( domtoimage.impl.options.cacheBust ) {
|
||
|
// Cache bypass so we dont have CORS issues with cached images
|
||
|
// Source: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache
|
||
|
url += ((/\?/).test( url ) ? "&" : "?") + (new Date()).getTime();
|
||
|
}
|
||
|
|
||
|
return new Promise( function ( resolve ) {
|
||
|
var request = new XMLHttpRequest();
|
||
|
|
||
|
request.onreadystatechange = done;
|
||
|
request.ontimeout = timeout;
|
||
|
request.responseType = 'blob';
|
||
|
request.timeout = TIMEOUT;
|
||
|
request.open( 'GET', url, true );
|
||
|
request.send();
|
||
|
|
||
|
var placeholder;
|
||
|
if ( domtoimage.impl.options.imagePlaceholder ) {
|
||
|
var split = domtoimage.impl.options.imagePlaceholder.split( /,/ );
|
||
|
if ( split && split[ 1 ] ) {
|
||
|
placeholder = split[ 1 ];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function done() {
|
||
|
if ( request.readyState !== 4 ) return;
|
||
|
|
||
|
if ( request.status !== 200 ) {
|
||
|
if ( placeholder ) {
|
||
|
resolve( placeholder );
|
||
|
} else {
|
||
|
fail( 'cannot fetch resource: ' + url + ', status: ' + request.status );
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var encoder = new FileReader();
|
||
|
encoder.onloadend = function () {
|
||
|
var content = encoder.result.split( /,/ )[ 1 ];
|
||
|
resolve( content );
|
||
|
};
|
||
|
encoder.readAsDataURL( request.response );
|
||
|
}
|
||
|
|
||
|
function timeout() {
|
||
|
if ( placeholder ) {
|
||
|
resolve( placeholder );
|
||
|
} else {
|
||
|
fail( 'timeout of ' + TIMEOUT + 'ms occured while fetching resource: ' + url );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function fail( message ) {
|
||
|
console.error( message );
|
||
|
resolve( '' );
|
||
|
}
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
function dataAsUrl( content, type ) {
|
||
|
return 'data:' + type + ';base64,' + content;
|
||
|
}
|
||
|
|
||
|
function escape( string ) {
|
||
|
return string.replace( /([.*+?^${}()|\[\]\/\\])/g, '\\$1' );
|
||
|
}
|
||
|
|
||
|
function delay( ms ) {
|
||
|
return function ( arg ) {
|
||
|
return new Promise( function ( resolve ) {
|
||
|
setTimeout( function () {
|
||
|
resolve( arg );
|
||
|
}, ms );
|
||
|
} );
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function asArray( arrayLike ) {
|
||
|
var array = [];
|
||
|
var length = arrayLike.length;
|
||
|
for ( var i = 0; i < length; i++ ) array.push( arrayLike[ i ] );
|
||
|
return array;
|
||
|
}
|
||
|
|
||
|
function escapeXhtml( string ) {
|
||
|
return string.replace( /#/g, '%23' ).replace( /\n/g, '%0A' );
|
||
|
}
|
||
|
|
||
|
function width( node ) {
|
||
|
var leftBorder = px( node, 'border-left-width' );
|
||
|
var rightBorder = px( node, 'border-right-width' );
|
||
|
return node.scrollWidth + leftBorder + rightBorder;
|
||
|
}
|
||
|
|
||
|
function height( node ) {
|
||
|
var topBorder = px( node, 'border-top-width' );
|
||
|
var bottomBorder = px( node, 'border-bottom-width' );
|
||
|
return node.scrollHeight + topBorder + bottomBorder;
|
||
|
}
|
||
|
|
||
|
function px( node, styleProperty ) {
|
||
|
var value = window.getComputedStyle( node ).getPropertyValue( styleProperty );
|
||
|
return parseFloat( value.replace( 'px', '' ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function newInliner() {
|
||
|
var URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/g;
|
||
|
|
||
|
return {
|
||
|
inlineAll: inlineAll,
|
||
|
shouldProcess: shouldProcess,
|
||
|
impl: {
|
||
|
readUrls: readUrls,
|
||
|
inline: inline
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function shouldProcess( string ) {
|
||
|
return string.search( URL_REGEX ) !== -1;
|
||
|
}
|
||
|
|
||
|
function readUrls( string ) {
|
||
|
var result = [];
|
||
|
var match;
|
||
|
while ( (match = URL_REGEX.exec( string )) !== null ) {
|
||
|
result.push( match[ 1 ] );
|
||
|
}
|
||
|
return result.filter( function ( url ) {
|
||
|
return ! util.isDataUrl( url );
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
function inline( string, url, baseUrl, get ) {
|
||
|
return Promise.resolve( url )
|
||
|
.then( function ( url ) {
|
||
|
return baseUrl ? util.resolveUrl( url, baseUrl ) : url;
|
||
|
} )
|
||
|
.then( get || util.getAndEncode )
|
||
|
.then( function ( data ) {
|
||
|
return util.dataAsUrl( data, util.mimeType( url ) );
|
||
|
} )
|
||
|
.then( function ( dataUrl ) {
|
||
|
return string.replace( urlAsRegex( url ), '$1' + dataUrl + '$3' );
|
||
|
} );
|
||
|
|
||
|
function urlAsRegex( url ) {
|
||
|
return new RegExp( '(url\\([\'"]?)(' + util.escape( url ) + ')([\'"]?\\))', 'g' );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function inlineAll( string, baseUrl, get ) {
|
||
|
if ( nothingToInline() ) return Promise.resolve( string );
|
||
|
|
||
|
return Promise.resolve( string )
|
||
|
.then( readUrls )
|
||
|
.then( function ( urls ) {
|
||
|
var done = Promise.resolve( string );
|
||
|
urls.forEach( function ( url ) {
|
||
|
done = done.then( function ( string ) {
|
||
|
return inline( string, url, baseUrl, get );
|
||
|
} );
|
||
|
} );
|
||
|
return done;
|
||
|
} );
|
||
|
|
||
|
function nothingToInline() {
|
||
|
return ! shouldProcess( string );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function newFontFaces() {
|
||
|
return {
|
||
|
resolveAll: resolveAll,
|
||
|
impl: {
|
||
|
readAll: readAll
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function resolveAll() {
|
||
|
return readAll( document )
|
||
|
.then( function ( webFonts ) {
|
||
|
return Promise.all(
|
||
|
webFonts.map( function ( webFont ) {
|
||
|
return webFont.resolve();
|
||
|
} )
|
||
|
);
|
||
|
} )
|
||
|
.then( function ( cssStrings ) {
|
||
|
return cssStrings.join( '\n' );
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
function readAll() {
|
||
|
return Promise.resolve( util.asArray( document.styleSheets ) )
|
||
|
.then( getCssRules )
|
||
|
.then( selectWebFontRules )
|
||
|
.then( function ( rules ) {
|
||
|
return rules.map( newWebFont );
|
||
|
} );
|
||
|
|
||
|
function selectWebFontRules( cssRules ) {
|
||
|
return cssRules
|
||
|
.filter( function ( rule ) {
|
||
|
return rule.type === CSSRule.FONT_FACE_RULE;
|
||
|
} )
|
||
|
.filter( function ( rule ) {
|
||
|
return inliner.shouldProcess( rule.style.getPropertyValue( 'src' ) );
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
function getCssRules( styleSheets ) {
|
||
|
var cssRules = [];
|
||
|
styleSheets.forEach( function ( sheet ) {
|
||
|
try {
|
||
|
util.asArray( sheet.cssRules || [] ).forEach( cssRules.push.bind( cssRules ) );
|
||
|
} catch ( e ) {
|
||
|
console.log( 'Error while reading CSS rules from ' + sheet.href, e.toString() );
|
||
|
}
|
||
|
} );
|
||
|
return cssRules;
|
||
|
}
|
||
|
|
||
|
function newWebFont( webFontRule ) {
|
||
|
return {
|
||
|
resolve: function resolve() {
|
||
|
var baseUrl = (webFontRule.parentStyleSheet || {}).href;
|
||
|
return inliner.inlineAll( webFontRule.cssText, baseUrl );
|
||
|
},
|
||
|
src: function () {
|
||
|
return webFontRule.style.getPropertyValue( 'src' );
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function newImages() {
|
||
|
return {
|
||
|
inlineAll: inlineAll,
|
||
|
impl: {
|
||
|
newImage: newImage
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function newImage( element ) {
|
||
|
return {
|
||
|
inline: inline
|
||
|
};
|
||
|
|
||
|
function inline( get ) {
|
||
|
if ( util.isDataUrl( element.src ) ) return Promise.resolve();
|
||
|
|
||
|
return Promise.resolve( element.src )
|
||
|
.then( get || util.getAndEncode )
|
||
|
.then( function ( data ) {
|
||
|
return util.dataAsUrl( data, util.mimeType( element.src ) );
|
||
|
} )
|
||
|
.then( function ( dataUrl ) {
|
||
|
return new Promise( function ( resolve, reject ) {
|
||
|
element.onload = resolve;
|
||
|
element.onerror = reject;
|
||
|
element.src = dataUrl;
|
||
|
} );
|
||
|
} );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function inlineAll( node ) {
|
||
|
if ( ! (node instanceof Element) ) return Promise.resolve( node );
|
||
|
|
||
|
return inlineBackground( node )
|
||
|
.then( function () {
|
||
|
if ( node instanceof HTMLImageElement )
|
||
|
return newImage( node ).inline();
|
||
|
else
|
||
|
return Promise.all(
|
||
|
util.asArray( node.childNodes ).map( function ( child ) {
|
||
|
return inlineAll( child );
|
||
|
} )
|
||
|
);
|
||
|
} );
|
||
|
|
||
|
function inlineBackground( node ) {
|
||
|
var background = node.style.getPropertyValue( 'background' );
|
||
|
|
||
|
if ( ! background ) return Promise.resolve( node );
|
||
|
|
||
|
return inliner.inlineAll( background )
|
||
|
.then( function ( inlined ) {
|
||
|
node.style.setProperty(
|
||
|
'background',
|
||
|
inlined,
|
||
|
node.style.getPropertyPriority( 'background' )
|
||
|
);
|
||
|
} )
|
||
|
.then( function () {
|
||
|
return node;
|
||
|
} );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
})( this );
|