tl;dr summary:给予我资源或帮助修复下面的代码,通过任意矩阵转换SVG <path>
元素的路径命令。
详情:
我正在编写一个库,用于将任意SVG形状转换为<path>
元素。当层次结构中没有transform="..."
元素时,我让它工作,但现在我想将对象的本地转换烘焙到路径数据命令本身。
当处理简单的moveto/lineto命令时,这主要是工作 (代码如下)。但是,我不确定变换贝塞尔控制柄或arcTo参数的适当方法。
例如,我可以将这个圆角矩形转换为<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" />
字符串
在没有任何圆角的情况下进行变换时,我得到了一个有效的结果:
<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" />
型
但是,仅变换椭圆弧命令的x/y坐标会产生有趣的结果:x1c 0d1x的数据
- 虚线是实际转换的矩形,绿色填充是我的路径。*
以下是我目前为止的代码(稍微精简)。我也有一个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;
}
型
5条答案
按热度按时间hyrbngr71#
我已经做了一个通用的SVG拼合器flatten.js,它支持所有的形状和路径命令:https://gist.github.com/timo22345/9413158
基本用法:x1n1x;
它的作用:展平元素(将元素转换为路径并展平变换)。如果参数元素(其id在'svg'之上)有子元素,或者它的后代有子元素,这些子元素也会被扁平化。
什么可以被压扁:整个SVG文档、单个形状(路径、圆、椭圆等)和组。嵌套组将自动处理。
属性呢?复制所有属性。仅丢弃路径元素中无效的参数(例如:r,rx,ry,cx,cy),但是它们不再被需要。此外,“变换”(transform)属性也将被删除,因为变换已展平为路径命令。
如果您想使用非仿射方法修改路径坐标(例如透视扭曲),可以使用以下命令将所有分段转换为三次曲线:
flatten(document.getElementById('svg'), true);
个还有参数'toAbsolute'(将坐标转换为绝对值)和'dec',小数点分隔符后的位数。
极端路径和形状测试仪:https://jsfiddle.net/fjm9423q/embedded/result/的
基本用法示例:http://jsfiddle.net/nrjvmqur/embedded/result/的
缺点:文本元素不起作用。这可能是我的下一个目标。
4nkexdtk2#
如果每个对象(圆等)都首先转换为路径,那么考虑变换就相当容易了。我做了一个测试平台(http://jsbin.com/oqojan/73),您可以在其中测试功能。测试台创建随机路径命令并将随机变换应用于路径,然后展平变换。当然,在现实中路径命令和变换不是随机的,但为了测试精度,这是好的。
有一个函数flatten_transformations(),它使主要任务:
字符串
该代码使用Raphael.pathToRelative()、Raphael._pathToAbsolute()和Raphael.path2curve()。Raphael.path2curve()是一个修复了bug的版本。
如果使用参数normalize_path=true调用flatten_transformations(),则所有命令都转换为Cubics,一切正常。代码可以通过删除
if (letter == "A") { ... }
以及删除H、V和Z的处理来简化。简化版本可以是类似this的东西。但是因为有些人可能只想烘焙变换,而不想进行All Segs -> Cubics归一化,所以我添加了一种可能性。因此,如果你想使用normalize_path=false来展平变换,这意味着椭圆弧参数也必须被展平,并且不可能通过简单地将矩阵应用于坐标来处理它们。两个半径(rx ry)、x轴旋转、大圆弧标志和扫掠标志必须分别处理。所以下面的函数可以使弧的变换变平。matrix参数是一个关系矩阵,它来自flatten_transformations()。
型
旧示例:
我做了一个an example,它有一个包含
M Q A A Q M
段的路径,其中应用了转换。路径在g内部,也应用了trans。为了确保这个g是在另一个g的内部,这个g被应用了不同的变换。代码可以:A)首先规范化所有路径段(感谢Raphaël的path 2curve,我对它做了a bug fix,在此修复之后,所有可能的路径段组合最终都工作了:http://jsbin.com/oqojan/42。原始的Raphaël 2.1.0有错误的行为,如您所见here,如果没有,请单击路径几次以生成新曲线。)
B)然后使用原生函数
getTransformToElement()
,createSVGPoint()
和matrixTransform()
进行扁平化变换。唯一缺少的是将圆形,矩形和多边形转换为路径命令的方法,但据我所知,你有一个很好的代码。
oknwwptz3#
更新1:我已经让绝对arcto命令完美地工作了,除了在非均匀比例的情况下。以下是新增内容:
字符串
感谢this answer提供了比我使用的更简单的提取方法,以及用于提取非均匀尺度的数学方法。
polhcujo4#
只要你把所有的坐标都转换成绝对坐标,所有的béziers都能正常工作;他们的手柄没有什么神奇之处。至于椭圆弧命令,唯一的通用解决方案(如您所指出的,在一般情况下,处理非均匀缩放,弧命令无法表示)是首先将它们转换为它们的贝塞尔近似值。
https://github.com/johan/svg-js-utils/blob/df605f3e21cc7fcd2d604eb318fb2466fd6d63a7/paths.js#L56..L113
absolutizePath
在同一个文件中,你的Convert SVG Path to Absolute Commands黑客的直接端口)做前者,但还没有后者.How to best approximate a geometrical arc with a Bezier curve?链接用于将圆弧转换为贝塞尔曲线的数学(每个
0 < α <= π/2
圆弧段一个贝塞尔曲线段); this paper在页面的末尾显示了方程(它更漂亮的pdf版本在3.4.1节的末尾)。wgxvkvu95#
灵感来自Timo Kähkönen的答案和他的flatten.jsgist
我使用Jarek Foksa's getpathData() polyfill编写了一个类似的帮助脚本来获取所需的数据。
工作原理
<path>
。通过调用element.getpathData({normalize:true})
,我们还可以获得<rect>
或<circle>
等原语的路径数据。此外,所有命令都转换为绝对命令,只使用立方béziers和linetos。Arctos被转换为立方贝济尔。
型
matrixTransform()
重新计算所有命令坐标,如下所示let pt = svg.createSVGPoint(); pt.x = x; pt.y = y; let pTrans = pt.matrixTransform(matrix);
<text>
元素?显然,我们不能将文本元素转换为路径(除非,我们使用的是像opentype.js或fontkit这样的库)。但是我们可以合并所有对文本有影响的转换,并应用一个自包含的转换属性值。
我使用的
qrDecomposeMatrix()
辅助函数基于AndreaBogazzi的伟大答案:“查找矩阵变换的旋转和倾斜”,将当前矩阵拆分为单独的变换函数,如translate()
,scale()
等。测试:Codepen example的