@7polo/kityminder-core
Version:
KityMinder Core Implement
274 lines (223 loc) • 10 kB
JavaScript
define(function(require, exports, module) {
var kity = require('../core/kity');
var data = require('../core/data');
var Promise = require('../core/promise');
var DomURL = window.URL || window.webkitURL || window;
function loadImage(info, callback) {
return new Promise(function(resolve, reject) {
var image = document.createElement("img");
image.onload = function() {
resolve({
element: this,
x: info.x,
y: info.y,
width: info.width,
height: info.height
});
};
image.onerror = function(err) {
reject(err);
};
image.crossOrigin = 'anonymous';
image.src = info.url;
});
}
/**
* xhrLoadImage: 通过 xhr 加载保存在 BOS 上的图片
* @note: BOS 上的 CORS 策略是取 headers 里面的 Origin 字段进行判断
* 而通过 image 的 src 的方式是无法传递 origin 的,因此需要通过 xhr 进行
*/
function xhrLoadImage(info, callback) {
return Promise(function (resolve, reject) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('GET', info.url + '?_=' + Date.now(), true);
xmlHttp.responseType = 'blob';
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
var blob = xmlHttp.response;
var image = document.createElement('img');
image.src = DomURL.createObjectURL(blob);
image.onload = function () {
DomURL.revokeObjectURL(image.src);
resolve({
element: image,
x: info.x,
y: info.y,
width: info.width,
height: info.height
});
};
}
};
xmlHttp.send();
});
}
function getSVGInfo(minder) {
var paper = minder.getPaper(),
paperTransform,
domContainer = paper.container,
svgXml,
svgContainer,
svgDom,
renderContainer = minder.getRenderContainer(),
renderBox = renderContainer.getRenderBox(),
width = renderBox.width + 1,
height = renderBox.height + 1,
blob, svgUrl, img;
// 保存原始变换,并且移动到合适的位置
paperTransform = paper.shapeNode.getAttribute('transform');
paper.shapeNode.setAttribute('transform', 'translate(0.5, 0.5)');
renderContainer.translate(-renderBox.x, -renderBox.y);
// 获取当前的 XML 代码
svgXml = paper.container.innerHTML;
// 回复原始变换及位置
renderContainer.translate(renderBox.x, renderBox.y);
paper.shapeNode.setAttribute('transform', paperTransform);
// 过滤内容
svgContainer = document.createElement('div');
svgContainer.innerHTML = svgXml;
svgDom = svgContainer.querySelector('svg');
svgDom.setAttribute('width', renderBox.width + 1);
svgDom.setAttribute('height', renderBox.height + 1);
svgDom.setAttribute('style', 'font-family: Arial, "Microsoft Yahei","Heiti SC";');
svgContainer = document.createElement('div');
svgContainer.appendChild(svgDom);
svgXml = svgContainer.innerHTML;
// Dummy IE
svgXml = svgXml.replace(' xmlns="http://www.w3.org/2000/svg" ' +
'xmlns:NS1="" NS1:ns1:xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:NS2="" NS2:xmlns:ns1=""', '');
// svg 含有 符号导出报错 Entity 'nbsp' not defined ,含有控制字符触发Load Image 会触发报错
svgXml = svgXml.replace(/ |[\x00-\x1F\x7F-\x9F]/g, "");
// fix title issue in safari
// @ http://stackoverflow.com/questions/30273775/namespace-prefix-ns1-for-href-on-tagelement-is-not-defined-setattributens
svgXml = svgXml.replace(/NS\d+:title/gi, 'xlink:title');
blob = new Blob([svgXml], {
type: 'image/svg+xml'
});
svgUrl = DomURL.createObjectURL(blob);
//svgUrl = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgXml);
var imagesInfo = [];
// 遍历取出图片信息
traverse(minder.getRoot());
function traverse(node) {
var nodeData = node.data;
if (nodeData.image) {
minder.renderNode(node);
var nodeData = node.data;
var imageUrl = nodeData.image;
var imageSize = nodeData.imageSize;
var imageRenderBox = node.getRenderBox("ImageRenderer", minder.getRenderContainer());
var imageInfo = {
url: imageUrl,
width: imageSize.width,
height: imageSize.height,
x: -renderContainer.getBoundaryBox().x + imageRenderBox.x,
y: -renderContainer.getBoundaryBox().y + imageRenderBox.y
};
imagesInfo.push(imageInfo);
}
// 若节点折叠,则直接返回
if (nodeData.expandState === 'collapse') {
return;
}
var children = node.getChildren();
for (var i = 0; i < children.length; i++) {
traverse(children[i]);
}
}
return {
width: width,
height: height,
dataUrl: svgUrl,
xml: svgXml,
imagesInfo: imagesInfo
};
}
function encode(json, minder, option) {
var resultCallback;
/* 绘制 PNG 的画布及上下文 */
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
/* 尝试获取背景图片 URL 或背景颜色 */
var bgDeclare = minder.getStyle('background').toString();
var bgUrl = /url\(\"(.+)\"\)/.exec(bgDeclare);
var bgColor = kity.Color.parse(bgDeclare);
/* 获取 SVG 文件内容 */
var svgInfo = getSVGInfo(minder);
var width = option && option.width && option.width > svgInfo.width ? option.width : svgInfo.width;
var height = option && option.height && option.height > svgInfo.height ? option.height : svgInfo.height;
var offsetX = option && option.width && option.width > svgInfo.width ? (option.width - svgInfo.width)/2 : 0;
var offsetY = option && option.height && option.height > svgInfo.height ? (option.height - svgInfo.height)/2 : 0;
var svgDataUrl = svgInfo.dataUrl;
var imagesInfo = svgInfo.imagesInfo;
/* 画布的填充大小 */
var padding = 20;
canvas.width = width + padding * 2;
canvas.height = height + padding * 2;
function fillBackground(ctx, style) {
ctx.save();
ctx.fillStyle = style;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
}
function drawImage(ctx, image, x, y, width, height) {
if (width && height) {
ctx.drawImage(image, x + padding, y + padding, width, height);
} else {
ctx.drawImage(image, x + padding, y + padding);
}
}
function generateDataUrl(canvas) {
return canvas.toDataURL('image/png');
}
// 加载节点上的图片
function loadImages(imagesInfo) {
var imagePromises = imagesInfo.map(function(imageInfo) {
return xhrLoadImage(imageInfo);
});
return Promise.all(imagePromises);
}
function drawSVG() {
var svgData = {url: svgDataUrl};
return loadImage(svgData).then(function($image) {
drawImage(ctx, $image.element, offsetX, offsetY, $image.width, $image.height);
return loadImages(imagesInfo);
}).then(function($images) {
for(var i = 0; i < $images.length; i++) {
drawImage(ctx, $images[i].element, $images[i].x + offsetX, $images[i].y + offsetY, $images[i].width, $images[i].height);
}
DomURL.revokeObjectURL(svgDataUrl);
document.body.appendChild(canvas);
var pngBase64 = generateDataUrl(canvas);
document.body.removeChild(canvas);
return pngBase64;
}, function(err) {
// 这里处理 reject,出错基本上是因为跨域,
// 出错后依然导出,只不过没有图片。
alert('脑图的节点中包含跨域图片,导出的 png 中节点图片不显示,你可以替换掉这些跨域的图片并重试。');
DomURL.revokeObjectURL(svgDataUrl);
document.body.appendChild(canvas);
var pngBase64 = generateDataUrl(canvas);
document.body.removeChild(canvas);
return pngBase64;
});
}
if (bgUrl) {
var bgInfo = {url: bgUrl[1]};
return loadImage(bgInfo).then(function($image) {
fillBackground(ctx, ctx.createPattern($image.element, "repeat"));
return drawSVG();
});
} else {
fillBackground(ctx, bgColor.toString());
return drawSVG();
}
}
data.registerProtocol("png", module.exports = {
fileDescription: "PNG 图片",
fileExtension: ".png",
mineType: "image/png",
dataType: "base64",
encode: encode
});
});