| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 | var _core = require("./core");var createElement = _core.createElement;var PathProxy = require("../core/PathProxy");var BoundingRect = require("../core/BoundingRect");var matrix = require("../core/matrix");var textContain = require("../contain/text");var textHelper = require("../graphic/helper/text");var Text = require("../graphic/Text");// TODO// 1. shadow// 2. Image: sx, sy, sw, shvar CMD = PathProxy.CMD;var arrayJoin = Array.prototype.join;var NONE = 'none';var mathRound = Math.round;var mathSin = Math.sin;var mathCos = Math.cos;var PI = Math.PI;var PI2 = Math.PI * 2;var degree = 180 / PI;var EPSILON = 1e-4;function round4(val) {  return mathRound(val * 1e4) / 1e4;}function isAroundZero(val) {  return val < EPSILON && val > -EPSILON;}function pathHasFill(style, isText) {  var fill = isText ? style.textFill : style.fill;  return fill != null && fill !== NONE;}function pathHasStroke(style, isText) {  var stroke = isText ? style.textStroke : style.stroke;  return stroke != null && stroke !== NONE;}function setTransform(svgEl, m) {  if (m) {    attr(svgEl, 'transform', 'matrix(' + arrayJoin.call(m, ',') + ')');  }}function attr(el, key, val) {  if (!val || val.type !== 'linear' && val.type !== 'radial') {    // Don't set attribute for gradient, since it need new dom nodes    el.setAttribute(key, val);  }}function attrXLink(el, key, val) {  el.setAttributeNS('http://www.w3.org/1999/xlink', key, val);}function bindStyle(svgEl, style, isText, el) {  if (pathHasFill(style, isText)) {    var fill = isText ? style.textFill : style.fill;    fill = fill === 'transparent' ? NONE : fill;    attr(svgEl, 'fill', fill);    attr(svgEl, 'fill-opacity', style.fillOpacity != null ? style.fillOpacity * style.opacity : style.opacity);  } else {    attr(svgEl, 'fill', NONE);  }  if (pathHasStroke(style, isText)) {    var stroke = isText ? style.textStroke : style.stroke;    stroke = stroke === 'transparent' ? NONE : stroke;    attr(svgEl, 'stroke', stroke);    var strokeWidth = isText ? style.textStrokeWidth : style.lineWidth;    var strokeScale = !isText && style.strokeNoScale ? el.getLineScale() : 1;    attr(svgEl, 'stroke-width', strokeWidth / strokeScale); // stroke then fill for text; fill then stroke for others    attr(svgEl, 'paint-order', isText ? 'stroke' : 'fill');    attr(svgEl, 'stroke-opacity', style.strokeOpacity != null ? style.strokeOpacity : style.opacity);    var lineDash = style.lineDash;    if (lineDash) {      attr(svgEl, 'stroke-dasharray', style.lineDash.join(','));      attr(svgEl, 'stroke-dashoffset', mathRound(style.lineDashOffset || 0));    } else {      attr(svgEl, 'stroke-dasharray', '');    } // PENDING    style.lineCap && attr(svgEl, 'stroke-linecap', style.lineCap);    style.lineJoin && attr(svgEl, 'stroke-linejoin', style.lineJoin);    style.miterLimit && attr(svgEl, 'stroke-miterlimit', style.miterLimit);  } else {    attr(svgEl, 'stroke', NONE);  }}/*************************************************** * PATH **************************************************/function pathDataToString(path) {  var str = [];  var data = path.data;  var dataLength = path.len();  for (var i = 0; i < dataLength;) {    var cmd = data[i++];    var cmdStr = '';    var nData = 0;    switch (cmd) {      case CMD.M:        cmdStr = 'M';        nData = 2;        break;      case CMD.L:        cmdStr = 'L';        nData = 2;        break;      case CMD.Q:        cmdStr = 'Q';        nData = 4;        break;      case CMD.C:        cmdStr = 'C';        nData = 6;        break;      case CMD.A:        var cx = data[i++];        var cy = data[i++];        var rx = data[i++];        var ry = data[i++];        var theta = data[i++];        var dTheta = data[i++];        var psi = data[i++];        var clockwise = data[i++];        var dThetaPositive = Math.abs(dTheta);        var isCircle = isAroundZero(dThetaPositive - PI2) || (clockwise ? dTheta >= PI2 : -dTheta >= PI2); // Mapping to 0~2PI        var unifiedTheta = dTheta > 0 ? dTheta % PI2 : dTheta % PI2 + PI2;        var large = false;        if (isCircle) {          large = true;        } else if (isAroundZero(dThetaPositive)) {          large = false;        } else {          large = unifiedTheta >= PI === !!clockwise;        }        var x0 = round4(cx + rx * mathCos(theta));        var y0 = round4(cy + ry * mathSin(theta)); // It will not draw if start point and end point are exactly the same        // We need to shift the end point with a small value        // FIXME A better way to draw circle ?        if (isCircle) {          if (clockwise) {            dTheta = PI2 - 1e-4;          } else {            dTheta = -PI2 + 1e-4;          }          large = true;          if (i === 9) {            // Move to (x0, y0) only when CMD.A comes at the            // first position of a shape.            // For instance, when drawing a ring, CMD.A comes            // after CMD.M, so it's unnecessary to move to            // (x0, y0).            str.push('M', x0, y0);          }        }        var x = round4(cx + rx * mathCos(theta + dTheta));        var y = round4(cy + ry * mathSin(theta + dTheta)); // FIXME Ellipse        str.push('A', round4(rx), round4(ry), mathRound(psi * degree), +large, +clockwise, x, y);        break;      case CMD.Z:        cmdStr = 'Z';        break;      case CMD.R:        var x = round4(data[i++]);        var y = round4(data[i++]);        var w = round4(data[i++]);        var h = round4(data[i++]);        str.push('M', x, y, 'L', x + w, y, 'L', x + w, y + h, 'L', x, y + h, 'L', x, y);        break;    }    cmdStr && str.push(cmdStr);    for (var j = 0; j < nData; j++) {      // PENDING With scale      str.push(round4(data[i++]));    }  }  return str.join(' ');}var svgPath = {};svgPath.brush = function (el) {  var style = el.style;  var svgEl = el.__svgEl;  if (!svgEl) {    svgEl = createElement('path');    el.__svgEl = svgEl;  }  if (!el.path) {    el.createPathProxy();  }  var path = el.path;  if (el.__dirtyPath) {    path.beginPath();    path.subPixelOptimize = false;    el.buildPath(path, el.shape);    el.__dirtyPath = false;    var pathStr = pathDataToString(path);    if (pathStr.indexOf('NaN') < 0) {      // Ignore illegal path, which may happen such in out-of-range      // data in Calendar series.      attr(svgEl, 'd', pathStr);    }  }  bindStyle(svgEl, style, false, el);  setTransform(svgEl, el.transform);  if (style.text != null) {    svgTextDrawRectText(el, el.getBoundingRect());  } else {    removeOldTextNode(el);  }};/*************************************************** * IMAGE **************************************************/var svgImage = {};svgImage.brush = function (el) {  var style = el.style;  var image = style.image;  if (image instanceof HTMLImageElement) {    var src = image.src;    image = src;  }  if (!image) {    return;  }  var x = style.x || 0;  var y = style.y || 0;  var dw = style.width;  var dh = style.height;  var svgEl = el.__svgEl;  if (!svgEl) {    svgEl = createElement('image');    el.__svgEl = svgEl;  }  if (image !== el.__imageSrc) {    attrXLink(svgEl, 'href', image); // Caching image src    el.__imageSrc = image;  }  attr(svgEl, 'width', dw);  attr(svgEl, 'height', dh);  attr(svgEl, 'x', x);  attr(svgEl, 'y', y);  setTransform(svgEl, el.transform);  if (style.text != null) {    svgTextDrawRectText(el, el.getBoundingRect());  } else {    removeOldTextNode(el);  }};/*************************************************** * TEXT **************************************************/var svgText = {};var _tmpTextHostRect = new BoundingRect();var _tmpTextBoxPos = {};var _tmpTextTransform = [];var TEXT_ALIGN_TO_ANCHRO = {  left: 'start',  right: 'end',  center: 'middle',  middle: 'middle'};/** * @param {module:zrender/Element} el * @param {Object|boolean} [hostRect] {x, y, width, height} *        If set false, rect text is not used. */var svgTextDrawRectText = function (el, hostRect) {  var style = el.style;  var elTransform = el.transform;  var needTransformTextByHostEl = el instanceof Text || style.transformText;  el.__dirty && textHelper.normalizeTextStyle(style, true);  var text = style.text; // Convert to string  text != null && (text += '');  if (!textHelper.needDrawText(text, style)) {    return;  } // render empty text for svg if no text but need draw text.  text == null && (text = ''); // Follow the setting in the canvas renderer, if not transform the  // text, transform the hostRect, by which the text is located.  if (!needTransformTextByHostEl && elTransform) {    _tmpTextHostRect.copy(hostRect);    _tmpTextHostRect.applyTransform(elTransform);    hostRect = _tmpTextHostRect;  }  var textSvgEl = el.__textSvgEl;  if (!textSvgEl) {    textSvgEl = createElement('text');    el.__textSvgEl = textSvgEl;  } // style.font has been normalized by `normalizeTextStyle`.  var textSvgElStyle = textSvgEl.style;  var font = style.font || textContain.DEFAULT_FONT;  var computedFont = textSvgEl.__computedFont;  if (font !== textSvgEl.__styleFont) {    textSvgElStyle.font = textSvgEl.__styleFont = font; // The computedFont might not be the orginal font if it is illegal font.    computedFont = textSvgEl.__computedFont = textSvgElStyle.font;  }  var textPadding = style.textPadding;  var textLineHeight = style.textLineHeight;  var contentBlock = el.__textCotentBlock;  if (!contentBlock || el.__dirtyText) {    contentBlock = el.__textCotentBlock = textContain.parsePlainText(text, computedFont, textPadding, textLineHeight, style.truncate);  }  var outerHeight = contentBlock.outerHeight;  var lineHeight = contentBlock.lineHeight;  textHelper.getBoxPosition(_tmpTextBoxPos, el, style, hostRect);  var baseX = _tmpTextBoxPos.baseX;  var baseY = _tmpTextBoxPos.baseY;  var textAlign = _tmpTextBoxPos.textAlign || 'left';  var textVerticalAlign = _tmpTextBoxPos.textVerticalAlign;  setTextTransform(textSvgEl, needTransformTextByHostEl, elTransform, style, hostRect, baseX, baseY);  var boxY = textContain.adjustTextY(baseY, outerHeight, textVerticalAlign);  var textX = baseX;  var textY = boxY; // TODO needDrawBg  if (textPadding) {    textX = getTextXForPadding(baseX, textAlign, textPadding);    textY += textPadding[0];  } // `textBaseline` is set as 'middle'.  textY += lineHeight / 2;  bindStyle(textSvgEl, style, true, el); // FIXME  // Add a <style> to reset all of the text font as inherit?  // otherwise the outer <style> may set the unexpected style.  // Font may affect position of each tspan elements  var canCacheByTextString = contentBlock.canCacheByTextString;  var tspanList = el.__tspanList || (el.__tspanList = []);  var tspanOriginLen = tspanList.length; // Optimize for most cases, just compare text string to determine change.  if (canCacheByTextString && el.__canCacheByTextString && el.__text === text) {    if (el.__dirtyText && tspanOriginLen) {      for (var idx = 0; idx < tspanOriginLen; ++idx) {        updateTextLocation(tspanList[idx], textAlign, textX, textY + idx * lineHeight);      }    }  } else {    el.__text = text;    el.__canCacheByTextString = canCacheByTextString;    var textLines = contentBlock.lines;    var nTextLines = textLines.length;    var idx = 0;    for (; idx < nTextLines; idx++) {      // Using cached tspan elements      var tspan = tspanList[idx];      var singleLineText = textLines[idx];      if (!tspan) {        tspan = tspanList[idx] = createElement('tspan');        textSvgEl.appendChild(tspan);        tspan.appendChild(document.createTextNode(singleLineText));      } else if (tspan.__zrText !== singleLineText) {        tspan.innerHTML = '';        tspan.appendChild(document.createTextNode(singleLineText));      }      updateTextLocation(tspan, textAlign, textX, textY + idx * lineHeight);    } // Remove unused tspan elements    if (tspanOriginLen > nTextLines) {      for (; idx < tspanOriginLen; idx++) {        textSvgEl.removeChild(tspanList[idx]);      }      tspanList.length = nTextLines;    }  }};function setTextTransform(textSvgEl, needTransformTextByHostEl, elTransform, style, hostRect, baseX, baseY) {  matrix.identity(_tmpTextTransform);  if (needTransformTextByHostEl && elTransform) {    matrix.copy(_tmpTextTransform, elTransform);  } // textRotation only apply in RectText.  var textRotation = style.textRotation;  if (hostRect && textRotation) {    var origin = style.textOrigin;    if (origin === 'center') {      baseX = hostRect.width / 2 + hostRect.x;      baseY = hostRect.height / 2 + hostRect.y;    } else if (origin) {      baseX = origin[0] + hostRect.x;      baseY = origin[1] + hostRect.y;    }    _tmpTextTransform[4] -= baseX;    _tmpTextTransform[5] -= baseY; // Positive: anticlockwise    matrix.rotate(_tmpTextTransform, _tmpTextTransform, textRotation);    _tmpTextTransform[4] += baseX;    _tmpTextTransform[5] += baseY;  } // See the definition in `Style.js#textOrigin`, the default  // origin is from the result of `getBoxPosition`.  setTransform(textSvgEl, _tmpTextTransform);} // FIXME merge the same code with `helper/text.js#getTextXForPadding`;function getTextXForPadding(x, textAlign, textPadding) {  return textAlign === 'right' ? x - textPadding[1] : textAlign === 'center' ? x + textPadding[3] / 2 - textPadding[1] / 2 : x + textPadding[3];}function updateTextLocation(tspan, textAlign, x, y) {  // Consider different font display differently in vertial align, we always  // set vertialAlign as 'middle', and use 'y' to locate text vertically.  attr(tspan, 'dominant-baseline', 'middle');  attr(tspan, 'text-anchor', TEXT_ALIGN_TO_ANCHRO[textAlign]);  attr(tspan, 'x', x);  attr(tspan, 'y', y);}function removeOldTextNode(el) {  if (el && el.__textSvgEl) {    // textSvgEl may has no parentNode if el has been removed temporary.    if (el.__textSvgEl.parentNode) {      el.__textSvgEl.parentNode.removeChild(el.__textSvgEl);    }    el.__textSvgEl = null;    el.__tspanList = [];    el.__text = null;  }}svgText.drawRectText = svgTextDrawRectText;svgText.brush = function (el) {  var style = el.style;  if (style.text != null) {    svgTextDrawRectText(el, false);  } else {    removeOldTextNode(el);  }};exports.path = svgPath;exports.image = svgImage;exports.text = svgText;
 |