如何以编程方式在 SVG 文件中进行组转换?
How to do the group transforms in SVG file programmatically?
我有包含以下图像的 SVG 文件:
每个箭头都由这样的代码表示:
<g
transform="matrix(-1,0,0,-1,149.82549,457.2455)"
id="signS"
inkscape:label="#sign">
<title
id="title4249">South, 180</title>
<path
sodipodi:nodetypes="ccc"
inkscape:connector-curvature="0"
id="path4251"
d="m 30.022973,250.04026 4.965804,-2.91109 4.988905,2.91109"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.6855976px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
y="250.11305"
x="29.768578"
height="2.6057031"
width="10.105703"
id="rect4253"
style="fill:#008000;fill-opacity:1;stroke:#000000;stroke-width:0.53715414;stroke-opacity:1" />
</g>
我想计算矩形(rect
节点)的绝对位置。为此,我需要评估 g
标签(上例中的 matrix(-1,0,0,-1,149.82549,457.2455)
)的 transform
标签内的表达式。
我该怎么做?
我假设第一步是使用 Apache Batik:
将文件读取为 SVGDocument
import java.io.IOException;
import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.util.XMLResourceDescriptor;
import org.w3c.dom.Document;
try {
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
String uri = "http://www.example.org/diagram.svg";
Document doc = f.createDocument(uri);
} catch (IOException ex) {
// ...
}
据我所知,doc
可以转换为SVGDocument。
如何从 SVG 文档获取矩形或组的绝对位置?
注意:我需要一些 现有的 代码来执行上述转换(不要让我自己实现这些转换 - 它们必须已经实现,我想重新- 使用该代码)。
更新 1(08.11.2015 12:56 MSK):
首次尝试实施 Robert Longson 的建议:
public final class BatikTest {
@Test
public void test() throws XPathExpressionException {
try {
final File initialFile =
new File("src/main/resources/trailer/scene05_signs.svg");
InputStream sceneFileStream = Files.asByteSource(initialFile).openStream();
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
String uri = "http://www.example.org/diagram.svg";
final SVGOMDocument doc = (SVGOMDocument) f.createDocument(
uri, sceneFileStream);
final NodeList nodes =
doc.getDocumentElement().getElementsByTagName("g");
SVGOMGElement signSouth = null;
for (int i=0; (i < nodes.getLength()) && (signSouth == null); i++) {
final Node curNode = nodes.item(i);
final Node id = curNode.getAttributes().getNamedItem("id");
if ("signS".equals(id.getTextContent())) {
signSouth = (SVGOMGElement) curNode;
}
System.out.println("curNode: " + nodes);
}
System.out.println("signSouth: " + signSouth);
final NodeList rectNodes = signSouth.getElementsByTagName("rect");
System.out.println("rectNodes: " + rectNodes);
SVGOMRectElement rectNode = (SVGOMRectElement) rectNodes.item(0);
System.out.println("rectNode: " + rectNode);
final SVGMatrix m2 =
signSouth.getTransformToElement(rectNode);
System.out.println("m2: " + m2);
} catch (IOException ex) {
Assert.fail(ex.getMessage());
}
}
}
对 m2.getA()
-m2.getF()
的调用导致 NullPointerException
秒。
更新 2(08.11.2015 13:38 MSK):
添加了以下代码来创建 SVGPoint
并对其应用矩阵变换:
final SVGSVGElement docElem = (SVGSVGElement)
doc.getDocumentElement();
final SVGPoint svgPoint = docElem.createSVGPoint();
svgPoint.setX((float) x);
svgPoint.setY((float) y);
final SVGPoint svgPoint1 =
svgPoint.matrixTransform(signSouth.getScreenCTM()); // Line 77
System.out.println("x: " + svgPoint1.getX());
System.out.println("y: " + svgPoint1.getY());
结果:
java.lang.NullPointerException
at org.apache.batik.dom.svg.SVGLocatableSupport.getAffineTransform(Unknown Source)
at org.apache.batik.dom.svg.AbstractSVGMatrix.getA(Unknown Source)
at org.apache.batik.dom.svg.SVGOMPoint.matrixTransform(Unknown Source)
at org.apache.batik.dom.svg.SVGOMPoint.matrixTransform(Unknown Source)
at [...].BatikTest.test(BatikTest.java:77)
更新 3,赏金条款 (10.11.2015 MSK):
获得赏金必须满足的条件:
我会将赏金奖励给在 JUnit 测试 BatikTest 中设法实现方法 magicallyCalculateXCoordinate
和 magicallyCalculateYCoordinate
的男女主角,这样我就可以在我的 Java 编码 InkScape 中显示的形状的坐标(示例请参见以下屏幕截图)。
计算 SVG 文件中形状位置的方法必须有效
- 对于组节点(如图片和 sample file 中的节点)或
- 三角形。
您提供的代码必须适用于示例文件中的所有四种形状,即。 e.使用它我必须能够在 Java 代码中计算出它们的坐标,这与 Inkscape 中显示的坐标相同。
您可以向方法 magicallyCalculateXCoordinate
和 magicallyCalculateYCoordinate
添加参数,您也可以创建自己的方法来计算坐标。
您可以使用任何可合法用于商业目的的库。
与此请求相关的所有文件都可以在 GitHub 上找到。我设法使用 IntelliJ Idea Community Edition 14.1.5 编译测试。
我知道,我来晚了,但我偶然发现了这个问题并且很喜欢这个谜语 ;-)
svg 中的原点是左上角,而 inkscape 中的原点是左下角。
所以我们需要应用描边和变换,然后找到左下角的点四舍五入到三位小数。
我使用 GVTBuilder 获取应用样式的边界框,然后转换 boundingBox,请求转换组的边界框,然后使用 xMin 和 yMax 作为参考点。使用 viewbox 确定高度,我转换了 y 坐标,最后对坐标进行了四舍五入。
(请参阅 github 上的拉取请求)
package test.java.svgspike;
import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
import org.apache.batik.anim.dom.SVGOMDocument;
import org.apache.batik.anim.dom.SVGOMGElement;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.util.XMLResourceDescriptor;
import javax.xml.xpath.XPathExpressionException;
import java.awt.geom.Point2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import com.google.common.io.Files;
import org.junit.Assert;
import org.junit.Test;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Created by pisarenko on 10.11.2015.
*/
public final class BatikTest {
@Test
public void test() throws XPathExpressionException {
try {
final File initialFile =
new File("src/test/resources/scene05_signs.svg");
InputStream sceneFileStream = Files.asByteSource(initialFile).openStream();
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
String uri = "http://www.example.org/diagram.svg";
final SVGOMDocument doc = (SVGOMDocument) f.createDocument(
uri, sceneFileStream);
String viewBox = doc.getDocumentElement().getAttribute("viewBox");
Point2D referencePoint = getReferencePoint(doc, getGroupElement(doc, "signS"));
double signSouthX = magicallyCalculateXCoordinate(referencePoint);
double signSouthY = magicallyCalculateYCoordinate(referencePoint, viewBox);
Assert.assertEquals(109.675, signSouthX, 0.0000001);
Assert.assertEquals(533.581, signSouthY, 0.0000001);
referencePoint = getReferencePoint(doc, getGroupElement(doc, "signN"));
Assert.assertEquals(109.906, magicallyCalculateXCoordinate(referencePoint), 0.0000001);
Assert.assertEquals(578.293, magicallyCalculateYCoordinate(referencePoint, viewBox), 0.0000001);
referencePoint = getReferencePoint(doc, getGroupElement(doc, "signE"));
Assert.assertEquals(129.672, magicallyCalculateXCoordinate(referencePoint), 0.0000001);
Assert.assertEquals(554.077, magicallyCalculateYCoordinate(referencePoint, viewBox), 0.0000001);
referencePoint = getReferencePoint(doc, getGroupElement(doc, "signW"));
Assert.assertEquals(93.398, magicallyCalculateXCoordinate(referencePoint), 0.0000001);
Assert.assertEquals(553.833, magicallyCalculateYCoordinate(referencePoint, viewBox), 0.0000001);
} catch (IOException ex) {
Assert.fail(ex.getMessage());
}
}
private SVGOMGElement getGroupElement(SVGOMDocument doc, String id){
final NodeList nodes = doc.getDocumentElement().getElementsByTagName("g");
SVGOMGElement signGroup = null;
for (int i=0; (i < nodes.getLength()) && (signGroup == null); i++) {
final Node curNode = nodes.item(i);
final Node idNode = curNode.getAttributes().getNamedItem("id");
if (id.equals(idNode.getTextContent())) signGroup = (SVGOMGElement) curNode;
}
return signGroup;
}
/**
* @param doc
* @param signGroup
* @return the reference point, inkscape uses for group (bottom left corner of group)
*/
private Point2D getReferencePoint(SVGOMDocument doc, SVGOMGElement signGroup){
Point2D referencePoint = new Point2D.Double(0, 0);
try {
BridgeContext ctx = new BridgeContext(new UserAgentAdapter());
new GVTBuilder().build(ctx, doc);
GraphicsNode gvtElement = new GVTBuilder().build(ctx, signGroup);
Rectangle2D rc = gvtElement.getSensitiveBounds();
rc = ((Path2D) gvtElement.getTransform().createTransformedShape(rc)).getBounds2D();
//find xMin and yMax in poi
referencePoint = new Point2D.Double(rc.getMinX(), rc.getMaxY());
} catch (Exception e) {
e.printStackTrace();
}
return referencePoint;
}
/**
* inkscape states y coordinate with origin in left bottom corner, while svg uses top left corner as origin
* @param referencePoint bottom left corner of group
* @param viewBox in "originX originY width height" notation
* @return corrected y coordinate, rounded to three decimal figures (half up)
*/
private double magicallyCalculateYCoordinate(Point2D referencePoint, String viewBox) {
String[] viewBoxValues = viewBox.split(" ");
BigDecimal roundedY = new BigDecimal(Double.parseDouble(viewBoxValues[3])-referencePoint.getY());
roundedY = roundedY.setScale(3, BigDecimal.ROUND_HALF_UP);
return roundedY.doubleValue();
}
/**
* @param referencePoint bottom left corner of group
* @return x coordinate, rounded to three decimal figures (half up)
*/
private double magicallyCalculateXCoordinate(Point2D referencePoint) {
BigDecimal roundedX = new BigDecimal(referencePoint.getX()).setScale(3, BigDecimal.ROUND_HALF_UP);
return roundedX.doubleValue();
}
}
它应该适用于所有组和所有转换。
我有包含以下图像的 SVG 文件:
每个箭头都由这样的代码表示:
<g
transform="matrix(-1,0,0,-1,149.82549,457.2455)"
id="signS"
inkscape:label="#sign">
<title
id="title4249">South, 180</title>
<path
sodipodi:nodetypes="ccc"
inkscape:connector-curvature="0"
id="path4251"
d="m 30.022973,250.04026 4.965804,-2.91109 4.988905,2.91109"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.6855976px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
y="250.11305"
x="29.768578"
height="2.6057031"
width="10.105703"
id="rect4253"
style="fill:#008000;fill-opacity:1;stroke:#000000;stroke-width:0.53715414;stroke-opacity:1" />
</g>
我想计算矩形(rect
节点)的绝对位置。为此,我需要评估 g
标签(上例中的 matrix(-1,0,0,-1,149.82549,457.2455)
)的 transform
标签内的表达式。
我该怎么做?
我假设第一步是使用 Apache Batik:
将文件读取为SVGDocument
import java.io.IOException;
import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.util.XMLResourceDescriptor;
import org.w3c.dom.Document;
try {
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
String uri = "http://www.example.org/diagram.svg";
Document doc = f.createDocument(uri);
} catch (IOException ex) {
// ...
}
据我所知,doc
可以转换为SVGDocument。
如何从 SVG 文档获取矩形或组的绝对位置?
注意:我需要一些 现有的 代码来执行上述转换(不要让我自己实现这些转换 - 它们必须已经实现,我想重新- 使用该代码)。
更新 1(08.11.2015 12:56 MSK):
首次尝试实施 Robert Longson 的建议:
public final class BatikTest {
@Test
public void test() throws XPathExpressionException {
try {
final File initialFile =
new File("src/main/resources/trailer/scene05_signs.svg");
InputStream sceneFileStream = Files.asByteSource(initialFile).openStream();
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
String uri = "http://www.example.org/diagram.svg";
final SVGOMDocument doc = (SVGOMDocument) f.createDocument(
uri, sceneFileStream);
final NodeList nodes =
doc.getDocumentElement().getElementsByTagName("g");
SVGOMGElement signSouth = null;
for (int i=0; (i < nodes.getLength()) && (signSouth == null); i++) {
final Node curNode = nodes.item(i);
final Node id = curNode.getAttributes().getNamedItem("id");
if ("signS".equals(id.getTextContent())) {
signSouth = (SVGOMGElement) curNode;
}
System.out.println("curNode: " + nodes);
}
System.out.println("signSouth: " + signSouth);
final NodeList rectNodes = signSouth.getElementsByTagName("rect");
System.out.println("rectNodes: " + rectNodes);
SVGOMRectElement rectNode = (SVGOMRectElement) rectNodes.item(0);
System.out.println("rectNode: " + rectNode);
final SVGMatrix m2 =
signSouth.getTransformToElement(rectNode);
System.out.println("m2: " + m2);
} catch (IOException ex) {
Assert.fail(ex.getMessage());
}
}
}
对 m2.getA()
-m2.getF()
的调用导致 NullPointerException
秒。
更新 2(08.11.2015 13:38 MSK):
添加了以下代码来创建 SVGPoint
并对其应用矩阵变换:
final SVGSVGElement docElem = (SVGSVGElement)
doc.getDocumentElement();
final SVGPoint svgPoint = docElem.createSVGPoint();
svgPoint.setX((float) x);
svgPoint.setY((float) y);
final SVGPoint svgPoint1 =
svgPoint.matrixTransform(signSouth.getScreenCTM()); // Line 77
System.out.println("x: " + svgPoint1.getX());
System.out.println("y: " + svgPoint1.getY());
结果:
java.lang.NullPointerException
at org.apache.batik.dom.svg.SVGLocatableSupport.getAffineTransform(Unknown Source)
at org.apache.batik.dom.svg.AbstractSVGMatrix.getA(Unknown Source)
at org.apache.batik.dom.svg.SVGOMPoint.matrixTransform(Unknown Source)
at org.apache.batik.dom.svg.SVGOMPoint.matrixTransform(Unknown Source)
at [...].BatikTest.test(BatikTest.java:77)
更新 3,赏金条款 (10.11.2015 MSK):
获得赏金必须满足的条件:
我会将赏金奖励给在 JUnit 测试 BatikTest 中设法实现方法 magicallyCalculateXCoordinate
和 magicallyCalculateYCoordinate
的男女主角,这样我就可以在我的 Java 编码 InkScape 中显示的形状的坐标(示例请参见以下屏幕截图)。
计算 SVG 文件中形状位置的方法必须有效
- 对于组节点(如图片和 sample file 中的节点)或
- 三角形。
您提供的代码必须适用于示例文件中的所有四种形状,即。 e.使用它我必须能够在 Java 代码中计算出它们的坐标,这与 Inkscape 中显示的坐标相同。
您可以向方法 magicallyCalculateXCoordinate
和 magicallyCalculateYCoordinate
添加参数,您也可以创建自己的方法来计算坐标。
您可以使用任何可合法用于商业目的的库。
与此请求相关的所有文件都可以在 GitHub 上找到。我设法使用 IntelliJ Idea Community Edition 14.1.5 编译测试。
我知道,我来晚了,但我偶然发现了这个问题并且很喜欢这个谜语 ;-)
svg 中的原点是左上角,而 inkscape 中的原点是左下角。
所以我们需要应用描边和变换,然后找到左下角的点四舍五入到三位小数。 我使用 GVTBuilder 获取应用样式的边界框,然后转换 boundingBox,请求转换组的边界框,然后使用 xMin 和 yMax 作为参考点。使用 viewbox 确定高度,我转换了 y 坐标,最后对坐标进行了四舍五入。 (请参阅 github 上的拉取请求)
package test.java.svgspike;
import org.apache.batik.anim.dom.SAXSVGDocumentFactory;
import org.apache.batik.anim.dom.SVGOMDocument;
import org.apache.batik.anim.dom.SVGOMGElement;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.util.XMLResourceDescriptor;
import javax.xml.xpath.XPathExpressionException;
import java.awt.geom.Point2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import com.google.common.io.Files;
import org.junit.Assert;
import org.junit.Test;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Created by pisarenko on 10.11.2015.
*/
public final class BatikTest {
@Test
public void test() throws XPathExpressionException {
try {
final File initialFile =
new File("src/test/resources/scene05_signs.svg");
InputStream sceneFileStream = Files.asByteSource(initialFile).openStream();
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
String uri = "http://www.example.org/diagram.svg";
final SVGOMDocument doc = (SVGOMDocument) f.createDocument(
uri, sceneFileStream);
String viewBox = doc.getDocumentElement().getAttribute("viewBox");
Point2D referencePoint = getReferencePoint(doc, getGroupElement(doc, "signS"));
double signSouthX = magicallyCalculateXCoordinate(referencePoint);
double signSouthY = magicallyCalculateYCoordinate(referencePoint, viewBox);
Assert.assertEquals(109.675, signSouthX, 0.0000001);
Assert.assertEquals(533.581, signSouthY, 0.0000001);
referencePoint = getReferencePoint(doc, getGroupElement(doc, "signN"));
Assert.assertEquals(109.906, magicallyCalculateXCoordinate(referencePoint), 0.0000001);
Assert.assertEquals(578.293, magicallyCalculateYCoordinate(referencePoint, viewBox), 0.0000001);
referencePoint = getReferencePoint(doc, getGroupElement(doc, "signE"));
Assert.assertEquals(129.672, magicallyCalculateXCoordinate(referencePoint), 0.0000001);
Assert.assertEquals(554.077, magicallyCalculateYCoordinate(referencePoint, viewBox), 0.0000001);
referencePoint = getReferencePoint(doc, getGroupElement(doc, "signW"));
Assert.assertEquals(93.398, magicallyCalculateXCoordinate(referencePoint), 0.0000001);
Assert.assertEquals(553.833, magicallyCalculateYCoordinate(referencePoint, viewBox), 0.0000001);
} catch (IOException ex) {
Assert.fail(ex.getMessage());
}
}
private SVGOMGElement getGroupElement(SVGOMDocument doc, String id){
final NodeList nodes = doc.getDocumentElement().getElementsByTagName("g");
SVGOMGElement signGroup = null;
for (int i=0; (i < nodes.getLength()) && (signGroup == null); i++) {
final Node curNode = nodes.item(i);
final Node idNode = curNode.getAttributes().getNamedItem("id");
if (id.equals(idNode.getTextContent())) signGroup = (SVGOMGElement) curNode;
}
return signGroup;
}
/**
* @param doc
* @param signGroup
* @return the reference point, inkscape uses for group (bottom left corner of group)
*/
private Point2D getReferencePoint(SVGOMDocument doc, SVGOMGElement signGroup){
Point2D referencePoint = new Point2D.Double(0, 0);
try {
BridgeContext ctx = new BridgeContext(new UserAgentAdapter());
new GVTBuilder().build(ctx, doc);
GraphicsNode gvtElement = new GVTBuilder().build(ctx, signGroup);
Rectangle2D rc = gvtElement.getSensitiveBounds();
rc = ((Path2D) gvtElement.getTransform().createTransformedShape(rc)).getBounds2D();
//find xMin and yMax in poi
referencePoint = new Point2D.Double(rc.getMinX(), rc.getMaxY());
} catch (Exception e) {
e.printStackTrace();
}
return referencePoint;
}
/**
* inkscape states y coordinate with origin in left bottom corner, while svg uses top left corner as origin
* @param referencePoint bottom left corner of group
* @param viewBox in "originX originY width height" notation
* @return corrected y coordinate, rounded to three decimal figures (half up)
*/
private double magicallyCalculateYCoordinate(Point2D referencePoint, String viewBox) {
String[] viewBoxValues = viewBox.split(" ");
BigDecimal roundedY = new BigDecimal(Double.parseDouble(viewBoxValues[3])-referencePoint.getY());
roundedY = roundedY.setScale(3, BigDecimal.ROUND_HALF_UP);
return roundedY.doubleValue();
}
/**
* @param referencePoint bottom left corner of group
* @return x coordinate, rounded to three decimal figures (half up)
*/
private double magicallyCalculateXCoordinate(Point2D referencePoint) {
BigDecimal roundedX = new BigDecimal(referencePoint.getX()).setScale(3, BigDecimal.ROUND_HALF_UP);
return roundedX.doubleValue();
}
}
它应该适用于所有组和所有转换。