https://rondocoach.com/wp-content/plugins/formationTool/assets/js1/dom-to-image-more.js?ver=12:29:12
1 2 'use strict'; 3 4 const util = newUtil(); 5 const inliner = newInliner(); 6 const fontFaces = newFontFaces(); 7 const images = newImages(); 8 9 // Default impl options 10 const defaultOptions = { 11 // Default is to copy default styles of elements 12 copyDefaultStyles: true, 13 // Default is to fail on error, no placeholder 14 imagePlaceholder: undefined, 15 // Default cache bust is false, it will use the cache 16 cacheBust: false, 17 // Use (existing) authentication credentials for external URIs (CORS requests) 18 useCredentials: false, 19 // Default resolve timeout 20 httpTimeout: 30000, 21 // Style computation cache tag rules (options are strict, relaxed) 22 styleCaching: 'strict', 23 }; 24 25 const domtoimage = { 26 toSvg: toSvg, 27 toPng: toPng, 28 toJpeg: toJpeg, 29 toBlob: toBlob, 30 toPixelData: toPixelData, 31 toCanvas: toCanvas, 32 impl: { 33 fontFaces: fontFaces, 34 images: images, 35 util: util, 36 inliner: inliner, 37 urlCache: [], 38 options: {}, 39 }, 40 }; 41 42 //if (typeof exports === 'object' && typeof module === 'object') { 43 // module.exports = domtoimage; // eslint-disable-line no-undef 44 //} else { 45 // global.domtoimage = domtoimage; 46 //} 47 48 // support node and browsers 49 const ELEMENT_NODE = 50 (typeof Node !== 'undefined' ? Node.ELEMENT_NODE : undefined) || 1; 51 const getComputedStyle = 52 (typeof global !== 'undefined' ? global.getComputedStyle : undefined) || 53 (typeof window !== 'undefined' ? window.getComputedStyle : undefined) || 54 globalThis.getComputedStyle; 55 const atob = 56 (typeof global !== 'undefined' ? global.atob : undefined) || 57 (typeof window !== 'undefined' ? window.atob : undefined) || 58 globalThis.atob; 59 60 /** 61 * @param {Node} node - The DOM Node object to render 62 * @param {Object} options - Rendering options 63 * @param {Function} options.filter - Should return true if passed node should be included in the output 64 * (excluding node means excluding it's children as well). Not called on the root node. 65 * @param {Function} options.onclone - Callback function which is called when the Document has been cloned for 66 * rendering, can be used to modify the contents that will be rendered without affecting the original 67 * source document. 68 * @param {String} options.bgcolor - color for the background, any valid CSS color value. 69 * @param {Number} options.width - width to be applied to node before rendering. 70 * @param {Number} options.height - height to be applied to node before rendering. 71 * @param {Object} options.style - an object whose properties to be copied to node's style before rendering. 72 * @param {Number} options.quality - a Number between 0 and 1 indicating image quality (applicable to JPEG only), 73 defaults to 1.0. 74 * @param {Number} options.scale - a Number multiplier to scale up the canvas before rendering to reduce fuzzy images, defaults to 1.0. 75 * @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 76 * @param {Boolean} options.cacheBust - set to true to cache bust by appending the time to the request url 77 * @param {String} options.styleCaching - set to 'strict', 'relaxed' to select style caching rules 78 * @param {Boolean} options.copyDefaultStyles - set to false to disable use of default styles of elements 79 * @return {Promise} - A promise that is fulfilled with a SVG image data URL 80 * */ 81 function toSvg(node, options) { 82 const ownerWindow = domtoimage.impl.util.getWindow(node); 83 options = options || {}; 84 copyOptions(options); 85 let restorations = []; 86 return Promise.resolve(node) 87 .then(ensureElement) 88 .then(function (clonee) { 89 return cloneNode(clonee, options, null, ownerWindow); 90 }) 91 .then(embedFonts) 92 .then(inlineImages) 93 .then(applyOptions) 94 .then(makeSvgDataUri) 95 .then(restoreWrappers) 96 .then(clearCache); 97 98 function ensureElement(node) { 99 if (node.nodeType === ELEMENT_NODE) return node; 100 101 const originalChild = node; 102 const originalParent = node.parentNode; 103 const wrappingSpan = document.createElement('span'); 104 originalParent.replaceChild(wrappingSpan, originalChild); 105 wrappingSpan.append(node); 106 restorations.push({ 107 parent: originalParent, 108 child: originalChild, 109 wrapper: wrappingSpan, 110 }); 111 return wrappingSpan; 112 } 113 114 function restoreWrappers(result) { 115 // put the original children back where the wrappers were inserted 116 while (restorations.length > 0) { 117 const restoration = restorations.pop(); 118 restoration.parent.replaceChild(restoration.child, restoration.wrapper); 119 } 120 121 return result; 122 } 123 124 function clearCache(result) { 125 domtoimage.impl.urlCache = []; 126 removeSandbox();
127 return result; 128 } 129 130 function applyOptions(clone) { 131 if (options.bgcolor) { 132 clone.style.backgroundColor = options.bgcolor; 133 } 134 if (options.width) { 135 clone.style.width = `${options.width}px`; 136 } 137 if (options.height) { 138 clone.style.height = `${options.height}px`; 139 } 140 if (options.style) { 141 Object.keys(options.style).forEach(function (property) { 142 clone.style[property] = options.style[property]; 143 }); 144 } 145 146 let onCloneResult = null; 147 148 if (typeof options.onclone === 'function') { 149 onCloneResult = options.onclone(clone); 150 } 151 152 return Promise.resolve(onCloneResult).then(function () { 153 return clone; 154 }); 155 } 156 157 function makeSvgDataUri(node) { 158 let width = options.width || util.width(node); 159 let height = options.height || util.height(node); 160 161 return Promise.resolve(node) 162 .then(function (svg) { 163 svg.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); 164 return new XMLSerializer().serializeToString(svg); 165 }) 166 .then(function (xhtml) { 167 const foreignObjectSizing = 168 (util.isDimensionMissing(width) 169 ? ' width="100%"' 170 : ` width="${width}"`) + 171 (util.isDimensionMissing(height) 172 ? ' height="100%"' 173 : ` height="${height}"`); 174 const svgSizing = 175 (util.isDimensionMissing(width) ? '' : ` width="${width}"`) + 176 (util.isDimensionMissing(height) ? '' : ` height="${height}"`); 177 return `<svg xmlns="http://www.w3.org/2000/svg"${svgSizing}><foreignObject${foreignObjectSizing}>${xhtml}</foreignObject></svg>`; 178 }) 179 .then(function (svg) { 180 return `data:image/svg+xml;charset=utf-8,${svg}`; 181 }); 182 } 183 } 184 185 /** 186 * @param {Node} node - The DOM Node object to render 187 * @param {Object} options - Rendering options, @see {@link toSvg} 188 * @return {Promise} - A promise that is fulfilled with a Uint8Array containing RGBA pixel data. 189 * */ 190 function toPixelData(node, options) { 191 return draw(node, options).then(function (canvas) { 192 return canvas 193 .getContext('2d') 194 .getImageData(0, 0, util.width(node), util.height(node)).data; 195 }); 196 } 197 198 /** 199 * @param {Node} node - The DOM Node object to render 200 * @param {Object} options - Rendering options, @see {@link toSvg} 201 * @return {Promise} - A promise that is fulfilled with a PNG image data URL 202 * */ 203 function toPng(node, options) { 204 return draw(node, options).then(function (canvas) { 205 return canvas.toDataURL(); 206 }); 207 } 208 209 /** 210 * @param {Node} node - The DOM Node object to render 211 * @param {Object} options - Rendering options, @see {@link toSvg} 212 * @return {Promise} - A promise that is fulfilled with a JPEG image data URL 213 * */ 214 function toJpeg(node, options) { 215 return draw(node, options).then(function (canvas) { 216 return canvas.toDataURL( 217 'image/jpeg', 218 (options ? options.quality : undefined) || 1.0 219 ); 220 }); 221 } 222 223 /** 224 * @param {Node} node - The DOM Node object to render 225 * @param {Object} options - Rendering options, @see {@link toSvg} 226 * @return {Promise} - A promise that is fulfilled with a PNG image blob 227 * */ 228 function toBlob(node, options) { 229 return draw(node, options).then(util.canvasToBlob); 230 } 231 232 /** 233 * @param {Node} node - The DOM Node object to render
234 * @param {Object} options - Rendering options, @see {@link toSvg} 235 * @return {Promise} - A promise that is fulfilled with a canvas object 236 * */ 237 function toCanvas(node, options) { 238 return draw(node, options); 239 } 240 241 function copyOptions(options) { 242 // Copy options to impl options for use in impl 243 if (typeof options.copyDefaultStyles === 'undefined') { 244 domtoimage.impl.options.copyDefaultStyles = defaultOptions.copyDefaultStyles; 245 } else { 246 domtoimage.impl.options.copyDefaultStyles = options.copyDefaultStyles; 247 } 248 249 if (typeof options.imagePlaceholder === 'undefined') { 250 domtoimage.impl.options.imagePlaceholder = defaultOptions.imagePlaceholder; 251 } else { 252 domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder; 253 } 254 255 if (typeof options.cacheBust === 'undefined') { 256 domtoimage.impl.options.cacheBust = defaultOptions.cacheBust; 257 } else { 258 domtoimage.impl.options.cacheBust = options.cacheBust; 259 } 260 261 if (typeof options.useCredentials === 'undefined') { 262 domtoimage.impl.options.useCredentials = defaultOptions.useCredentials; 263 } else { 264 domtoimage.impl.options.useCredentials = options.useCredentials; 265 } 266 267 if (typeof options.httpTimeout === 'undefined') { 268 domtoimage.impl.options.httpTimeout = defaultOptions.httpTimeout; 269 } else { 270 domtoimage.impl.options.httpTimeout = options.httpTimeout; 271 } 272 273 if (typeof options.styleCaching === 'undefined') { 274 domtoimage.impl.options.styleCaching = defaultOptions.styleCaching; 275 } else { 276 domtoimage.impl.options.styleCaching = options.styleCaching; 277 } 278 } 279 280 function draw(domNode, options) { 281 options = options || {}; 282 return toSvg(domNode, options) 283 .then(util.makeImage) 284 .then(function (image) { 285 const scale = typeof options.scale !== 'number' ? 1 : options.scale; 286 const canvas = newCanvas(domNode, scale); 287 const ctx = canvas.getContext('2d'); 288 ctx.msImageSmoothingEnabled = false; 289 ctx.imageSmoothingEnabled = false; 290 if (image) { 291 ctx.scale(scale, scale); 292 ctx.drawImage(image, 0, 0); 293 } 294 return canvas; 295 }); 296 297 function newCanvas(node, scale) { 298 let width = options.width || util.width(node); 299 let height = options.height || util.height(node); 300 301 // per https://www.w3.org/TR/CSS2/visudet.html#inline-replaced-width the default width should be 300px if height 302 // not set, otherwise should be 2:1 aspect ratio for whatever height is specified 303 if (util.isDimensionMissing(width)) { 304 width = util.isDimensionMissing(height) ? 300 : height * 2.0; 305 } 306 307 if (util.isDimensionMissing(height)) { 308 height = width / 2.0; 309 } 310 311 const canvas = document.createElement('canvas'); 312 canvas.width = width * scale; 313 canvas.height = height * scale; 314 315 if (options.bgcolor) { 316 const ctx = canvas.getContext('2d'); 317 ctx.fillStyle = options.bgcolor; 318 ctx.fillRect(0, 0, canvas.width, canvas.height); 319 } 320 321 return canvas; 322 } 323 } 324 325 let sandbox = null; 326 327 function cloneNode(node, options, parentComputedStyles, ownerWindow) { 328 const filter = options.filter; 329 if ( 330 node === sandbox || 331 util.isHTMLScriptElement(node) || 332 util.isHTMLStyleElement(node) || 333 util.isHTMLLinkElement(node) || 334 (parentComputedStyles !== null && filter && !filter(node)) 335 ) { 336 return Promise.resolve(); 337 } 338 339 return Promise.resolve(node) 340 .then(makeNodeCopy) 341 .then(function (clone) { 342 return cloneChildren(clone, getParentOfChildren(node));
343 }) 344 .then(function (clone) { 345 return processClone(clone, node); 346 }); 347 348 function makeNodeCopy(original) { 349 if (util.isHTMLCanvasElement(original)) { 350 return util.makeImage(original.toDataURL()); 351 } 352 return original.cloneNode(false); 353 } 354 355 function getParentOfChildren(original) { 356 if (util.isElementHostForOpenShadowRoot(original)) { 357 return original.shadowRoot; // jump "down" to #shadow-root 358 } 359 return original; 360 } 361 362 function cloneChildren(clone, original) { 363 const originalChildren = getRenderedChildren(original); 364 let done = Promise.resolve(); 365 366 if (originalChildren.length !== 0) { 367 const originalComputedStyles = getComputedStyle( 368 getRenderedParent(original) 369 ); 370 371 util.asArray(originalChildren).forEach(function (originalChild) { 372 done = done.then(function () { 373 return cloneNode( 374 originalChild, 375 options, 376 originalComputedStyles, 377 ownerWindow 378 ).then(function (clonedChild) { 379 if (clonedChild) { 380 clone.appendChild(clonedChild); 381 } 382 }); 383 }); 384 }); 385 } 386 387 return done.then(function () { 388 return clone; 389 }); 390 391 function getRenderedParent(original) { 392 if (util.isShadowRoot(original)) { 393 return original.host; // jump up from #shadow-root to its parent <element> 394 } 395 return original; 396 } 397 398 function getRenderedChildren(original) { 399 if (util.isShadowSlotElement(original)) { 400 return original.assignedNodes(); // shadow DOM <slot> has "assigned nodes" as rendered children 401 } 402 return original.childNodes; 403 } 404 } 405 406 function processClone(clone, original) { 407 if (!util.isElement(clone) || util.isShadowSlotElement(original)) { 408 return Promise.resolve(clone); 409 } 410 411 return Promise.resolve() 412 .then(cloneStyle) 413 .then(clonePseudoElements) 414 .then(copyUserInput) 415 .then(fixSvg) 416 .then(function () { 417 return clone; 418 }); 419 420 function cloneStyle() { 421 copyStyle(original, clone); 422 423 function copyFont(source, target) { 424 target.font = source.font; 425 target.fontFamily = source.fontFamily; 426 target.fontFeatureSettings = source.fontFeatureSettings; 427 target.fontKerning = source.fontKerning; 428 target.fontSize = source.fontSize; 429 target.fontStretch = source.fontStretch; 430 target.fontStyle = source.fontStyle; 431 target.fontVariant = source.fontVariant; 432 target.fontVariantCaps = source.fontVariantCaps; 433 target.fontVariantEastAsian = source.fontVariantEastAsian; 434 target.fontVariantLigatures = source.fontVariantLigatures; 435 target.fontVariantNumeric = source.fontVariantNumeric; 436 target.fontVariationSettings = source.fontVariationSettings; 437 target.fontWeight = source.fontWeight; 438 } 439 440 function copyStyle(sourceElement, targetElement) { 441 const sourceComputedStyles = getComputedStyle(sourceElement); 442 if (sourceComputedStyles.cssText) { 443 targetElement.style.cssText = sourceComputedStyles.cssText; 444 copyFont(sourceComputedStyles, targetElement.style); // here we re-assign the font props. 445 } else { 446 copyUserComputedStyleFast( 447 options, 448 sourceElement, 449 sourceComputedStyles, 450 parentComputedStyles, 451 targetElement 452 ); 453 454 // Remove positioning of initial element, which stops them from being captured correctly 455 if (parentComputedStyles === null) { 456 [ 457 'inset-block', 458 'inset-block-start', 459 'inset-block-end', 460 ].forEach((prop) => targetElement.style.removeProperty(prop)); 461 ['left', 'right', 'top', 'bottom'].forEach((prop) => { 462 if (targetElement.style.getPropertyValue(prop)) { 463 targetElement.style.setProperty(prop, '0px'); 464 } 465 }); 466 } 467 } 468 } 469 } 470 471 function clonePseudoElements() { 472 const cloneClassName = util.uid(); 473
474 [':before', ':after'].forEach(function (element) { 475 clonePseudoElement(element); 476 }); 477 478 function clonePseudoElement(element) { 479 const style = getComputedStyle(original, element); 480 const content = style.getPropertyValue('content'); 481 482 if (content === '' || content === 'none') { 483 return; 484 } 485 486 const currentClass = clone.getAttribute('class') || ''; 487 clone.setAttribute('class', `${currentClass} ${cloneClassName}`); 488 489 const styleElement = document.createElement('style'); 490 styleElement.appendChild(formatPseudoElementStyle()); 491 clone.appendChild(styleElement); 492 493 function formatPseudoElementStyle() { 494 const selector = `.${cloneClassName}:${element}`; 495 const cssText = style.cssText 496 ? formatCssText() 497 : formatCssProperties(); 498 499 return document.createTextNode(`${selector}{${cssText}}`); 500 501 function formatCssText() { 502 return `${style.cssText} content: ${content};`; 503 } 504 505 function formatCssProperties() { 506 const styleText = util 507 .asArray(style) 508 .map(formatProperty) 509 .join('; '); 510 return `${styleText};`; 511 512 function formatProperty(name) { 513 const propertyValue = style.getPropertyValue(name); 514 const propertyPriority = style.getPropertyPriority(name) 515 ? ' !important' 516 : ''; 517 return `${name}: ${propertyValue}${propertyPriority}`; 518 } 519 } 520 } 521 } 522 } 523 524 function copyUserInput() { 525 if (util.isHTMLTextAreaElement(original)) { 526 clone.innerHTML = original.value; 527 } 528 if (util.isHTMLInputElement(original)) { 529 clone.setAttribute('value', original.value); 530 } 531 } 532 533 function fixSvg() { 534 if (util.isSVGElement(clone)) { 535 clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); 536 537 if (util.isSVGRectElement(clone)) { 538 ['width', 'height'].forEach(function (attribute) { 539 const value = clone.getAttribute(attribute); 540 if (value) { 541 clone.style.setProperty(attribute, value); 542 } 543 }); 544 } 545 } 546 } 547 } 548 } 549 550 function embedFonts(node) { 551 return fontFaces.resolveAll().then(function (cssText) { 552 if (cssText !== '') { 553 const styleNode = document.createElement('style'); 554 node.appendChild(styleNode); 555 styleNode.appendChild(document.createTextNode(cssText)); 556 } 557 return node; 558 }); 559 } 560 561 function inlineImages(node) { 562 return images.inlineAll(node).then(function () { 563 return node; 564 }); 565 } 566 function newUtil() { 567 let uid_index = 0; 568 569 return { 570 escape: escapeRegEx, 571 isDataUrl: isDataUrl, 572 canvasToBlob: canvasToBlob, 573 resolveUrl: resolveUrl, 574 getAndEncode: getAndEncode, 575 uid: uid, 576 delay: delay, 577 asArray: asArray, 578 escapeXhtml: escapeXhtml, 579 makeImage: makeImage, 580 width: width, 581 height: height,
582 getWindow: getWindow, 583 isElement: isElement, 584 isElementHostForOpenShadowRoot: isElementHostForOpenShadowRoot, 585 isShadowRoot: isShadowRoot, 586 isInShadowRoot: isInShadowRoot, 587 isHTMLElement: isHTMLElement, 588 isHTMLCanvasElement: isHTMLCanvasElement, 589 isHTMLInputElement: isHTMLInputElement, 590 isHTMLImageElement: isHTMLImageElement, 591 isHTMLLinkElement: isHTMLLinkElement, 592 isHTMLScriptElement: isHTMLScriptElement, 593 isHTMLStyleElement: isHTMLStyleElement, 594 isHTMLTextAreaElement: isHTMLTextAreaElement, 595 isShadowSlotElement: isShadowSlotElement, 596 isSVGElement: isSVGElement, 597 isSVGRectElement: isSVGRectElement, 598 isDimensionMissing: isDimensionMissing, 599 }; 600 601 function getWindow(node) { 602 const ownerDocument = node ? node.ownerDocument : undefined; 603 return ( 604 (ownerDocument ? ownerDocument.defaultView : undefined) || 605 global || 606 window 607 ); 608 } 609 610 function isElementHostForOpenShadowRoot(value) { 611 return isElement(value) && value.shadowRoot !== null; 612 } 613 614 function isShadowRoot(value) { 615 return value instanceof getWindow(value).ShadowRoot; 616 } 617 618 function isInShadowRoot(value) { 619 return ( 620 value !== null && 621 Object.prototype.hasOwnProperty.call(value, 'getRootNode') && 622 isShadowRoot(value.getRootNode()) 623 ); 624 } 625 626 function isElement(value) { 627 return value instanceof getWindow(value).Element; 628 } 629 630 function isHTMLCanvasElement(value) { 631 return value instanceof getWindow(value).HTMLCanvasElement; 632 } 633 634 function isHTMLElement(value) { 635 return value instanceof getWindow(value).HTMLElement; 636 } 637 638 function isHTMLImageElement(value) { 639 return value instanceof getWindow(value).HTMLImageElement; 640 } 641 642 function isHTMLInputElement(value) { 643 return value instanceof getWindow(value).HTMLInputElement; 644 } 645 646 function isHTMLLinkElement(value) { 647 return value instanceof getWindow(value).HTMLLinkElement; 648 } 649 650 function isHTMLScriptElement(value) { 651 return value instanceof getWindow(value).HTMLScriptElement; 652 } 653 654 function isHTMLStyleElement(value) { 655 return value instanceof getWindow(value).HTMLStyleElement; 656 } 657 658 function isHTMLTextAreaElement(value) { 659 return value instanceof getWindow(value).HTMLTextAreaElement; 660 } 661 662 function isShadowSlotElement(value) { 663 return ( 664 isInShadowRoot(value) && value instanceof getWindow(value).HTMLSlotElement 665 ); 666 } 667 668 function isSVGElement(value) { 669 return value instanceof getWindow(value).SVGElement; 670 } 671 672 function isSVGRectElement(value) { 673 return value instanceof getWindow(value).SVGRectElement; 674 } 675 676 function isDataUrl(url) { 677 return url.search(/^(data:)/) !== -1; 678 } 679 680 function isDimensionMissing(value) { 681 return isNaN(value) || value <= 0; 682 } 683 684 function asBlob(canvas) { 685 return new Promise(function (resolve) { 686 const binaryString = atob(canvas.toDataURL().split(',')[1]); 687 const length = binaryString.length; 688 const binaryArray = new Uint8Array(length); 689 690 for (let i = 0; i < length; i++) { 691 binaryArray[i] = binaryString.charCodeAt(i); 692 } 693 694 resolve( 695 new Blob([binaryArray], { 696 type: 'image/png', 697 }) 698 ); 699 }); 700 } 701 702 function canvasToBlob(canvas) { 703 if (canvas.toBlob) { 704 return new Promise(function (resolve) { 705 canvas.toBlob(resolve); 706 }); 707 } 708 709 return asBlob(canvas); 710 } 711 712 function resolveUrl(url, baseUrl) { 713 const doc = document.implementation.createHTMLDocument(); 714 const base = doc.createElement('base'); 715 doc.head.appendChild(base); 716 const a = doc.createElement('a'); 717 doc.body.appendChild(a); 718 base.href = baseUrl; 719 a.href = url; 720 return a.href; 721 } 722 723 function uid() { 724 return `u${fourRandomChars()}${uid_index++}`; 725 726 function fourRandomChars() { 727 /* see https://stackoverflow.com/a/6248722/2519373 */ 728 return `0000${((Math.random() * Math.pow(36, 4)) << 0).toString( 729 36 730 )}`.slice(-4); 731 } 732 } 733 734 function makeImage(uri) { 735 if (uri === 'data:,') { 736 return Promise.resolve(); 737 } 738 739 return new Promise(function (resolve, reject) { 740 const image = new Image(); 741 if (domtoimage.impl.options.useCredentials) { 742 image.crossOrigin = 'use-credentials'; 743 } 744 image.onload = function () { 745 746 if (window && window.requestAnimationFrame) { 747 // In order to work around a Firefox bug (webcompat/web-bugs#119834) we 748 // need to wait one extra frame before it's s
748afe to read the image data. 749 window.requestAnimationFrame(function () { 750 resolve(image); 751 }); 752 } else { 753 // If we don't have a window or requestAnimationFrame function proceed immediately. 754 resolve(image); 755 } 756 }; 757 758 image.onerror = reject; 759 image.src = uri; 760 }); 761 } 762 763 function getAndEncode(url) { 764 let cacheEntry = domtoimage.impl.urlCache.find(function (el) { 765 return el.url === url; 766 }); 767 768 if (!cacheEntry) { 769 cacheEntry = { 770 url: url, 771 promise: null, 772 }; 773 domtoimage.impl.urlCache.push(cacheEntry); 774 } 775 776 if (cacheEntry.promise === null) { 777 if (domtoimage.impl.options.cacheBust) { 778 // Cache bypass so we dont have CORS issues with cached images 779 // Source: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache 780 url += (/\?/.test(url) ? '&' : '?') + new Date().getTime(); 781 } 782 783 cacheEntry.promise = new Promise(function (resolve) { 784 const httpTimeout = domtoimage.impl.options.httpTimeout; 785 const request = new XMLHttpRequest(); 786 787 request.onreadystatechange = done; 788 request.ontimeout = timeout; 789 request.responseType = 'blob'; 790 request.timeout = httpTimeout; 791 if (domtoimage.impl.options.useCredentials) { 792 request.withCredentials = true; 793 } 794 request.open('GET', url, true); 795 request.send(); 796 797 let placeholder; 798 if (domtoimage.impl.options.imagePlaceholder) { 799 const split = domtoimage.impl.options.imagePlaceholder.split(/,/); 800 if (split && split[1]) { 801 placeholder = split[1]; 802 } 803 } 804 805 function done() { 806 if (request.readyState !== 4) { 807 return; 808 } 809 810 if (request.status !== 200) { 811 if (placeholder) { 812 resolve(placeholder); 813 } else { 814 fail( 815 `cannot fetch resource: ${url}, status: ${request.status}` 816 ); 817 } 818 819 return; 820 } 821 822 const encoder = new FileReader(); 823 encoder.onloadend = function () { 824 resolve(encoder.result); 825 }; 826 encoder.readAsDataURL(request.response); 827 } 828 829 function timeout() { 830 if (placeholder) { 831 resolve(placeholder); 832 } else { 833 fail( 834 `timeout of ${httpTimeout}ms occured while fetching resource: ${url}` 835 ); 836 } 837 } 838 839 function fail(message) { 840 console.error(message); 841 resolve(''); 842 } 843 }); 844 } 845 return cacheEntry.promise; 846 } 847 848 function escapeRegEx(string) { 849 return string.replace(/([.*+?^${}()|[]\/\\])/g, '\\$1'); 850 } 851 852 function delay(ms) { 853 return function (arg) { 854 return new Promise(function (resolve) { 855 setTimeout(function () { 856 resolve(arg); 857 }, ms); 858 }); 859 }; 860 } 861 862 function asArray(arrayLike) { 863 const array = []; 864 const length = arrayLike.length; 865 for (let i = 0; i < length; i++) { 866 array.push(arrayLike[i]); 867 } 868 869 return array; 870 } 871 872 function escapeXhtml(string) { 873 return string.replace(/%/g, '%25').replace(/#/g, '%23').replace(/\n/g, '%0A'); 874 } 875 876 function width(node) { 877 const width = px(node, 'width'); 878 879 if (!isNaN(width)) return width; 880 881 const leftBorder = px(node, 'border-left-width'); 882 const rightBorder = px(node, 'border-right-width'); 883 return node.scrollWidth + leftBorder + rightBorder; 884 } 885 886 function height(node) { 887 const height = px(node, 'height'); 888 889 if (!isNaN(height)) return height; 890 891 const topBorder = px(node, 'border-top-width'); 892 const bottomBorder = px(node, 'border-bottom-width'); 893 return node.scrollHeight + topBorder + bottomBorder; 894 } 895 896 function px(node, styleProperty) { 897 if (node.nodeType === ELEMENT_NODE) { 898 let value = getComputedStyle(node).getPropertyValue(styleProperty); 899 if (value.slice(-2) === 'px') { 900 value = value.slice(0, -2); 901 return parseFloat(value); 902 } 903 } 904 905 return NaN; 906 } 907 } 908 909 function newInliner() { 910 const URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/g; 911 912 return { 913 inlineAll: inlineAll, 914 shouldProcess: shouldProcess, 915 impl: { 916 readUrls: readUrls, 917 inline: inline, 918 }, 919 }; 920 921 function shouldProcess(string) { 922 return string.search(URL_REGEX) !== -1; 923 } 924 925 function readUrls(string) { 926 const result = []; 927 let match; 928 while ((match = URL_REGEX.exec(string)) !== null) { 929 result.push(match[1]); 930 } 931 return result.filter(function (url) { 932 return !util.isDataUrl(url); 933 }); 934 } 935 936 function inline(string, url, baseUrl, get) { 937 return Promise.resolve(url) 938 .then(function (urlValue) { 939 return baseUrl ? util.resolveUrl(urlValue, baseUrl) : urlValue; 940 }) 941 .then(get || util.getAndEncode) 942 .then(function (dataUrl) { 943 return string.replace(urlAsRegex(url), `$1${dataUrl}$3`); 944 }); 945 946 function urlAsRegex(urlValue) { 947 return new RegExp( 948 `(url\\(['"]?)(${util.escape(urlValue)})(['"]?\\))`, 949 'g' 950 ); 951 } 952 } 953 954 function inlineAll(string, baseUrl, get) { 955 if (nothingToInline()) { 956 return Promise.resolve(string); 957 } 958 959 return Promise.resolve(string) 960 .then(readUrls) 961 .then(function (urls) { 962 let done = Promise.resolve(string);
963 urls.forEach(function (url) { 964 done = done.then(function (prefix) { 965 return inline(prefix, url, baseUrl, get); 966 }); 967 }); 968 return done; 969 }); 970 971 function nothingToInline() { 972 return !shouldProcess(string); 973 } 974 } 975 } 976 977 function newFontFaces() { 978 return { 979 resolveAll: resolveAll, 980 impl: { 981 readAll: readAll, 982 }, 983 }; 984 985 function resolveAll() { 986 return readAll() 987 .then(function (webFonts) { 988 return Promise.all( 989 webFonts.map(function (webFont) { 990 return webFont.resolve(); 991 }) 992 ); 993 }) 994 .then(function (cssStrings) { 995 return cssStrings.join('\n'); 996 }); 997 } 998
999 function readAll() { 1000 return Promise.resolve(util.asArray(document.styleSheets)) 1001 .then(getCssRules) 1002 .then(selectWebFontRules) 1003 .then(function (rules) { 1004 return rules.map(newWebFont); 1005 }); 1006 1007 function selectWebFontRules(cssRules) { 1008 return cssRules 1009 .filter(function (rule) { 1010 return rule.type === CSSRule.FONT_FACE_RULE; 1011 }) 1012 .filter(function (rule) { 1013 return inliner.shouldProcess(rule.style.getPropertyValue('src')); 1014 }); 1015 } 1016 1017 function getCssRules(styleSheets) { 1018 const cssRules = []; 1019 styleSheets.forEach(function (sheet) { 1020 if ( 1021 Object.prototype.hasOwnProperty.call( 1022 Object.getPrototypeOf(sheet), 1023 'cssRules' 1024 ) 1025 ) { 1026 try { 1027 util.asArray(sheet.cssRules || []).forEach( 1028 cssRules.push.bind(cssRules) 1029 ); 1030 } catch (e) { 1031 console.error( 1032 `domtoimage: Error while reading CSS rules from ${sheet.href}`, 1033 e.toString() 1034 ); 1035 } 1036 } 1037 }); 1038 return cssRules; 1039 } 1040 1041 function newWebFont(webFontRule) { 1042 return { 1043 resolve: function resolve() { 1044 const baseUrl = (webFontRule.parentStyleSheet || {}).href; 1045 return inliner.inlineAll(webFontRule.cssText, baseUrl); 1046 }, 1047 src: function () { 1048 return webFontRule.style.getPropertyValue('src'); 1049 }, 1050 }; 1051 } 1052 } 1053 } 1054 1055 function newImages() { 1056 return { 1057 inlineAll: inlineAll, 1058 impl: { 1059 newImage: newImage, 1060 }, 1061 }; 1062 1063 function newImage(element) { 1064 return { 1065 inline: inline, 1066 }; 1067 1068 function inline(get) { 1069 if (util.isDataUrl(element.src)) { 1070 return Promise.resolve(); 1071 } 1072 1073 return Promise.resolve(element.src) 1074 .then(get || util.getAndEncode) 1075 .then(function (dataUrl) { 1076 return new Promise(function (resolve) { 1077 element.onload = resolve; 1078 // for any image with invalid src(such as <img src />), just ignore it 1079 element.onerror = resolve; 1080 element.src = dataUrl; 1081 }); 1082 }); 1083 } 1084 } 1085 1086 function inlineAll(node) { 1087 if (!util.isElement(node)) { 1088 return Promise.resolve(node); 1089 } 1090 1091 return inlineCSSProperty(node).then(function () { 1092 if (util.isHTMLImageElement(node)) { 1093 return newImage(node).inline(); 1094 } else { 1095 return Promise.all( 1096 util.asArray(node.childNodes).map(function (child) { 1097 return inlineAll(child); 1098 }) 1099 ); 1100 } 1101 }); 1102 1103 function inlineCSSProperty(node) { 1104 const properties = ['background', 'background-image']; 1105 1106 const inliningTasks = properties.map(function (propertyName) { 1107 const value = node.style.getPropertyValue(propertyName); 1108 const priority = node.style.getPropertyPriority(propertyName); 1109 1110 if (!value) { 1111 return Promise.resolve(); 1112 } 1113 1114 return inliner.inlineAll(value).then(function (inlinedValue) { 1115 node.style.setProperty(propertyName, inlinedValue, priority); 1116 }); 1117 }); 1118 1119 return Promise.all(inliningTasks).then(function () { 1120 return node; 1121 }); 1122 } 1123 } 1124 } 1125 1126 function setStyleProperty(targetStyle, name, value, priority) { 1127 const needs_prefixing = ['background-clip'].indexOf(name) >= 0; 1128 if (priority) { 1129 targetStyle.setProperty(name, value, priority); 1130 if (needs_prefixing) { 1131 targetStyle.setProperty(`-webkit-${name}`, value, priority); 1132 } 1133 } else { 1134 targetStyle.setProperty(name, value); 1135 if (needs_prefixing) { 1136 targetStyle.setProperty(`-webkit-${name}`, value); 1137 } 1138 } 1139 } 1140 1141 function copyUserComputedStyleFast( 1142 options, 1143 sourceElement, 1144 sourceComputedStyles, 1145 parentComputedStyles, 1146 targetElement 1147 ) { 1148 const defaultStyle = domtoimage.impl.options.copyDefaultStyles 1149 ? getDefaultStyle(options, sourceElement) 1150 : {}; 1151 const targetStyle = targetElement.style; 1152
1153 util.asArray(sourceComputedStyles).forEach(function (name) { 1154 const sourceValue = sourceComputedStyles.getPropertyValue(name); 1155 const defaultValue = defaultStyle[name]; 1156 const parentValue = parentComputedStyles 1157 ? parentComputedStyles.getPropertyValue(name) 1158 : undefined; 1159 1160 // If the style does not match the default, or it does not match the parent's, set it. We don't know which 1161 // styles are inherited from the parent and which aren't, so we have to always check both. 1162 if ( 1163 sourceValue !== defaultValue || 1164 (parentComputedStyles && sourceValue !== parentValue) 1165 ) { 1166 const priority = sourceComputedStyles.getPropertyPriority(name); 1167 setStyleProperty(targetStyle, name, sourceValue, priority); 1168 } 1169 }); 1170 } 1171 1172 let removeDefaultStylesTimeoutId = null; 1173 let tagNameDefaultStyles = {}; 1174 1175 const ascentStoppers = [ 1176 // these come from https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements 1177 'ADDRESS', 1178 'ARTICLE', 1179 'ASIDE', 1180 'BLOCKQUOTE', 1181 'DETAILS', 1182 'DIALOG', 1183 'DD', 1184 'DIV', 1185 'DL', 1186 'DT', 1187 'FIELDSET', 1188 'FIGCAPTION', 1189 'FIGURE', 1190 'FOOTER', 1191 'FORM', 1192 'H1', 1193 'H2', 1194 'H3', 1195 'H4', 1196 'H5', 1197 'H6', 1198 'HEADER', 1199 'HGROUP', 1200 'HR', 1201 'LI', 1202 'MAIN', 1203 'NAV', 1204 'OL', 1205 'P', 1206 'PRE', 1207 'SECTION', 1208 'SVG', 1209 'TABLE', 1210 'UL', 1211 // this is some non-standard ones 1212 'math', // intentionally lowercase, thanks Safari 1213 'svg', // in case we have an svg embedded element 1214 // these are ultimate stoppers in case something drastic changes in how the DOM works 1215 'BODY', 1216 'HEAD', 1217 'HTML', 1218 ]; 1219 1220 function getDefaultStyle(options, sourceElement) { 1221 const tagHierarchy = computeTagHierarchy(sourceElement); 1222 const tagKey = computeTagKey(tagHierarchy); 1223 if (tagNameDefaultStyles[tagKey]) { 1224 return tagNameDefaultStyles[tagKey]; 1225 } 1226 1227 // We haven't cached the answer for that hierachy yet, build a 1228 // sandbox (if not yet created), fill it with the hierarchy that 1229 // matters, and grab the default styles associated 1230 const sandboxWindow = ensureSandboxWindow(); 1231 const defaultElement = constructElementHierachy( 1232 sandboxWindow.document, 1233 tagHierarchy 1234 ); 1235 const defaultStyle = computeStyleForDefaults(sandboxWindow, defaultElement); 1236 destroyElementHierarchy(defaultElement); 1237 1238 tagNameDefaultStyles[tagKey] = defaultStyle; 1239 return defaultStyle; 1240 1241 function computeTagHierarchy(sourceNode) { 1242 const tagNames = []; 1243 1244 do { 1245 if (sourceNode.nodeType === ELEMENT_NODE) { 1246 const tagName = sourceNode.tagName; 1247 tagNames.push(tagName); 1248 1249 if (ascentStoppers.includes(tagName)) { 1250 break; 1251 } 1252 } 1253 1254 sourceNode = sourceNode.parentNode; 1255 } while (sourceNode); 1256 1257 return tagNames; 1258 } 1259 1260 function computeTagKey(tagHierarchy) { 1261 if (options.styleCaching === 'relaxed') { 1262 // pick up only the ascent-stopping element tag and the element tag itself 1263 /* jshint unused:true */ 1264 return tagHierarchy 1265 .filter((_, i, a) => i === 0 || i === a.length - 1) 1266 .join('>'); 1267 } 1268 // for all other cases, fall back the the entire path 1269 return tagHierarchy.join('>'); // it's like CSS 1270 } 1271 1272 function constructElementHierachy(sandboxDocument, tagHierarchy) { 1273 let element = sandboxDocument.body; 1274 do { 1275 const childTagName = tagHierarchy.pop(); 1276 const childElement = sandboxDocument.createElement(childTagName); 1277 element.appendChild(childElement); 1278 element = childElement; 1279 } while (tagHierarchy.length > 0); 1280 1281 // Ensure that there is some content, so that properties like margin are applied. 1282 // we use zero-width space to handle FireFox adding a pixel 1283 element.textContent = '\u200b'; 1284 return element; 1285 } 1286 1287 function computeStyleForDefaults(sandboxWindow, defaultElement) { 1288 const defaultStyle = {}; 1289 const defaultComputedStyle = sandboxWindow.getComputedStyle(defaultElement); 1290 1291 // Copy styles to an object, making sure that 'width' and 'height' are given the default value of 'auto', since 1292 // their initial value is always 'auto' despite that the default computed value is sometimes an absolute length. 1293 util.asArray(defaultComputedStyle).forEach(function (name) { 1294 defaultStyle[name] = 1295 name === 'width' || name === 'height' 1296 ? 'auto' 1297 : defaultComputedStyle.getPropertyValue(name); 1298 }); 1299 return defaultStyle; 1300 } 1301 1302 function destroyElementHierarchy(element) { 1303 do { 1304 const parentElement = element.parentElement; 1305 if (parentElement !== null) { 1306 parentElement.removeChild(element); 1307 } 1308 element = parentElement; 1309 } while (element && element.tagName !== 'BODY'); 1310 } 1311 } 1312 1313 function ensureSandboxWindow() { 1314 if (sandbox) { 1315 return sandbox.contentWindow; 1316 } 1317 1318 // figure out how this document is defined (doctype and charset) 1319 const charsetToUse = document.characterSet || 'UTF-8'; 1320 const docType = document.doctype; 1321 const docTypeDeclaration = docType 1322 ? `<!DOCTYPE ${escapeHTML(docType.name)}
1322 ${escapeHTML( 1323 docType.publicId 1324 )} ${escapeHTML(docType.systemId)}`.trim() + '>' 1325 : ''; 1326 1327 // Create a hidden sandbox <iframe> element within we can create default HTML elements and query their 1328 // computed styles. Elements must be rendered in order to query their computed styles. The <iframe> won't 1329 // render at all with `display: none`, so we have to use `visibility: hidden` with `position: fixed`. 1330 sandbox = document.createElement('iframe'); 1331 sandbox.id = 'domtoimage-sandbox-' + util.uid(); 1332 sandbox.style.visibility = 'hidden'; 1333 sandbox.style.position = 'fixed'; 1334 document.body.appendChild(sandbox); 1335 1336 return tryTechniques( 1337 sandbox, 1338 docTypeDeclaration, 1339 charsetToUse, 1340 'domtoimage-sandbox' 1341 ); 1342 1343 function escapeHTML(unsafeText) { 1344 if (unsafeText) { 1345 const div = document.createElement('div'); 1346 div.innerText = unsafeText; 1347 return div.innerHTML; 1348 } else { 1349 return ''; 1350 } 1351 } 1352 1353 function tryTechniques(sandbox, doctype, charset, title) { 1354 // try the good old-fashioned document write with all the correct attributes set 1355 try { 1356 sandbox.contentWindow.document.write( 1357 `${doctype}<html><head><meta charset='${charset}'><title>${title}</title></head><body></body></html>` 1358 ); 1359 return sandbox.contentWindow; 1360 } catch (_) { 1361 // swallow exception and fall through to next technique 1362 } 1363 1364 const metaCharset = document.createElement('meta'); 1365 metaCharset.setAttribute('charset', charset); 1366 1367 // let's attempt it using srcdoc, so we can still set the doctype and charset 1368 try { 1369 const sandboxDocument = document.implementation.createHTMLDocument(title); 1370 sandboxDocument.head.appendChild(metaCharset); 1371 const sandboxHTML = doctype + sandboxDocument.documentElement.outerHTML; 1372 sandbox.setAttribute('srcdoc', sandboxHTML); 1373 return sandbox.contentWindow; 1374 } catch (_) { 1375 // swallow exception and fall through to the simplest path 1376 } 1377 1378 // let's attempt it using contentDocument... here we're not able to set the doctype 1379 sandbox.contentDocument.head.appendChild(metaCharset); 1380 sandbox.contentDocument.title = title; 1381 return sandbox.contentWindow; 1382 } 1383 } 1384 1385 function removeSandbox() { 1386 if (sandbox) { 1387 document.body.removeChild(sandbox); 1388 sandbox = null; 1389 } 1390 1391 if (removeDefaultStylesTimeoutId) { 1392 clearTimeout(removeDefaultStylesTimeoutId); 1393 } 1394 1395 removeDefaultStylesTimeoutId = setTimeout(() => { 1396 removeDefaultStylesTimeoutId = null; 1397 tagNameDefaultStyles = {}; 1398 }, 20 * 1000); 1399 } 1400 1401 1402 // module.exports = domtoimage; // eslint-disable-line no-undef 1403 export default domtoimage;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.