123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- var Group = require("../container/Group");
- var ZImage = require("../graphic/Image");
- var Text = require("../graphic/Text");
- var Circle = require("../graphic/shape/Circle");
- var Rect = require("../graphic/shape/Rect");
- var Ellipse = require("../graphic/shape/Ellipse");
- var Line = require("../graphic/shape/Line");
- var Path = require("../graphic/Path");
- var Polygon = require("../graphic/shape/Polygon");
- var Polyline = require("../graphic/shape/Polyline");
- var LinearGradient = require("../graphic/LinearGradient");
- var Style = require("../graphic/Style");
- var matrix = require("../core/matrix");
- var _path = require("./path");
- var createFromString = _path.createFromString;
- var _util = require("../core/util");
- var isString = _util.isString;
- var extend = _util.extend;
- var defaults = _util.defaults;
- var trim = _util.trim;
- var each = _util.each;
- // import RadialGradient from '../graphic/RadialGradient';
- // import Pattern from '../graphic/Pattern';
- // import * as vector from '../core/vector';
- // Most of the values can be separated by comma and/or white space.
- var DILIMITER_REG = /[\s,]+/;
- /**
- * For big svg string, this method might be time consuming.
- *
- * @param {string} svg xml string
- * @return {Object} xml root.
- */
- function parseXML(svg) {
- if (isString(svg)) {
- var parser = new DOMParser();
- svg = parser.parseFromString(svg, 'text/xml');
- } // Document node. If using $.get, doc node may be input.
- if (svg.nodeType === 9) {
- svg = svg.firstChild;
- } // nodeName of <!DOCTYPE svg> is also 'svg'.
- while (svg.nodeName.toLowerCase() !== 'svg' || svg.nodeType !== 1) {
- svg = svg.nextSibling;
- }
- return svg;
- }
- function SVGParser() {
- this._defs = {};
- this._root = null;
- this._isDefine = false;
- this._isText = false;
- }
- SVGParser.prototype.parse = function (xml, opt) {
- opt = opt || {};
- var svg = parseXML(xml);
- if (!svg) {
- throw new Error('Illegal svg');
- }
- var root = new Group();
- this._root = root; // parse view port
- var viewBox = svg.getAttribute('viewBox') || ''; // If width/height not specified, means "100%" of `opt.width/height`.
- // TODO: Other percent value not supported yet.
- var width = parseFloat(svg.getAttribute('width') || opt.width);
- var height = parseFloat(svg.getAttribute('height') || opt.height); // If width/height not specified, set as null for output.
- isNaN(width) && (width = null);
- isNaN(height) && (height = null); // Apply inline style on svg element.
- parseAttributes(svg, root, null, true);
- var child = svg.firstChild;
- while (child) {
- this._parseNode(child, root);
- child = child.nextSibling;
- }
- var viewBoxRect;
- var viewBoxTransform;
- if (viewBox) {
- var viewBoxArr = trim(viewBox).split(DILIMITER_REG); // Some invalid case like viewBox: 'none'.
- if (viewBoxArr.length >= 4) {
- viewBoxRect = {
- x: parseFloat(viewBoxArr[0] || 0),
- y: parseFloat(viewBoxArr[1] || 0),
- width: parseFloat(viewBoxArr[2]),
- height: parseFloat(viewBoxArr[3])
- };
- }
- }
- if (viewBoxRect && width != null && height != null) {
- viewBoxTransform = makeViewBoxTransform(viewBoxRect, width, height);
- if (!opt.ignoreViewBox) {
- // If set transform on the output group, it probably bring trouble when
- // some users only intend to show the clipped content inside the viewBox,
- // but not intend to transform the output group. So we keep the output
- // group no transform. If the user intend to use the viewBox as a
- // camera, just set `opt.ignoreViewBox` as `true` and set transfrom
- // manually according to the viewBox info in the output of this method.
- var elRoot = root;
- root = new Group();
- root.add(elRoot);
- elRoot.scale = viewBoxTransform.scale.slice();
- elRoot.position = viewBoxTransform.position.slice();
- }
- } // Some shapes might be overflow the viewport, which should be
- // clipped despite whether the viewBox is used, as the SVG does.
- if (!opt.ignoreRootClip && width != null && height != null) {
- root.setClipPath(new Rect({
- shape: {
- x: 0,
- y: 0,
- width: width,
- height: height
- }
- }));
- } // Set width/height on group just for output the viewport size.
- return {
- root: root,
- width: width,
- height: height,
- viewBoxRect: viewBoxRect,
- viewBoxTransform: viewBoxTransform
- };
- };
- SVGParser.prototype._parseNode = function (xmlNode, parentGroup) {
- var nodeName = xmlNode.nodeName.toLowerCase(); // TODO
- // support <style>...</style> in svg, where nodeName is 'style',
- // CSS classes is defined globally wherever the style tags are declared.
- if (nodeName === 'defs') {
- // define flag
- this._isDefine = true;
- } else if (nodeName === 'text') {
- this._isText = true;
- }
- var el;
- if (this._isDefine) {
- var parser = defineParsers[nodeName];
- if (parser) {
- var def = parser.call(this, xmlNode);
- var id = xmlNode.getAttribute('id');
- if (id) {
- this._defs[id] = def;
- }
- }
- } else {
- var parser = nodeParsers[nodeName];
- if (parser) {
- el = parser.call(this, xmlNode, parentGroup);
- parentGroup.add(el);
- }
- }
- var child = xmlNode.firstChild;
- while (child) {
- if (child.nodeType === 1) {
- this._parseNode(child, el);
- } // Is text
- if (child.nodeType === 3 && this._isText) {
- this._parseText(child, el);
- }
- child = child.nextSibling;
- } // Quit define
- if (nodeName === 'defs') {
- this._isDefine = false;
- } else if (nodeName === 'text') {
- this._isText = false;
- }
- };
- SVGParser.prototype._parseText = function (xmlNode, parentGroup) {
- if (xmlNode.nodeType === 1) {
- var dx = xmlNode.getAttribute('dx') || 0;
- var dy = xmlNode.getAttribute('dy') || 0;
- this._textX += parseFloat(dx);
- this._textY += parseFloat(dy);
- }
- var text = new Text({
- style: {
- text: xmlNode.textContent,
- transformText: true
- },
- position: [this._textX || 0, this._textY || 0]
- });
- inheritStyle(parentGroup, text);
- parseAttributes(xmlNode, text, this._defs);
- var fontSize = text.style.fontSize;
- if (fontSize && fontSize < 9) {
- // PENDING
- text.style.fontSize = 9;
- text.scale = text.scale || [1, 1];
- text.scale[0] *= fontSize / 9;
- text.scale[1] *= fontSize / 9;
- }
- var rect = text.getBoundingRect();
- this._textX += rect.width;
- parentGroup.add(text);
- return text;
- };
- var nodeParsers = {
- 'g': function (xmlNode, parentGroup) {
- var g = new Group();
- inheritStyle(parentGroup, g);
- parseAttributes(xmlNode, g, this._defs);
- return g;
- },
- 'rect': function (xmlNode, parentGroup) {
- var rect = new Rect();
- inheritStyle(parentGroup, rect);
- parseAttributes(xmlNode, rect, this._defs);
- rect.setShape({
- x: parseFloat(xmlNode.getAttribute('x') || 0),
- y: parseFloat(xmlNode.getAttribute('y') || 0),
- width: parseFloat(xmlNode.getAttribute('width') || 0),
- height: parseFloat(xmlNode.getAttribute('height') || 0)
- }); // console.log(xmlNode.getAttribute('transform'));
- // console.log(rect.transform);
- return rect;
- },
- 'circle': function (xmlNode, parentGroup) {
- var circle = new Circle();
- inheritStyle(parentGroup, circle);
- parseAttributes(xmlNode, circle, this._defs);
- circle.setShape({
- cx: parseFloat(xmlNode.getAttribute('cx') || 0),
- cy: parseFloat(xmlNode.getAttribute('cy') || 0),
- r: parseFloat(xmlNode.getAttribute('r') || 0)
- });
- return circle;
- },
- 'line': function (xmlNode, parentGroup) {
- var line = new Line();
- inheritStyle(parentGroup, line);
- parseAttributes(xmlNode, line, this._defs);
- line.setShape({
- x1: parseFloat(xmlNode.getAttribute('x1') || 0),
- y1: parseFloat(xmlNode.getAttribute('y1') || 0),
- x2: parseFloat(xmlNode.getAttribute('x2') || 0),
- y2: parseFloat(xmlNode.getAttribute('y2') || 0)
- });
- return line;
- },
- 'ellipse': function (xmlNode, parentGroup) {
- var ellipse = new Ellipse();
- inheritStyle(parentGroup, ellipse);
- parseAttributes(xmlNode, ellipse, this._defs);
- ellipse.setShape({
- cx: parseFloat(xmlNode.getAttribute('cx') || 0),
- cy: parseFloat(xmlNode.getAttribute('cy') || 0),
- rx: parseFloat(xmlNode.getAttribute('rx') || 0),
- ry: parseFloat(xmlNode.getAttribute('ry') || 0)
- });
- return ellipse;
- },
- 'polygon': function (xmlNode, parentGroup) {
- var points = xmlNode.getAttribute('points');
- if (points) {
- points = parsePoints(points);
- }
- var polygon = new Polygon({
- shape: {
- points: points || []
- }
- });
- inheritStyle(parentGroup, polygon);
- parseAttributes(xmlNode, polygon, this._defs);
- return polygon;
- },
- 'polyline': function (xmlNode, parentGroup) {
- var path = new Path();
- inheritStyle(parentGroup, path);
- parseAttributes(xmlNode, path, this._defs);
- var points = xmlNode.getAttribute('points');
- if (points) {
- points = parsePoints(points);
- }
- var polyline = new Polyline({
- shape: {
- points: points || []
- }
- });
- return polyline;
- },
- 'image': function (xmlNode, parentGroup) {
- var img = new ZImage();
- inheritStyle(parentGroup, img);
- parseAttributes(xmlNode, img, this._defs);
- img.setStyle({
- image: xmlNode.getAttribute('xlink:href'),
- x: xmlNode.getAttribute('x'),
- y: xmlNode.getAttribute('y'),
- width: xmlNode.getAttribute('width'),
- height: xmlNode.getAttribute('height')
- });
- return img;
- },
- 'text': function (xmlNode, parentGroup) {
- var x = xmlNode.getAttribute('x') || 0;
- var y = xmlNode.getAttribute('y') || 0;
- var dx = xmlNode.getAttribute('dx') || 0;
- var dy = xmlNode.getAttribute('dy') || 0;
- this._textX = parseFloat(x) + parseFloat(dx);
- this._textY = parseFloat(y) + parseFloat(dy);
- var g = new Group();
- inheritStyle(parentGroup, g);
- parseAttributes(xmlNode, g, this._defs);
- return g;
- },
- 'tspan': function (xmlNode, parentGroup) {
- var x = xmlNode.getAttribute('x');
- var y = xmlNode.getAttribute('y');
- if (x != null) {
- // new offset x
- this._textX = parseFloat(x);
- }
- if (y != null) {
- // new offset y
- this._textY = parseFloat(y);
- }
- var dx = xmlNode.getAttribute('dx') || 0;
- var dy = xmlNode.getAttribute('dy') || 0;
- var g = new Group();
- inheritStyle(parentGroup, g);
- parseAttributes(xmlNode, g, this._defs);
- this._textX += dx;
- this._textY += dy;
- return g;
- },
- 'path': function (xmlNode, parentGroup) {
- // TODO svg fill rule
- // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule
- // path.style.globalCompositeOperation = 'xor';
- var d = xmlNode.getAttribute('d') || ''; // Performance sensitive.
- var path = createFromString(d);
- inheritStyle(parentGroup, path);
- parseAttributes(xmlNode, path, this._defs);
- return path;
- }
- };
- var defineParsers = {
- 'lineargradient': function (xmlNode) {
- var x1 = parseInt(xmlNode.getAttribute('x1') || 0, 10);
- var y1 = parseInt(xmlNode.getAttribute('y1') || 0, 10);
- var x2 = parseInt(xmlNode.getAttribute('x2') || 10, 10);
- var y2 = parseInt(xmlNode.getAttribute('y2') || 0, 10);
- var gradient = new LinearGradient(x1, y1, x2, y2);
- _parseGradientColorStops(xmlNode, gradient);
- return gradient;
- },
- 'radialgradient': function (xmlNode) {}
- };
- function _parseGradientColorStops(xmlNode, gradient) {
- var stop = xmlNode.firstChild;
- while (stop) {
- if (stop.nodeType === 1) {
- var offset = stop.getAttribute('offset');
- if (offset.indexOf('%') > 0) {
- // percentage
- offset = parseInt(offset, 10) / 100;
- } else if (offset) {
- // number from 0 to 1
- offset = parseFloat(offset);
- } else {
- offset = 0;
- }
- var stopColor = stop.getAttribute('stop-color') || '#000000';
- gradient.addColorStop(offset, stopColor);
- }
- stop = stop.nextSibling;
- }
- }
- function inheritStyle(parent, child) {
- if (parent && parent.__inheritedStyle) {
- if (!child.__inheritedStyle) {
- child.__inheritedStyle = {};
- }
- defaults(child.__inheritedStyle, parent.__inheritedStyle);
- }
- }
- function parsePoints(pointsString) {
- var list = trim(pointsString).split(DILIMITER_REG);
- var points = [];
- for (var i = 0; i < list.length; i += 2) {
- var x = parseFloat(list[i]);
- var y = parseFloat(list[i + 1]);
- points.push([x, y]);
- }
- return points;
- }
- var attributesMap = {
- 'fill': 'fill',
- 'stroke': 'stroke',
- 'stroke-width': 'lineWidth',
- 'opacity': 'opacity',
- 'fill-opacity': 'fillOpacity',
- 'stroke-opacity': 'strokeOpacity',
- 'stroke-dasharray': 'lineDash',
- 'stroke-dashoffset': 'lineDashOffset',
- 'stroke-linecap': 'lineCap',
- 'stroke-linejoin': 'lineJoin',
- 'stroke-miterlimit': 'miterLimit',
- 'font-family': 'fontFamily',
- 'font-size': 'fontSize',
- 'font-style': 'fontStyle',
- 'font-weight': 'fontWeight',
- 'text-align': 'textAlign',
- 'alignment-baseline': 'textBaseline'
- };
- function parseAttributes(xmlNode, el, defs, onlyInlineStyle) {
- var zrStyle = el.__inheritedStyle || {};
- var isTextEl = el.type === 'text'; // TODO Shadow
- if (xmlNode.nodeType === 1) {
- parseTransformAttribute(xmlNode, el);
- extend(zrStyle, parseStyleAttribute(xmlNode));
- if (!onlyInlineStyle) {
- for (var svgAttrName in attributesMap) {
- if (attributesMap.hasOwnProperty(svgAttrName)) {
- var attrValue = xmlNode.getAttribute(svgAttrName);
- if (attrValue != null) {
- zrStyle[attributesMap[svgAttrName]] = attrValue;
- }
- }
- }
- }
- }
- var elFillProp = isTextEl ? 'textFill' : 'fill';
- var elStrokeProp = isTextEl ? 'textStroke' : 'stroke';
- el.style = el.style || new Style();
- var elStyle = el.style;
- zrStyle.fill != null && elStyle.set(elFillProp, getPaint(zrStyle.fill, defs));
- zrStyle.stroke != null && elStyle.set(elStrokeProp, getPaint(zrStyle.stroke, defs));
- each(['lineWidth', 'opacity', 'fillOpacity', 'strokeOpacity', 'miterLimit', 'fontSize'], function (propName) {
- var elPropName = propName === 'lineWidth' && isTextEl ? 'textStrokeWidth' : propName;
- zrStyle[propName] != null && elStyle.set(elPropName, parseFloat(zrStyle[propName]));
- });
- if (!zrStyle.textBaseline || zrStyle.textBaseline === 'auto') {
- zrStyle.textBaseline = 'alphabetic';
- }
- if (zrStyle.textBaseline === 'alphabetic') {
- zrStyle.textBaseline = 'bottom';
- }
- if (zrStyle.textAlign === 'start') {
- zrStyle.textAlign = 'left';
- }
- if (zrStyle.textAlign === 'end') {
- zrStyle.textAlign = 'right';
- }
- each(['lineDashOffset', 'lineCap', 'lineJoin', 'fontWeight', 'fontFamily', 'fontStyle', 'textAlign', 'textBaseline'], function (propName) {
- zrStyle[propName] != null && elStyle.set(propName, zrStyle[propName]);
- });
- if (zrStyle.lineDash) {
- el.style.lineDash = trim(zrStyle.lineDash).split(DILIMITER_REG);
- }
- if (elStyle[elStrokeProp] && elStyle[elStrokeProp] !== 'none') {
- // enable stroke
- el[elStrokeProp] = true;
- }
- el.__inheritedStyle = zrStyle;
- }
- var urlRegex = /url\(\s*#(.*?)\)/;
- function getPaint(str, defs) {
- // if (str === 'none') {
- // return;
- // }
- var urlMatch = defs && str && str.match(urlRegex);
- if (urlMatch) {
- var url = trim(urlMatch[1]);
- var def = defs[url];
- return def;
- }
- return str;
- }
- var transformRegex = /(translate|scale|rotate|skewX|skewY|matrix)\(([\-\s0-9\.e,]*)\)/g;
- function parseTransformAttribute(xmlNode, node) {
- var transform = xmlNode.getAttribute('transform');
- if (transform) {
- transform = transform.replace(/,/g, ' ');
- var m = null;
- var transformOps = [];
- transform.replace(transformRegex, function (str, type, value) {
- transformOps.push(type, value);
- });
- for (var i = transformOps.length - 1; i > 0; i -= 2) {
- var value = transformOps[i];
- var type = transformOps[i - 1];
- m = m || matrix.create();
- switch (type) {
- case 'translate':
- value = trim(value).split(DILIMITER_REG);
- matrix.translate(m, m, [parseFloat(value[0]), parseFloat(value[1] || 0)]);
- break;
- case 'scale':
- value = trim(value).split(DILIMITER_REG);
- matrix.scale(m, m, [parseFloat(value[0]), parseFloat(value[1] || value[0])]);
- break;
- case 'rotate':
- value = trim(value).split(DILIMITER_REG);
- matrix.rotate(m, m, parseFloat(value[0]));
- break;
- case 'skew':
- value = trim(value).split(DILIMITER_REG);
- console.warn('Skew transform is not supported yet');
- break;
- case 'matrix':
- var value = trim(value).split(DILIMITER_REG);
- m[0] = parseFloat(value[0]);
- m[1] = parseFloat(value[1]);
- m[2] = parseFloat(value[2]);
- m[3] = parseFloat(value[3]);
- m[4] = parseFloat(value[4]);
- m[5] = parseFloat(value[5]);
- break;
- }
- }
- node.setLocalTransform(m);
- }
- } // Value may contain space.
- var styleRegex = /([^\s:;]+)\s*:\s*([^:;]+)/g;
- function parseStyleAttribute(xmlNode) {
- var style = xmlNode.getAttribute('style');
- var result = {};
- if (!style) {
- return result;
- }
- var styleList = {};
- styleRegex.lastIndex = 0;
- var styleRegResult;
- while ((styleRegResult = styleRegex.exec(style)) != null) {
- styleList[styleRegResult[1]] = styleRegResult[2];
- }
- for (var svgAttrName in attributesMap) {
- if (attributesMap.hasOwnProperty(svgAttrName) && styleList[svgAttrName] != null) {
- result[attributesMap[svgAttrName]] = styleList[svgAttrName];
- }
- }
- return result;
- }
- /**
- * @param {Array.<number>} viewBoxRect
- * @param {number} width
- * @param {number} height
- * @return {Object} {scale, position}
- */
- function makeViewBoxTransform(viewBoxRect, width, height) {
- var scaleX = width / viewBoxRect.width;
- var scaleY = height / viewBoxRect.height;
- var scale = Math.min(scaleX, scaleY); // preserveAspectRatio 'xMidYMid'
- var viewBoxScale = [scale, scale];
- var viewBoxPosition = [-(viewBoxRect.x + viewBoxRect.width / 2) * scale + width / 2, -(viewBoxRect.y + viewBoxRect.height / 2) * scale + height / 2];
- return {
- scale: viewBoxScale,
- position: viewBoxPosition
- };
- }
- /**
- * @param {string|XMLElement} xml
- * @param {Object} [opt]
- * @param {number} [opt.width] Default width if svg width not specified or is a percent value.
- * @param {number} [opt.height] Default height if svg height not specified or is a percent value.
- * @param {boolean} [opt.ignoreViewBox]
- * @param {boolean} [opt.ignoreRootClip]
- * @return {Object} result:
- * {
- * root: Group, The root of the the result tree of zrender shapes,
- * width: number, the viewport width of the SVG,
- * height: number, the viewport height of the SVG,
- * viewBoxRect: {x, y, width, height}, the declared viewBox rect of the SVG, if exists,
- * viewBoxTransform: the {scale, position} calculated by viewBox and viewport, is exists.
- * }
- */
- function parseSVG(xml, opt) {
- var parser = new SVGParser();
- return parser.parse(xml, opt);
- }
- exports.parseXML = parseXML;
- exports.makeViewBoxTransform = makeViewBoxTransform;
- exports.parseSVG = parseSVG;
|