javascript - 烘焙转换为 SVG 路径元素命令

标签 javascript svg 2d transform

tl;dr 摘要:给我资源或帮助修复以下代码以转换 SVG 的路径命令 <path>任意矩阵的元素。

详细信息:
我正在编写一个库来将任意 SVG 形状转换为 <path>元素。当没有transform="..."时我让它工作层次结构中的元素,但现在我想将对象的局部变换烘焙到 path data 中命令自己。

在处理简单的 moveto/lineto 命令时,这主要是有效的(代码如下)。但是,我不确定转换贝塞尔曲线 handle 或 arcTo 参数的适当方法。

例如,我可以将此圆 Angular 矩形转换为 <path> :

<rect x="10" y="30" rx="10" ry="20" width="80" height="70" />
--> <path d=​"M20,30 L80,30 A10,20,0,0,1,90,50 L90,80 A10,20,0,0,1,80,100
             L20,100 A10,20,0,0,1,10,80 L10,50 A10,20,0,0,1,20,30" />

在没有任何圆 Angular 的情况下进行转换时,我得到了有效的结果:

<rect x="10" y="30" width="80" height="70"
      transform="translate(-200,0) scale(1.5) rotate(50)" />
--> <path d=​"M10,30 L90,30 L90,100 L10,100 L10,30" />

但是,仅转换 elliptical arc 的 x/y 坐标命令产生有趣的结果: Rounded rectangle with green blobs oozing from the corners outside the boundary
虚线是实际转换后的矩形,绿色填充是我的路径。

以下是我到目前为止的代码(稍微精简)。我还有一个test page我正在测试各种形状。请帮助我确定如何正确转换 elliptical arc以及给定任意变换矩阵的各种其他贝塞尔命令。

function flattenToPaths(el,transform,svg){
  if (!svg) svg=el; while(svg && svg.tagName!='svg') svg=svg.parentNode;
  var doc = el.ownerDocument;
  var svgNS = svg.getAttribute('xmlns');

  // Identity transform if nothing passed in
  if (!transform) transform= svg.createSVGMatrix();

  // Calculate local transform matrix for the object
  var localMatrix = svg.createSVGMatrix();
  for (var xs=el.transform.baseVal,i=xs.numberOfItems-1;i>=0;--i){
    localMatrix = xs.getItem(i).matrix.multiply(localMatrix);
  }
  // Transform the local transform by whatever was recursively passed in
  transform = transform.multiply(localMatrix);

  var path = doc.createElementNS(svgNS,'path');
  switch(el.tagName){
    case 'rect':
      path.setAttribute('stroke',el.getAttribute('stroke'));
      var x  = el.getAttribute('x')*1,     y  = el.getAttribute('y')*1,
          w  = el.getAttribute('width')*1, h  = el.getAttribute('height')*1,
          rx = el.getAttribute('rx')*1,    ry = el.getAttribute('ry')*1;
      if (rx && !el.hasAttribute('ry')) ry=rx;
      else if (ry && !el.hasAttribute('rx')) rx=ry;
      if (rx>w/2) rx=w/2;
      if (ry>h/2) ry=h/2;
      path.setAttribute('d',
        'M'+(x+rx)+','+y+
        'L'+(x+w-rx)+','+y+
        ((rx||ry) ? ('A'+rx+','+ry+',0,0,'+(rx*ry<0?0:1)+','+(x+w)+','+(y+ry)) : '') +
        'L'+(x+w)+','+(y+h-ry)+
        ((rx||ry) ? ('A'+rx+','+ry+',0,0,'+(rx*ry<0?0:1)+','+(x+w-rx)+','+(y+h)) : '')+
        'L'+(x+rx)+','+(y+h)+
        ((rx||ry) ? ('A'+rx+','+ry+',0,0,'+(rx*ry<0?0:1)+','+x+','+(y+h-ry)) : '')+
        'L'+x+','+(y+ry)+
        ((rx||ry) ? ('A'+rx+','+ry+',0,0,'+(rx*ry<0?0:1)+','+(x+rx)+','+y) : '')
      );
    break;

    case 'circle':
      var cx = el.getAttribute('cx')*1, cy = el.getAttribute('cy')*1,
          r  = el.getAttribute('r')*1,  r0 = r/2+','+r/2;
      path.setAttribute('d','M'+cx+','+(cy-r)+' A'+r0+',0,0,0,'+cx+','+(cy+r)+' '+r0+',0,0,0,'+cx+','+(cy-r) );
    break;

    case 'ellipse':
      var cx = el.getAttribute('cx')*1, cy = el.getAttribute('cy')*1,
          rx = el.getAttribute('rx')*1, ry = el.getAttribute('ry')*1;
      path.setAttribute('d','M'+cx+','+(cy-ry)+' A'+rx+','+ry+',0,0,0,'+cx+','+(cy+ry)+' '+rx+','+ry+',0,0,0,'+cx+','+(cy-ry) );
    break;

    case 'line':
      var x1=el.getAttribute('x1')*1, y1=el.getAttribute('y1')*1,
          x2=el.getAttribute('x2')*1, y2=el.getAttribute('y2')*1;
      path.setAttribute('d','M'+x1+','+y1+'L'+x2+','+y2);
    break;

    case 'polyline':
    case 'polygon':
      for (var i=0,l=[],pts=el.points,len=pts.numberOfItems;i<len;++i){
        var p = pts.getItem(i);
        l[i] = p.x+','+p.y;
      }
      path.setAttribute('d',"M"+l.shift()+"L"+l.join(' ') + (el.tagName=='polygon') ? 'z' : '');
    break;

    case 'path':
      path = el.cloneNode(false);
    break;
  }

  // Convert local space by the transform matrix
  var x,y;
  var pt = svg.createSVGPoint();
  var setXY = function(x,y,xN,yN){
    pt.x = x; pt.y = y;
    pt = pt.matrixTransform(transform);
    if (xN) seg[xN] = pt.x;
    if (yN) seg[yN] = pt.y;
  };

  // Extract rotation and scale from the transform
  var rotation = Math.atan2(transform.b,transform.d)*180/Math.PI;
  var sx = Math.sqrt(transform.a*transform.a+transform.c*transform.c);
  var sy = Math.sqrt(transform.b*transform.b+transform.d*transform.d);

  // FIXME: Must translate any Horizontal or Vertical lineto commands into absolute moveto
  for (var segs=path.pathSegList,c=segs.numberOfItems,i=0;i<c;++i){
    var seg = segs.getItem(i);

    // Odd-numbered path segments are all relative
    // http://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg
    var isRelative = (seg.pathSegType%2==1);
    var hasX = seg.x != null;
    var hasY = seg.y != null;
    if (hasX) x = isRelative ? x+seg.x : seg.x;
    if (hasY) y = isRelative ? y+seg.y : seg.y;
    if (hasX || hasY) setXY( x, y, hasX && 'x', hasY && 'y' );

    if (seg.x1 != null) setXY( seg.x1, seg.y1, 'x1', 'y1' );
    if (seg.x2 != null) setXY( seg.x2, seg.y2, 'x2', 'y2' );
    if (seg.angle != null){
      seg.angle += rotation;
      seg.r1 *= sx; // FIXME; only works for uniform scale
      seg.r2 *= sy; // FIXME; only works for uniform scale
    }
  }

  return path;
}

