dom-to-image.js

dom-to-image文件

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greatest.deepsurf.us/scripts/448541/1074759/dom-to-imagejs.js

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