烘焙转换为 SVG 路径元素命令 [英] Baking transforms into SVG Path Element commands

查看:31
本文介绍了烘焙转换为 SVG 路径元素命令的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

tl;dr summary:给我资源或帮助修复以下代码以通过任意矩阵转换 SVG 元素的路径命令.

详细信息:
我正在编写一个库来将任何任意 SVG 形状转换为 元素.当层次结构中没有 transform="..." 元素时,我让它工作,但现在我想将对象的本地转换烘焙到 ,我正在测试各种形状.请帮助我确定如何在给定任意变换矩阵的情况下正确变换 椭圆弧 和其他各种贝塞尔命令.

function flattenToPaths(el,transform,svg){如果 (!svg) svg=el;while(svg && svg.tagName!='svg') svg=svg.parentNode;var doc = el.ownerDocument;var svgNS = svg.getAttribute('xmlns');//如果没有传入,则身份转换if (!transform) transform= svg.createSVGMatrix();//计算对象的局部变换矩阵var localMatrix = svg.createSVGMatrix();for (var xs=el.transform.baseVal,i=xs.numberOfItems-1;i>=0;--i){localMatrix = xs.getItem(i).matrix.multiply(localMatrix);}//通过递归传入的任何内容转换本地转换变换 = 变换.乘法(localMatrix);var path = doc.createElementNS(svgNS,'path');开关(el.tagName){案例'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;否则如果 (ry && !el.hasAttribute('rx')) rx=ry;如果(rx>w/2)rx=w/2;如果(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) : ''));休息;案例'圈':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));休息;案例'椭圆':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));休息;案例行":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);休息;案例折线":案例多边形":for (var i=0,l=[],pts=el.points,len=pts.numberOfItems;i

解决方案

我做了一个通用的 SVG flattener flatten.js,支持所有的形状和路径命令:https://gist.github.com/timo22345/9413158

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

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

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

属性呢?复制所有属性.仅删除在路径元素中无效的参数(例如 r、rx、ry、cx、cy),但不再需要它们.还删除了转换属性,因为转换被扁平化为路径命令.

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

还有参数'toAbsolute'(将坐标转换为绝对坐标)和'dec',小数点后的位数.

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

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

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

tl;dr summary: Give me the resources or help fix the below code to transform path commands for SVG <path> elements by an arbitrary matrix.

details:
I'm writing a library to convert any arbitrary SVG shape into a <path> element. I have it working when there are no transform="..." elements in the hierarchy, but now I want to bake the local transform of the object into the
path data commands themselves.

This is mostly working (code below) when dealing with the simple moveto/lineto commands. However, I'm not sure of the appropriate way to transform the bezier handles or arcTo parameters.

For example, I am able to convert this rounded rectangle to a <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" />

And I get a valid result when transforming without any round corners:

<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" />

However, transforming only the x/y coords of the elliptical arc commands yields amusing results:
The dotted line is the actual transformed rect, the green fill is my path.

Following is the code I have so far (slightly pared-down). I also have a test page where I'm testing various shapes. Please help me determine how to properly transform the elliptical arc and various other bezier commands given an arbitrary transformation matrix.

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;
}

解决方案

I have made a general SVG flattener flatten.js, that supports all shapes and path commands: https://gist.github.com/timo22345/9413158

Basic usage: flatten(document.getElementById('svg'));

What it does: Flattens elements (converts elements to paths and flattens transformations). If the argument element (whose id is above 'svg') has children, or it's descendants has children, these children elements are flattened also.

What can be flattened: entire SVG document, individual shapes (path, circle, ellipse etc.) and groups. Nested groups are handled automatically.

How about attributes? All attributes are copied. Only arguments that are not valid in path element, are dropped (eg. r, rx, ry, cx, cy), but they are not needed anymore. Also transform attribute is dropped, because transformations are flattened to path commands.

If you want to modify path coordinates using non-affine methods (eg. perspective distort), you can convert all segments to cubic curves using: flatten(document.getElementById('svg'), true);

There are also arguments 'toAbsolute' (convert coordinates to absolute) and 'dec', number of digits after decimal separator.

Extreme path and shape tester: https://jsfiddle.net/fjm9423q/embedded/result/

Basic usage example: http://jsfiddle.net/nrjvmqur/embedded/result/

CONS: text element is not working. It could be my next goal.

这篇关于烘焙转换为 SVG 路径元素命令的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