最佳答案

我制作了一个通用的 SVG 拼合器 flatten.js,它支持所有形状和路径命令: https://gist.github.com/timo22345/9413158

基本用法:flatten(document.getElementById('svg'));

它的作用:扁平化元素(将元素转换为路径并扁平化转换)。 如果参数元素(其 id 高于“svg”)有子元素,或者它的后代有子元素, 这些子元素也被展平。

可以展平的内容:整个 SVG 文档、单个形状(路径、圆形、椭圆形等)和组。嵌套组会自动处理。

属性怎么样?所有属性均被复制。仅删除路径元素中无效的参数(例如 r、rx、ry、cx、cy),但不再需要它们。变换属性也被删除,因为变换被扁平化为路径命令。

如果您想使用非仿射方法修改路径坐标(例如透视扭曲), 您可以使用以下方法将所有线段转换为三次曲线: 展平(document.getElementById('svg'), true);

还有参数“toAbsolute”(将坐标转换为绝对坐标)和“dec”, 小数点分隔符后的位数。

极限路径和形状测试仪:https://jsfiddle.net/fjm9423q/embedded/result/

基本用法示例:http://jsfiddle.net/nrjvmqur/embedded/result/

缺点:文本元素不起作用。这可能是我的下一个目标。

关于javascript - 烘焙转换为 SVG 路径元素命令,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5149301/

相关文章:

javascript - Angular ControlValueAccessor 的默认值因内部原因而变脏

javascript - JavaScript 中的 Console.log 输出

java - 在 Java 游戏上轻松从 2D 移植到 3D

html - 带有过滤器的 SVG 投影被截断

svg - 带有 SVG 的双重绑定(bind) Angular 2

rust - 如果我在调用 present 之前更改 Canvas 的 vew_port,为什么会有旧图像闪烁?

java - Android 开发路线图

javascript - 添加到列表而不是单击时替换内容

javascript - For Var In Loop - 我可以从中省略变量吗?

Javascript SVG 查看器库