从 Javascript 中的 SVG 对象中提取所有行

Extract all lines from an SVG object in Javascript

我试图从一个 SVG 对象中提取所有的线条,以便它们可以被一个特殊的光栅化器渲染,但事实证明它比我想象的要复杂得多。

主要挑战是:

有没有一种简单的方法可以做到这一点,或者有一些已经存在的代码可以避免重新发明轮子?我在 Javascript.

工作

谢谢!

第二点在先:
将弯曲路径转换为直线路径:
此时,它不会将其转换为 <line> 个元素,但这样做应该不会太困难,我不确定您是否真的需要这个。

function simplifyPathes(svg) {
  var pathes = svg.querySelectorAll('path');
  for (i = 0; i < pathes.length; i++) {
    for (var j = 0; j < pathes[i].pathSegList.numberOfItems; j++) {
      var segment = pathes[i].pathSegList.getItem(j);
      if (segment.pathSegType > 4) {
        if (segment.pathSegType & 1) {
          var newSeg = pathes[i].createSVGPathSegLinetoRel(segment.x, segment.y);
        } else {
          var newSeg = pathes[i].createSVGPathSegLinetoAbs(segment.x, segment.y);
        }
        pathes[i].pathSegList.replaceItem(newSeg, j);
      }
    }
  }
}

setTimeout(function(){simplifyPathes(document.querySelector('svg'))}, 1500);
<svg width="600" height="300" viewBox="0 0 600 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <path id="path" fill="none" stroke="#000000" stroke-miterlimit="10" d="M286,81c-70.7,14-134.3-57.3-91.3-2.7s145,74.7,53,74.3 C155.7,152.3,150,16,164.3,93c14.3,77-33.7,68.7,53,87s-23.7,31.7-123,6.3-52,86-150,0" />
  </defs>
  <path fill="none" stroke="#000000" stroke-miterlimit="10" d="M161,88.7c-3.3,44-58.4-5.7-58.4-5.7s-41-31,49.4-31.7 c90.3-0.7,86-61.3,83.7,2.7c-2.3,64,54.3,57.3-19.3,71.6S-27,47.7,89,110.7s107.7,152.7,106.7,65.7" />
  <use xlink:href="#path" transform="translate(75, 25)" />
</svg>

可能有更好的方法,但我还没有。
Some readings here
你可能想将它与一些库结合起来,比如 simplify.js

现在,问题的第一部分。
我假设如果 <use> 个元素有问题,那是因为 Gecko's restrictive rules on external resources in svg Images.
如果不是这样,我很好奇你为什么需要 "flattening"。

我写了一个可怕的 hack,可能会告诉你一个解决方法。

function cloneUses(svg) {
  var uses = svg.querySelectorAll('use')
  for (i = 0; i < uses.length; i++) {
 var elem = uses[i],
  id = elem.getAttributeNS('http://www.w3.org/1999/xlink', 'href').substring(1),
   c = svg.getElementById(id).cloneNode(true),
   b = elem.transform.baseVal,
   a = elem.attributes;
 if(b.length>1){
  b.consolidate();
  c.transform.baseVal.appendItem(b.getItem(0));
  c.transform.baseVal.consolidate();
  }
 for (j = 0; j < a.length; j++) {
   if (a[j].value !== ('#' + id) && a[j].value !== "transform") {
  c.setAttribute(a[j].name, a[j].value);
   }
 }
 elem.parentNode.insertBefore(c, elem);
 elem.parentNode.removeChild(elem);
  }
}
setTimeout(function(){cloneUses(document.querySelector('svg'))}, 1500);
<svg width="600" height="300" viewBox="0 0 600 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <line id="line" stroke="black" x1="72" y1="120" x2="25" y2="25" />
    <path id="path" fill="none" stroke="#000000" stroke-miterlimit="10" d="M286,81c-70.7,14-134.3-57.3-91.3-2.7s145,74.7,53,74.3 C155.7,152.3,150,16,164.3,93c14.3,77-33.7,68.7,53,87s-23.7,31.7-123,6.3-52,86-150,0" />
  </defs>
  <use xlink:href="#path" transform="translate(75, 25)" />
  <use xlink:href="#line" transform="translate(125, 0)" />
</svg>
通过浏览器的检查器查看 svg

Ps:我让你自己想办法把<circle>变成<line>

感谢凯多的回答!非常感激!这样做的原因是我需要为 SVG 使用自定义光栅化器来纠正某些图像失真。它不用于传统的网络环境。

下面的方法使用两个函数将路径分解为线段。一个函数 _samplePath 可以沿任意 SVG 路径采样点。另一个 pathToLines 能够检查每条路径并生成(对于某些路径更有效)所需的线段。对于包含需要大量时间支持的 besiers、arcs 和其他奇怪的路径,pathToLines 返回到 _samplePath

