You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

XSLFFreeformShape.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*
  2. * ====================================================================
  3. * Licensed to the Apache Software Foundation (ASF) under one or more
  4. * contributor license agreements. See the NOTICE file distributed with
  5. * this work for additional information regarding copyright ownership.
  6. * The ASF licenses this file to You under the Apache License, Version 2.0
  7. * (the "License"); you may not use this file except in compliance with
  8. * the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. * ====================================================================
  18. */
  19. package org.apache.poi.xslf.usermodel;
  20. import java.awt.geom.AffineTransform;
  21. import java.awt.geom.Path2D;
  22. import java.awt.geom.PathIterator;
  23. import java.awt.geom.Rectangle2D;
  24. import javax.xml.stream.XMLStreamException;
  25. import javax.xml.stream.XMLStreamReader;
  26. import org.apache.poi.ooxml.POIXMLTypeLoader;
  27. import org.apache.poi.sl.draw.geom.CustomGeometry;
  28. import org.apache.poi.sl.draw.geom.PresetGeometries;
  29. import org.apache.poi.sl.usermodel.FreeformShape;
  30. import org.apache.poi.util.Beta;
  31. import org.apache.poi.util.POILogFactory;
  32. import org.apache.poi.util.POILogger;
  33. import org.apache.poi.util.Units;
  34. import org.apache.xmlbeans.XmlCursor;
  35. import org.apache.xmlbeans.XmlObject;
  36. import org.apache.xmlbeans.XmlOptions;
  37. import org.openxmlformats.schemas.drawingml.x2006.main.CTAdjPoint2D;
  38. import org.openxmlformats.schemas.drawingml.x2006.main.CTCustomGeometry2D;
  39. import org.openxmlformats.schemas.drawingml.x2006.main.CTGeomRect;
  40. import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
  41. import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2D;
  42. import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DClose;
  43. import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DCubicBezierTo;
  44. import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DLineTo;
  45. import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DMoveTo;
  46. import org.openxmlformats.schemas.drawingml.x2006.main.CTPath2DQuadBezierTo;
  47. import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties;
  48. import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D;
  49. import org.openxmlformats.schemas.presentationml.x2006.main.CTShape;
  50. import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual;
  51. /**
  52. * Represents a custom geometric shape.
  53. * This shape will consist of a series of lines and curves described within a creation path.
  54. */
  55. @Beta
  56. public class XSLFFreeformShape extends XSLFAutoShape
  57. implements FreeformShape<XSLFShape,XSLFTextParagraph> {
  58. private static final POILogger LOG = POILogFactory.getLogger(XSLFFreeformShape.class);
  59. /*package*/ XSLFFreeformShape(CTShape shape, XSLFSheet sheet) {
  60. super(shape, sheet);
  61. }
  62. @Override
  63. public int setPath(final Path2D path) {
  64. final CTPath2D ctPath = CTPath2D.Factory.newInstance();
  65. final Rectangle2D bounds = path.getBounds2D();
  66. final int x0 = Units.toEMU(bounds.getX());
  67. final int y0 = Units.toEMU(bounds.getY());
  68. final PathIterator it = path.getPathIterator(new AffineTransform());
  69. int numPoints = 0;
  70. ctPath.setH(Units.toEMU(bounds.getHeight()));
  71. ctPath.setW(Units.toEMU(bounds.getWidth()));
  72. final double[] vals = new double[6];
  73. while (!it.isDone()) {
  74. final int type = it.currentSegment(vals);
  75. final CTAdjPoint2D[] points;
  76. switch (type) {
  77. case PathIterator.SEG_MOVETO:
  78. points = addMoveTo(ctPath);
  79. break;
  80. case PathIterator.SEG_LINETO:
  81. points = addLineTo(ctPath);
  82. break;
  83. case PathIterator.SEG_QUADTO:
  84. points = addQuadBezierTo(ctPath);
  85. break;
  86. case PathIterator.SEG_CUBICTO:
  87. points = addCubicBezierTo(ctPath);
  88. break;
  89. case PathIterator.SEG_CLOSE:
  90. points = addClosePath(ctPath);
  91. break;
  92. default: {
  93. throw new IllegalStateException("Unrecognized path segment type: " + type);
  94. }
  95. }
  96. int i=0;
  97. for (final CTAdjPoint2D point : points) {
  98. point.setX(Units.toEMU(vals[i++])-x0);
  99. point.setY(Units.toEMU(vals[i++])-y0);
  100. }
  101. numPoints += Math.max(points.length, 1);
  102. it.next();
  103. }
  104. XmlObject xo = getShapeProperties();
  105. if (!(xo instanceof CTShapeProperties)) {
  106. return -1;
  107. }
  108. ((CTShapeProperties)xo).getCustGeom().getPathLst().setPathArray(new CTPath2D[]{ctPath});
  109. setAnchor(bounds);
  110. return numPoints;
  111. }
  112. /**
  113. * @return definition of the shape geometry
  114. */
  115. @Override
  116. public CustomGeometry getGeometry() {
  117. final XmlObject xo = getShapeProperties();
  118. if (!(xo instanceof CTShapeProperties)) {
  119. return null;
  120. }
  121. XmlOptions xop = new XmlOptions(POIXMLTypeLoader.DEFAULT_XML_OPTIONS);
  122. xop.setSaveOuter();
  123. XMLStreamReader staxReader = ((CTShapeProperties)xo).getCustGeom().newXMLStreamReader(xop);
  124. CustomGeometry custGeo = PresetGeometries.convertCustomGeometry(staxReader);
  125. try {
  126. staxReader.close();
  127. } catch (XMLStreamException e) {
  128. LOG.log(POILogger.WARN,
  129. "An error occurred while closing a Custom Geometry XML Stream Reader: " + e.getMessage());
  130. }
  131. return custGeo;
  132. }
  133. @Override
  134. public Path2D.Double getPath() {
  135. final Path2D.Double path = new Path2D.Double();
  136. final XmlObject xo = getShapeProperties();
  137. if (!(xo instanceof CTShapeProperties)) {
  138. return null;
  139. }
  140. final CTCustomGeometry2D geom = ((CTShapeProperties)xo).getCustGeom();
  141. //noinspection deprecation
  142. for(CTPath2D spPath : geom.getPathLst().getPathArray()){
  143. XmlCursor cursor = spPath.newCursor();
  144. try {
  145. if (cursor.toFirstChild()) {
  146. do {
  147. final XmlObject ch = cursor.getObject();
  148. if (ch instanceof CTPath2DMoveTo) {
  149. addMoveTo(path, (CTPath2DMoveTo)ch);
  150. } else if (ch instanceof CTPath2DLineTo) {
  151. addLineTo(path, (CTPath2DLineTo)ch);
  152. } else if (ch instanceof CTPath2DQuadBezierTo) {
  153. addQuadBezierTo(path, (CTPath2DQuadBezierTo)ch);
  154. } else if (ch instanceof CTPath2DCubicBezierTo) {
  155. addCubicBezierTo(path, (CTPath2DCubicBezierTo)ch);
  156. } else if (ch instanceof CTPath2DClose) {
  157. addClosePath(path);
  158. } else {
  159. LOG.log(POILogger.WARN, "can't handle path of type "+xo.getClass());
  160. }
  161. } while (cursor.toNextSibling());
  162. }
  163. } finally {
  164. cursor.dispose();
  165. }
  166. }
  167. // the created path starts at (x=0, y=0).
  168. // this used to scale each path element to the path bounding box,
  169. // but now the dimensions/relations are kept as-is
  170. final AffineTransform at = new AffineTransform();
  171. final CTTransform2D xfrm = getXfrm(false);
  172. final Rectangle2D xfrm2d = new Rectangle2D.Double
  173. (xfrm.getOff().getX(), xfrm.getOff().getY(), xfrm.getExt().getCx(), xfrm.getExt().getCy());
  174. final Rectangle2D bounds = getAnchor();
  175. at.translate(bounds.getX()+bounds.getCenterX(), bounds.getY()+bounds.getCenterY());
  176. at.scale(1./Units.EMU_PER_POINT, 1./Units.EMU_PER_POINT);
  177. at.translate(-xfrm2d.getCenterX(), -xfrm2d.getCenterY());
  178. return new Path2D.Double(at.createTransformedShape(path));
  179. }
  180. private static CTAdjPoint2D[] addMoveTo(final CTPath2D path) {
  181. return new CTAdjPoint2D[]{path.addNewMoveTo().addNewPt()};
  182. }
  183. private static void addMoveTo(final Path2D path, final CTPath2DMoveTo xo) {
  184. final CTAdjPoint2D pt = xo.getPt();
  185. path.moveTo((Long)pt.getX(), (Long)pt.getY());
  186. }
  187. private static CTAdjPoint2D[] addLineTo(final CTPath2D path) {
  188. return new CTAdjPoint2D[]{path.addNewLnTo().addNewPt()};
  189. }
  190. private static void addLineTo(final Path2D path, final CTPath2DLineTo xo) {
  191. final CTAdjPoint2D pt = xo.getPt();
  192. path.lineTo((Long)pt.getX(), (Long)pt.getY());
  193. }
  194. private static CTAdjPoint2D[] addQuadBezierTo(final CTPath2D path) {
  195. final CTPath2DQuadBezierTo bez = path.addNewQuadBezTo();
  196. return new CTAdjPoint2D[]{ bez.addNewPt(), bez.addNewPt() };
  197. }
  198. private static void addQuadBezierTo(final Path2D path, final CTPath2DQuadBezierTo xo) {
  199. final CTAdjPoint2D pt1 = xo.getPtArray(0);
  200. final CTAdjPoint2D pt2 = xo.getPtArray(1);
  201. path.quadTo((Long)pt1.getX(), (Long)pt1.getY(),
  202. (Long)pt2.getX(), (Long)pt2.getY());
  203. }
  204. private static CTAdjPoint2D[] addCubicBezierTo(final CTPath2D path) {
  205. final CTPath2DCubicBezierTo bez = path.addNewCubicBezTo();
  206. return new CTAdjPoint2D[]{ bez.addNewPt(), bez.addNewPt(), bez.addNewPt() };
  207. }
  208. private static void addCubicBezierTo(final Path2D path, final CTPath2DCubicBezierTo xo) {
  209. final CTAdjPoint2D pt1 = xo.getPtArray(0);
  210. final CTAdjPoint2D pt2 = xo.getPtArray(1);
  211. final CTAdjPoint2D pt3 = xo.getPtArray(2);
  212. path.curveTo((Long)pt1.getX(), (Long)pt1.getY(),
  213. (Long)pt2.getX(), (Long)pt2.getY(),
  214. (Long)pt3.getX(), (Long)pt3.getY());
  215. }
  216. private static CTAdjPoint2D[] addClosePath(final CTPath2D path) {
  217. path.addNewClose();
  218. return new CTAdjPoint2D[0];
  219. }
  220. private static void addClosePath(final Path2D path) {
  221. path.closePath();
  222. }
  223. /**
  224. * @param shapeId 1-based shapeId
  225. */
  226. static CTShape prototype(int shapeId) {
  227. CTShape ct = CTShape.Factory.newInstance();
  228. CTShapeNonVisual nvSpPr = ct.addNewNvSpPr();
  229. CTNonVisualDrawingProps cnv = nvSpPr.addNewCNvPr();
  230. cnv.setName("Freeform " + shapeId);
  231. cnv.setId(shapeId);
  232. nvSpPr.addNewCNvSpPr();
  233. nvSpPr.addNewNvPr();
  234. CTShapeProperties spPr = ct.addNewSpPr();
  235. CTCustomGeometry2D geom = spPr.addNewCustGeom();
  236. geom.addNewAvLst();
  237. geom.addNewGdLst();
  238. geom.addNewAhLst();
  239. geom.addNewCxnLst();
  240. CTGeomRect rect = geom.addNewRect();
  241. rect.setR("r");
  242. rect.setB("b");
  243. rect.setT("t");
  244. rect.setL("l");
  245. geom.addNewPathLst();
  246. return ct;
  247. }
  248. }