java svg batik

java - ¿Cómo hacer que el grupo se transforme en un archivo SVG mediante programación?



batik (1)

Lo sé, llego tarde a la fiesta, pero me topé con esta pregunta y disfruté el enigma ;-)

El origen en un svg es la esquina superior izquierda, mientras que inkscape usa la parte inferior izquierda como origen.

Así que debemos aplicar el trazo y la transformación, luego encontrar el punto inferior izquierdo redondeado o tres cifras decimales. Utilicé GVTBuilder para obtener el cuadro delimitador con estilo aplicado, luego transformé el cuadro de límite delimitado, solicité el cuadro delimitador del grupo transformado, luego usé xMin y yMax como el punto de referencia. Usando el cuadro de vista para determinar la altura, transformé la coordenada y, y finalmente redondeé las coordenadas. (ver solicitud de extracción en 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(); } }

Debería funcionar para todos los grupos y todas las transformaciones.

Tengo un archivo SVG con la siguiente imagen:

Cada una de las flechas está representada por un código como este:

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

Quiero calcular la posición absoluta del rectángulo (nodo rect ). Para hacer esto, necesito evaluar la expresión dentro de la etiqueta de transform de la etiqueta g ( matrix(-1,0,0,-1,149.82549,457.2455) en el ejemplo anterior).

¿Cómo puedo hacerlo?

Supongo que el primer paso es leer el archivo como SVGDocument usando Apache Batik :

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) { // ... }

Por lo que yo sé, doc puede ser convertido a SVGDocument .

¿Cómo puedo pasar del documento SVG a las ubicaciones absolutas de los rectángulos o grupos?

Nota: Necesito algún código existente , que hace las transformaciones anteriores (no me pida que implemente estas transformaciones, ya deben estar implementadas y quiero reutilizar ese código).

Actualización 1 (08.11.2015 12:56 MSK):

Primer intento de implementar la recomendación de 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()); } } }

Las llamadas a m2.getA() - m2.getF() dan como resultado NullPointerException s.

Actualización 2 (08.11.2015 13:38 MSK):

Se agregó el siguiente código para crear SVGPoint y aplicarle la transformación de matriz:

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());

Resultado:

java.lang.NullPointerException at org.apache.batik.dom.svg.SVGLocatableSupport$3.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)

Actualización 3, términos de la recompensa (10.11.2015 MSK):

Condiciones que deben cumplirse para obtener la recompensa:

Otorgaré la recompensa al héroe o heroína, que logre implementar los métodos magicallyCalculateXCoordinate BatikTest y magicallyCalculateYCoordinate BatikTest en la Prueba de BatikTest JUnit BatikTest que pueda obtener en mi código Java las coordenadas de las formas, que se muestran en InkScape (vea la siguiente captura de pantalla para un ejemplo).

El método presentado para calcular la posición de las formas en los archivos SVG debe funcionar bien

  1. para nodos de grupo (como el de la imagen y el archivo de muestra ) o
  2. para triangulos.

El código que proporcione debe funcionar para las cuatro formas en el archivo de muestra, es decir, usarlo debo poder calcular en el código Java las coordenadas de las mismas, que son iguales a las que se muestran en Inkscape.

Puede agregar parámetros a los métodos magicallyCalculateXCoordinate y magicallyCalculateYCoordinate , y también puede crear sus propios métodos, que calculan las coordenadas.

Puede utilizar cualquier biblioteca, que puede ser utilizada legalmente con fines comerciales.

Todos los archivos relacionados con esta solicitud están disponibles en GitHub . Logré compilar la prueba utilizando IntelliJ Idea Community Edition 14.1.5.