这是基于我在 SO 上找到的一些示例。

/**
 * Take an SVG path and use a sampling technique to break it down into line segments.
 * @param The path element.
 * @param The list of lines to write into. Default is to create a new list and return it.
 * @param The number of samples to take. Default is length / 2.
 * @return The resulting list of line segment.
 */
function _samplePath(path, lines, samples) {
 
 var length  = path.getTotalLength();
 lines = lines || []
 samples = samples || length / 3;
 var segSize = length / samples;
 
 var last_pt = path.getPointAtLength(0);
 for (var i = 0; i < samples; ++i) {
  
  // Compute the next sample point.
  var pt = path.getPointAtLength(segSize * i);
  
  // If this sample point is over a certain size relative to the segSize, we have hit a m/M and jumped.
  //  Thus we should not render this bit...
  if (Math.pow(last_pt.x - pt.x, 2) + Math.pow(last_pt.y - pt.y, 2) < (segSize*segSize*2))
   lines.push({ x1: last_pt.x, y1: last_pt.y, x2: pt.x, y2: pt.y })
  
  // Next line please.
  last_pt = pt;
 }
 
 // Last line.
 var pt = path.getPointAtLength(length);
 lines.push({ x1: last_pt.x, y1: last_pt.y, x2: pt.x, y2: pt.y })
 
 // Return the lines.
 return lines;
}

/**
 * Take any SVG path element and flatten it into a line list in absolute coordinates.
 * @param path The SVG path element to flatten and segment.
 * @param The list of lines to write into. Default is to create a new list and return it.
 * @return The list of line segements.
 */
function pathToLines(path, lines) {
 
 // Init.
 var xl, yl, x0, y0, x1, y1, x2, y2, segs = path.pathSegList;
 var segs = path.pathSegList;
 lines = lines || [];
 if (segs.length <= 1) return lines;
 
 // Rendering functions.
 function emitLine(x, y) { lines.push({ x1: xl, y1: yl, x2: x, y2: y }); xl = x; yl = y; } 
 //function emitArc(x, y, rx, ry, large, sweep, segmentsize) { }
 
 // If we have weird objects I don't want to support by hand...
 //   Handle as a complex path with a given number of samples.
 for (var i = 0, len = segs.numberOfItems; i < len; i++) { 
  if (/[csqtaCSQTA]/.test(segs.getItem(i).pathSegTypeAsLetter))
   return _samplePath(path, lines);
 }
 
 // Otherwise, parse the path for a more accurate and optimal experience.
    for (var x = 0, y = 0, i = 0, len = segs.numberOfItems; i < len; i++)
 {
  var seg = segs.getItem(i);
  var c = seg.pathSegTypeAsLetter;
  
  // Handle absolute coordinates.
  if (/[MLHVCSQTA]/.test(c)) {
   if ('x' in seg) x = seg.x;
   if ('y' in seg) y = seg.y;
  }
  
  // Handle relative coordinates.
  else
  {
   //if ('x1' in seg) x1 = seg.x1 + x;
   //if ('x2' in seg) x2 = seg.x2 + x;
   //if ('y1' in seg) y1 = seg.y1 + y;
   //if ('y2' in seg) y2 = seg.y2 + y;
   if ('x'  in seg) x += seg.x;
   if ('y'  in seg) y += seg.y;
  }
  
  switch (c)
  {
   case 'm': case 'M': x0=x, y0=y; xl=x; yl=y;   break;  // MoveTo (just skip)
   case 'l': case 'L': emitLine(x, y);     break;  // LineTo (emit)
   case 'h': case 'H': emitLine(x, yl);     break;  // LineToHorizontalAbs (emit with y0)
   case 'v': case 'V': emitLine(xl, y);             break;  // LinetoVerticalAbs (emit with x0)
   case 'z': case 'Z': emitLine(x0, y0);     break;  // Move back to zero (close the line).
   
   // Flatten an arc efficiently?
   //case 'a': case 'A': console.log("Arc: " + seg);  break;
   
   
  }
 }
 
 return lines;
}

<use><defs> 相关的问题的另一部分使用了 SVG 矩阵。

// Get the use element and its target transformation matrix t0.
var element = $("use").get(0);
var link = use[i].getAttribute("xlink:href")
var t0 = use[i].getScreenCTM();

// Find the source element and its current transformation matrix t1.
//  This is not the same as the <use> element, because the inners are offset in the <defs>.
var defLink = $(svg).find(link);
var t1 = defLink.get(0).getCTM();

// Then use this function to transform each point on the line by the returned SVGMatrix.
function transformSVGPoint(point, matrix) {
  return {
    x: point.x * matrix.a + point.y * matrix.c + matrix.e,
    y: point.x * matrix.b + point.y * matrix.d + matrix.f
  }
}