| /* |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * ==================================================================== |
| */ |
| |
| package org.apache.poi.xslf.usermodel; |
| |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Path2D; |
| import java.awt.geom.PathIterator; |
| import java.awt.geom.Rectangle2D; |
| |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamReader; |
| |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.poi.ooxml.POIXMLTypeLoader; |
| import org.apache.poi.ooxml.util.POIXMLUnits; |
| import org.apache.poi.sl.draw.geom.CustomGeometry; |
| import org.apache.poi.sl.draw.geom.PresetGeometries; |
| import org.apache.poi.sl.usermodel.FreeformShape; |
| import org.apache.poi.util.Beta; |
| import org.apache.poi.util.Units; |
| import org.apache.xmlbeans.XmlCursor; |
| import org.apache.xmlbeans.XmlObject; |
| import org.apache.xmlbeans.XmlOptions; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTAdjPoint2D; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTCustomGeometry2D; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTGeomRect; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2D; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DClose; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DCubicBezierTo; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DLineTo; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DMoveTo; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DQuadBezierTo; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; |
| import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTShape; |
| import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual; |
| |
| /** |
| * Represents a custom geometric shape. |
| * This shape will consist of a series of lines and curves described within a creation path. |
| */ |
| @Beta |
| public class XSLFFreeformShape extends XSLFAutoShape |
| implements FreeformShape<XSLFShape,XSLFTextParagraph> { |
| |
| private static final Logger LOG = LogManager.getLogger(XSLFFreeformShape.class); |
| |
| /*package*/ XSLFFreeformShape(CTShape shape, XSLFSheet sheet) { |
| super(shape, sheet); |
| } |
| |
| @Override |
| public int setPath(final Path2D path) { |
| final CTPath2D ctPath = CTPath2D.Factory.newInstance(); |
| |
| final Rectangle2D bounds = path.getBounds2D(); |
| final int x0 = Units.toEMU(bounds.getX()); |
| final int y0 = Units.toEMU(bounds.getY()); |
| final PathIterator it = path.getPathIterator(new AffineTransform()); |
| int numPoints = 0; |
| ctPath.setH(Units.toEMU(bounds.getHeight())); |
| ctPath.setW(Units.toEMU(bounds.getWidth())); |
| |
| final double[] vals = new double[6]; |
| while (!it.isDone()) { |
| final int type = it.currentSegment(vals); |
| final CTAdjPoint2D[] points; |
| switch (type) { |
| case PathIterator.SEG_MOVETO: |
| points = addMoveTo(ctPath); |
| break; |
| case PathIterator.SEG_LINETO: |
| points = addLineTo(ctPath); |
| break; |
| case PathIterator.SEG_QUADTO: |
| points = addQuadBezierTo(ctPath); |
| break; |
| case PathIterator.SEG_CUBICTO: |
| points = addCubicBezierTo(ctPath); |
| break; |
| case PathIterator.SEG_CLOSE: |
| points = addClosePath(ctPath); |
| break; |
| default: { |
| throw new IllegalStateException("Unrecognized path segment type: " + type); |
| } |
| } |
| |
| int i=0; |
| for (final CTAdjPoint2D point : points) { |
| point.setX(Units.toEMU(vals[i++])-x0); |
| point.setY(Units.toEMU(vals[i++])-y0); |
| } |
| |
| numPoints += Math.max(points.length, 1); |
| it.next(); |
| } |
| |
| XmlObject xo = getShapeProperties(); |
| if (!(xo instanceof CTShapeProperties)) { |
| return -1; |
| } |
| |
| ((CTShapeProperties)xo).getCustGeom().getPathLst().setPathArray(new CTPath2D[]{ctPath}); |
| setAnchor(bounds); |
| return numPoints; |
| } |
| |
| /** |
| * @return definition of the shape geometry |
| */ |
| @Override |
| public CustomGeometry getGeometry() { |
| final XmlObject xo = getShapeProperties(); |
| if (!(xo instanceof CTShapeProperties)) { |
| return null; |
| } |
| |
| XmlOptions xop = new XmlOptions(POIXMLTypeLoader.DEFAULT_XML_OPTIONS); |
| xop.setSaveOuter(); |
| |
| XMLStreamReader staxReader = ((CTShapeProperties)xo).getCustGeom().newXMLStreamReader(xop); |
| CustomGeometry custGeo = PresetGeometries.convertCustomGeometry(staxReader); |
| try { |
| staxReader.close(); |
| } catch (XMLStreamException e) { |
| LOG.atWarn().log("An error occurred while closing a Custom Geometry XML Stream Reader: {}", e.getMessage()); |
| } |
| |
| return custGeo; |
| } |
| |
| @Override |
| public Path2D.Double getPath() { |
| final Path2D.Double path = new Path2D.Double(); |
| |
| final XmlObject xo = getShapeProperties(); |
| if (!(xo instanceof CTShapeProperties)) { |
| return null; |
| } |
| |
| final CTCustomGeometry2D geom = ((CTShapeProperties)xo).getCustGeom(); |
| for(CTPath2D spPath : geom.getPathLst().getPathArray()){ |
| XmlCursor cursor = spPath.newCursor(); |
| try { |
| if (cursor.toFirstChild()) { |
| do { |
| final XmlObject ch = cursor.getObject(); |
| if (ch instanceof CTPath2DMoveTo) { |
| addMoveTo(path, (CTPath2DMoveTo)ch); |
| } else if (ch instanceof CTPath2DLineTo) { |
| addLineTo(path, (CTPath2DLineTo)ch); |
| } else if (ch instanceof CTPath2DQuadBezierTo) { |
| addQuadBezierTo(path, (CTPath2DQuadBezierTo)ch); |
| } else if (ch instanceof CTPath2DCubicBezierTo) { |
| addCubicBezierTo(path, (CTPath2DCubicBezierTo)ch); |
| } else if (ch instanceof CTPath2DClose) { |
| addClosePath(path); |
| } else { |
| LOG.atWarn().log("can't handle path of type {}", xo.getClass()); |
| } |
| } while (cursor.toNextSibling()); |
| } |
| } finally { |
| cursor.dispose(); |
| } |
| } |
| |
| // the created path starts at (x=0, y=0). |
| // this used to scale each path element to the path bounding box, |
| // but now the dimensions/relations are kept as-is |
| final AffineTransform at = new AffineTransform(); |
| |
| final CTTransform2D xfrm = getXfrm(false); |
| final Rectangle2D xfrm2d = new Rectangle2D.Double |
| (POIXMLUnits.parseLength(xfrm.getOff().xgetX()), POIXMLUnits.parseLength(xfrm.getOff().xgetY()), xfrm.getExt().getCx(), xfrm.getExt().getCy()); |
| |
| final Rectangle2D bounds = getAnchor(); |
| at.translate(bounds.getX()+bounds.getCenterX(), bounds.getY()+bounds.getCenterY()); |
| at.scale(1./Units.EMU_PER_POINT, 1./Units.EMU_PER_POINT); |
| at.translate(-xfrm2d.getCenterX(), -xfrm2d.getCenterY()); |
| return new Path2D.Double(at.createTransformedShape(path)); |
| } |
| |
| private static CTAdjPoint2D[] addMoveTo(final CTPath2D path) { |
| return new CTAdjPoint2D[]{path.addNewMoveTo().addNewPt()}; |
| } |
| |
| private static void addMoveTo(final Path2D path, final CTPath2DMoveTo xo) { |
| final CTAdjPoint2D pt = xo.getPt(); |
| path.moveTo((Long)pt.getX(), (Long)pt.getY()); |
| } |
| |
| private static CTAdjPoint2D[] addLineTo(final CTPath2D path) { |
| return new CTAdjPoint2D[]{path.addNewLnTo().addNewPt()}; |
| } |
| |
| private static void addLineTo(final Path2D path, final CTPath2DLineTo xo) { |
| final CTAdjPoint2D pt = xo.getPt(); |
| path.lineTo((Long)pt.getX(), (Long)pt.getY()); |
| } |
| |
| private static CTAdjPoint2D[] addQuadBezierTo(final CTPath2D path) { |
| final CTPath2DQuadBezierTo bez = path.addNewQuadBezTo(); |
| return new CTAdjPoint2D[]{ bez.addNewPt(), bez.addNewPt() }; |
| } |
| |
| private static void addQuadBezierTo(final Path2D path, final CTPath2DQuadBezierTo xo) { |
| final CTAdjPoint2D pt1 = xo.getPtArray(0); |
| final CTAdjPoint2D pt2 = xo.getPtArray(1); |
| path.quadTo((Long)pt1.getX(), (Long)pt1.getY(), |
| (Long)pt2.getX(), (Long)pt2.getY()); |
| } |
| |
| private static CTAdjPoint2D[] addCubicBezierTo(final CTPath2D path) { |
| final CTPath2DCubicBezierTo bez = path.addNewCubicBezTo(); |
| return new CTAdjPoint2D[]{ bez.addNewPt(), bez.addNewPt(), bez.addNewPt() }; |
| } |
| |
| private static void addCubicBezierTo(final Path2D path, final CTPath2DCubicBezierTo xo) { |
| final CTAdjPoint2D pt1 = xo.getPtArray(0); |
| final CTAdjPoint2D pt2 = xo.getPtArray(1); |
| final CTAdjPoint2D pt3 = xo.getPtArray(2); |
| path.curveTo((Long)pt1.getX(), (Long)pt1.getY(), |
| (Long)pt2.getX(), (Long)pt2.getY(), |
| (Long)pt3.getX(), (Long)pt3.getY()); |
| } |
| |
| private static CTAdjPoint2D[] addClosePath(final CTPath2D path) { |
| path.addNewClose(); |
| return new CTAdjPoint2D[0]; |
| } |
| |
| private static void addClosePath(final Path2D path) { |
| path.closePath(); |
| } |
| |
| /** |
| * @param shapeId 1-based shapeId |
| */ |
| static CTShape prototype(int shapeId) { |
| CTShape ct = CTShape.Factory.newInstance(); |
| CTShapeNonVisual nvSpPr = ct.addNewNvSpPr(); |
| CTNonVisualDrawingProps cnv = nvSpPr.addNewCNvPr(); |
| cnv.setName("Freeform " + shapeId); |
| cnv.setId(shapeId); |
| nvSpPr.addNewCNvSpPr(); |
| nvSpPr.addNewNvPr(); |
| CTShapeProperties spPr = ct.addNewSpPr(); |
| CTCustomGeometry2D geom = spPr.addNewCustGeom(); |
| geom.addNewAvLst(); |
| geom.addNewGdLst(); |
| geom.addNewAhLst(); |
| geom.addNewCxnLst(); |
| CTGeomRect rect = geom.addNewRect(); |
| rect.setR("r"); |
| rect.setB("b"); |
| rect.setT("t"); |
| rect.setL("l"); |
| geom.addNewPathLst(); |
| return ct; |
| } |
| } |