png.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. define(function(require, exports, module) {
  2. var kity = require('../core/kity');
  3. var data = require('../core/data');
  4. var Promise = require('../core/promise');
  5. var DomURL = window.URL || window.webkitURL || window;
  6. function loadImage(info, callback) {
  7. return new Promise(function(resolve, reject) {
  8. var image = document.createElement("img");
  9. image.onload = function() {
  10. resolve({
  11. element: this,
  12. x: info.x,
  13. y: info.y,
  14. width: info.width,
  15. height: info.height
  16. });
  17. };
  18. image.onerror = function(err) {
  19. reject(err);
  20. };
  21. image.crossOrigin = 'anonymous';
  22. image.src = info.url;
  23. });
  24. }
  25. /**
  26. * xhrLoadImage: 通过 xhr 加载保存在 BOS 上的图片
  27. * @note: BOS 上的 CORS 策略是取 headers 里面的 Origin 字段进行判断
  28. * 而通过 image 的 src 的方式是无法传递 origin 的,因此需要通过 xhr 进行
  29. */
  30. function xhrLoadImage(info, callback) {
  31. return Promise(function (resolve, reject) {
  32. var xmlHttp = new XMLHttpRequest();
  33. xmlHttp.open('GET', info.url + '?_=' + Date.now(), true);
  34. xmlHttp.responseType = 'blob';
  35. xmlHttp.onreadystatechange = function () {
  36. if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
  37. var blob = xmlHttp.response;
  38. var image = document.createElement('img');
  39. image.src = DomURL.createObjectURL(blob);
  40. image.onload = function () {
  41. DomURL.revokeObjectURL(image.src);
  42. resolve({
  43. element: image,
  44. x: info.x,
  45. y: info.y,
  46. width: info.width,
  47. height: info.height
  48. });
  49. };
  50. }
  51. };
  52. xmlHttp.send();
  53. });
  54. }
  55. function getSVGInfo(minder) {
  56. var paper = minder.getPaper(),
  57. paperTransform,
  58. domContainer = paper.container,
  59. svgXml,
  60. svgContainer,
  61. svgDom,
  62. renderContainer = minder.getRenderContainer(),
  63. renderBox = renderContainer.getRenderBox(),
  64. width = renderBox.width + 1,
  65. height = renderBox.height + 1,
  66. blob, svgUrl, img;
  67. // 保存原始变换,并且移动到合适的位置
  68. paperTransform = paper.shapeNode.getAttribute('transform');
  69. paper.shapeNode.setAttribute('transform', 'translate(0.5, 0.5)');
  70. renderContainer.translate(-renderBox.x, -renderBox.y);
  71. // 获取当前的 XML 代码
  72. svgXml = paper.container.innerHTML;
  73. // 回复原始变换及位置
  74. renderContainer.translate(renderBox.x, renderBox.y);
  75. paper.shapeNode.setAttribute('transform', paperTransform);
  76. // 过滤内容
  77. svgContainer = document.createElement('div');
  78. svgContainer.innerHTML = svgXml;
  79. svgDom = svgContainer.querySelector('svg');
  80. svgDom.setAttribute('width', renderBox.width + 1);
  81. svgDom.setAttribute('height', renderBox.height + 1);
  82. svgDom.setAttribute('style', 'font-family: Arial, "Microsoft Yahei","Heiti SC";');
  83. svgContainer = document.createElement('div');
  84. svgContainer.appendChild(svgDom);
  85. svgXml = svgContainer.innerHTML;
  86. // Dummy IE
  87. svgXml = svgXml.replace(' xmlns="http://www.w3.org/2000/svg" ' +
  88. 'xmlns:NS1="" NS1:ns1:xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:NS2="" NS2:xmlns:ns1=""', '');
  89. // svg 含有   符号导出报错 Entity 'nbsp' not defined ,含有控制字符触发Load Image 会触发报错
  90. svgXml = svgXml.replace(/ |[\x00-\x1F\x7F-\x9F]/g, "");
  91. // fix title issue in safari
  92. // @ http://stackoverflow.com/questions/30273775/namespace-prefix-ns1-for-href-on-tagelement-is-not-defined-setattributens
  93. svgXml = svgXml.replace(/NS\d+:title/gi, 'xlink:title');
  94. blob = new Blob([svgXml], {
  95. type: 'image/svg+xml'
  96. });
  97. svgUrl = DomURL.createObjectURL(blob);
  98. //svgUrl = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgXml);
  99. var imagesInfo = [];
  100. // 遍历取出图片信息
  101. traverse(minder.getRoot());
  102. function traverse(node) {
  103. var nodeData = node.data;
  104. if (nodeData.image) {
  105. minder.renderNode(node);
  106. var nodeData = node.data;
  107. var imageUrl = nodeData.image;
  108. var imageSize = nodeData.imageSize;
  109. var imageRenderBox = node.getRenderBox("ImageRenderer", minder.getRenderContainer());
  110. var imageInfo = {
  111. url: imageUrl,
  112. width: imageSize.width,
  113. height: imageSize.height,
  114. x: -renderContainer.getBoundaryBox().x + imageRenderBox.x,
  115. y: -renderContainer.getBoundaryBox().y + imageRenderBox.y
  116. };
  117. imagesInfo.push(imageInfo);
  118. }
  119. // 若节点折叠,则直接返回
  120. if (nodeData.expandState === 'collapse') {
  121. return;
  122. }
  123. var children = node.getChildren();
  124. for (var i = 0; i < children.length; i++) {
  125. traverse(children[i]);
  126. }
  127. }
  128. return {
  129. width: width,
  130. height: height,
  131. dataUrl: svgUrl,
  132. xml: svgXml,
  133. imagesInfo: imagesInfo
  134. };
  135. }
  136. function encode(json, minder, option) {
  137. var resultCallback;
  138. /* 绘制 PNG 的画布及上下文 */
  139. var canvas = document.createElement('canvas');
  140. var ctx = canvas.getContext('2d');
  141. /* 尝试获取背景图片 URL 或背景颜色 */
  142. var bgDeclare = minder.getStyle('background').toString();
  143. var bgUrl = /url\(\"(.+)\"\)/.exec(bgDeclare);
  144. var bgColor = kity.Color.parse(bgDeclare);
  145. /* 获取 SVG 文件内容 */
  146. var svgInfo = getSVGInfo(minder);
  147. var width = option && option.width && option.width > svgInfo.width ? option.width : svgInfo.width;
  148. var height = option && option.height && option.height > svgInfo.height ? option.height : svgInfo.height;
  149. var offsetX = option && option.width && option.width > svgInfo.width ? (option.width - svgInfo.width)/2 : 0;
  150. var offsetY = option && option.height && option.height > svgInfo.height ? (option.height - svgInfo.height)/2 : 0;
  151. var svgDataUrl = svgInfo.dataUrl;
  152. var imagesInfo = svgInfo.imagesInfo;
  153. /* 画布的填充大小 */
  154. var padding = 20;
  155. canvas.width = width + padding * 2;
  156. canvas.height = height + padding * 2;
  157. function fillBackground(ctx, style) {
  158. ctx.save();
  159. ctx.fillStyle = style;
  160. ctx.fillRect(0, 0, canvas.width, canvas.height);
  161. ctx.restore();
  162. }
  163. function drawImage(ctx, image, x, y, width, height) {
  164. if (width && height) {
  165. ctx.drawImage(image, x + padding, y + padding, width, height);
  166. } else {
  167. ctx.drawImage(image, x + padding, y + padding);
  168. }
  169. }
  170. function generateDataUrl(canvas) {
  171. return canvas.toDataURL('image/png');
  172. }
  173. // 加载节点上的图片
  174. function loadImages(imagesInfo) {
  175. var imagePromises = imagesInfo.map(function(imageInfo) {
  176. return xhrLoadImage(imageInfo);
  177. });
  178. return Promise.all(imagePromises);
  179. }
  180. function drawSVG() {
  181. var svgData = {url: svgDataUrl};
  182. return loadImage(svgData).then(function($image) {
  183. drawImage(ctx, $image.element, offsetX, offsetY, $image.width, $image.height);
  184. return loadImages(imagesInfo);
  185. }).then(function($images) {
  186. for(var i = 0; i < $images.length; i++) {
  187. drawImage(ctx, $images[i].element, $images[i].x + offsetX, $images[i].y + offsetY, $images[i].width, $images[i].height);
  188. }
  189. DomURL.revokeObjectURL(svgDataUrl);
  190. document.body.appendChild(canvas);
  191. var pngBase64 = generateDataUrl(canvas);
  192. document.body.removeChild(canvas);
  193. return pngBase64;
  194. }, function(err) {
  195. // 这里处理 reject,出错基本上是因为跨域,
  196. // 出错后依然导出,只不过没有图片。
  197. alert('脑图的节点中包含跨域图片,导出的 png 中节点图片不显示,你可以替换掉这些跨域的图片并重试。');
  198. DomURL.revokeObjectURL(svgDataUrl);
  199. document.body.appendChild(canvas);
  200. var pngBase64 = generateDataUrl(canvas);
  201. document.body.removeChild(canvas);
  202. return pngBase64;
  203. });
  204. }
  205. if (bgUrl) {
  206. var bgInfo = {url: bgUrl[1]};
  207. return loadImage(bgInfo).then(function($image) {
  208. fillBackground(ctx, ctx.createPattern($image.element, "repeat"));
  209. return drawSVG();
  210. });
  211. } else {
  212. fillBackground(ctx, bgColor.toString());
  213. return drawSVG();
  214. }
  215. }
  216. data.registerProtocol("png", module.exports = {
  217. fileDescription: "PNG 图片",
  218. fileExtension: ".png",
  219. mineType: "image/png",
  220. dataType: "base64",
  221. encode: encode
  222. });
  223. });