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.

XSLFDiagram.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.xslf.usermodel;
  16. import com.microsoft.schemas.office.drawing.x2008.diagram.CTGroupShape;
  17. import com.microsoft.schemas.office.drawing.x2008.diagram.CTShape;
  18. import org.apache.poi.ooxml.POIXMLDocumentPart;
  19. import org.apache.poi.util.Beta;
  20. import org.apache.xmlbeans.XmlObject;
  21. import org.openxmlformats.schemas.drawingml.x2006.diagram.CTRelIds;
  22. import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData;
  23. import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties;
  24. import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph;
  25. import org.openxmlformats.schemas.presentationml.x2006.main.CTApplicationNonVisualDrawingProps;
  26. import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame;
  27. import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShapeNonVisual;
  28. import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual;
  29. import javax.xml.namespace.QName;
  30. import java.awt.geom.Rectangle2D;
  31. import java.util.ArrayList;
  32. import java.util.List;
  33. /**
  34. * Representation of a DrawingML Diagram
  35. * <p>
  36. * This class converts the diagram to an {@link XSLFGroupShape} accessible via {@link #getGroupShape()}. The underlying
  37. * {@link XSLFDiagramDrawing} used to create the group shape is accessible via {@link #getDiagramDrawing()}.
  38. * <p>
  39. * In pptx files, these diagrams are generated by creating SmartArt. When a pptx has SmartArt, a directory with the
  40. * following structure is created:
  41. *
  42. * <pre>
  43. * ppt/
  44. * diagrams/
  45. * data#.xml
  46. * drawing#.xml^
  47. * colors#.xml
  48. * quickStyle#.xml
  49. * layout#.xml
  50. * rels/
  51. * data#.xml.rels
  52. * drawing#.xml.rels
  53. * </pre>
  54. * <p>
  55. * ^The `drawing#.xml` file is not in the OpenXML spec. It was added as an extension by Microsoft, namespace:
  56. * http://schemas.microsoft.com/office/drawing/2008/diagram
  57. * <p>
  58. * The drawing#.xml file contains the rendered output of the diagram. This class reads the underlying drawing#.xml and
  59. * converts it to a {@link XSLFGroupShape}.
  60. * <p>
  61. * The data, drawing, colors, and quickStyle files are in the OpenXML spec. These contain the instructions that define
  62. * how to render the diagram. Rendering diagrams from these files is not trivial, they support for loops, if/elses, etc.
  63. * Integrating such a change into POI would be quite sophisticated and challenging.
  64. *
  65. * @since POI 5.2.3
  66. */
  67. @Beta
  68. public class XSLFDiagram extends XSLFGraphicFrame {
  69. public static final String DRAWINGML_DIAGRAM_URI = "http://schemas.openxmlformats.org/drawingml/2006/diagram";
  70. private final XSLFDiagramDrawing _drawing;
  71. private final XSLFDiagramGroupShape _groupShape;
  72. /* package protected */ XSLFDiagram(CTGraphicalObjectFrame shape, XSLFSheet sheet) {
  73. super(shape, sheet);
  74. _drawing = readDiagramDrawing(shape, sheet);
  75. _groupShape = initGroupShape();
  76. }
  77. private static boolean hasTextContent(CTShape msShapeCt) {
  78. if (msShapeCt.getTxBody() == null || msShapeCt.getTxXfrm() == null) {
  79. return false;
  80. }
  81. // A shape has text content when there is at least 1 paragraph with 1 paragraph run list
  82. List<CTTextParagraph> paragraphs = msShapeCt.getTxBody().getPList();
  83. return paragraphs.stream()
  84. .flatMap(p -> p.getRList().stream())
  85. .anyMatch(run -> run.getT() != null && !run.getT().trim().isEmpty());
  86. }
  87. private static XSLFDiagramDrawing readDiagramDrawing(CTGraphicalObjectFrame shape, XSLFSheet sheet) {
  88. CTGraphicalObjectData graphicData = shape.getGraphic().getGraphicData();
  89. XmlObject[] children = graphicData.selectChildren(new QName(DRAWINGML_DIAGRAM_URI, "relIds"));
  90. if (children.length == 0) {
  91. return null;
  92. }
  93. // CTRelIds doesn't contain a relationship to the drawing#.xml
  94. // But it has the same name as the other data#.xml, layout#.xml, etc.
  95. CTRelIds relIds = (CTRelIds) children[0];
  96. POIXMLDocumentPart dataModelPart = sheet.getRelationById(relIds.getDm());
  97. if (dataModelPart == null) {
  98. return null;
  99. }
  100. String dataPartName = dataModelPart.getPackagePart().getPartName().getName();
  101. String drawingPartName = dataPartName.replace("data", "drawing");
  102. for (POIXMLDocumentPart.RelationPart rp : sheet.getRelationParts()) {
  103. if (drawingPartName.equals(rp.getDocumentPart().getPackagePart().getPartName().getName())) {
  104. if (rp.getDocumentPart() instanceof XSLFDiagramDrawing) {
  105. return rp.getDocumentPart();
  106. }
  107. }
  108. }
  109. return null;
  110. }
  111. /**
  112. * Returns the underlying {@link XSLFDiagramDrawing} used to create this diagram.
  113. * <p>
  114. * NOTE: Modifying this drawing will not update the groupShape returned from {@link #getGroupShape()}.
  115. */
  116. public XSLFDiagramDrawing getDiagramDrawing() {
  117. return _drawing;
  118. }
  119. /**
  120. * Returns the diagram represented as a grouped shape.
  121. */
  122. public XSLFDiagramGroupShape getGroupShape() {
  123. return _groupShape;
  124. }
  125. // If the shape has text, two XSLFShapes are created. One shape element and one textbox element.
  126. private List<org.openxmlformats.schemas.presentationml.x2006.main.CTShape> convertShape(CTShape msShapeCt) {
  127. org.openxmlformats.schemas.presentationml.x2006.main.CTShape shapeCt
  128. = org.openxmlformats.schemas.presentationml.x2006.main.CTShape.Factory.newInstance();
  129. // The fields on MS CTShape largely re-use the underlying openxml classes.
  130. // We just copy the fields from the MS CTShape to the openxml CTShape
  131. shapeCt.setStyle(msShapeCt.getStyle());
  132. shapeCt.setSpPr(msShapeCt.getSpPr());
  133. CTShapeNonVisual nonVisualCt = shapeCt.addNewNvSpPr();
  134. nonVisualCt.setCNvPr(msShapeCt.getNvSpPr().getCNvPr());
  135. nonVisualCt.setCNvSpPr(msShapeCt.getNvSpPr().getCNvSpPr());
  136. nonVisualCt.setNvPr(CTApplicationNonVisualDrawingProps.Factory.newInstance());
  137. shapeCt.setNvSpPr(nonVisualCt);
  138. ArrayList<org.openxmlformats.schemas.presentationml.x2006.main.CTShape> shapes = new ArrayList<>();
  139. shapes.add(shapeCt);
  140. if (hasTextContent(msShapeCt)) {
  141. org.openxmlformats.schemas.presentationml.x2006.main.CTShape textShapeCT = convertText(msShapeCt, nonVisualCt);
  142. shapes.add(textShapeCT);
  143. }
  144. return shapes;
  145. }
  146. private org.openxmlformats.schemas.presentationml.x2006.main.CTShape convertText(CTShape msShapeCt, CTShapeNonVisual nonVisualCt) {
  147. org.openxmlformats.schemas.presentationml.x2006.main.CTShape textShapeCT
  148. = org.openxmlformats.schemas.presentationml.x2006.main.CTShape.Factory.newInstance();
  149. // SmartArt shapes define a separate `txXfrm` property for the placement of text inside the shape
  150. // We can't easily (is it even possible?) set a separate xfrm for the text on the openxml CTShape.
  151. // Instead, we create a separate textbox shape with the same xfrm.
  152. org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties textShapeProps = textShapeCT.addNewSpPr();
  153. textShapeCT.setTxBody(msShapeCt.getTxBody());
  154. textShapeCT.setStyle(msShapeCt.getStyle());
  155. // Create a copy of the nonVisualCt when setting it for the text box.
  156. // If we shared the one object, a consumer may be surprised that updating the text shape properties
  157. // also updates the parent shape.
  158. textShapeCT.setNvSpPr((CTShapeNonVisual) nonVisualCt.copy());
  159. textShapeCT.getNvSpPr().getCNvSpPr().setTxBox(true);
  160. textShapeProps.setXfrm(msShapeCt.getTxXfrm());
  161. int shapeRotation = msShapeCt.getSpPr().getXfrm().getRot();
  162. int textRotation = msShapeCt.getTxXfrm().getRot();
  163. if (textRotation != 0) {
  164. // SmartArt diagrams (e.g. hexagon) have rotated shapes and the txXfrm can change the rotation for visual
  165. // reasons. We perform that same calculation here again and calculate a new rotation for the text box.
  166. int resolvedRotation = shapeRotation + textRotation;
  167. textShapeProps.getXfrm().setRot(resolvedRotation);
  168. }
  169. return textShapeCT;
  170. }
  171. private XSLFDiagramGroupShape initGroupShape() {
  172. XSLFDiagramDrawing drawing = getDiagramDrawing();
  173. if (drawing == null || drawing.getDrawingDocument() == null) {
  174. return null;
  175. }
  176. CTGroupShape msGroupShapeCt = drawing.getDrawingDocument().getDrawing().getSpTree();
  177. if (msGroupShapeCt == null || msGroupShapeCt.getSpList().isEmpty()) {
  178. return null;
  179. }
  180. return convertMsGroupToGroupShape(msGroupShapeCt, drawing);
  181. }
  182. private XSLFDiagramGroupShape convertMsGroupToGroupShape(CTGroupShape msGroupShapeCt, XSLFDiagramDrawing drawing) {
  183. org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape groupShapeCt
  184. = org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape.Factory.newInstance();
  185. CTGroupShapeProperties groupShapePropsCt = groupShapeCt.addNewGrpSpPr();
  186. CTGroupShapeNonVisual groupShapeNonVisualCt = groupShapeCt.addNewNvGrpSpPr();
  187. groupShapeNonVisualCt.setCNvPr(msGroupShapeCt.getNvGrpSpPr().getCNvPr());
  188. groupShapeNonVisualCt.setCNvGrpSpPr(msGroupShapeCt.getNvGrpSpPr().getCNvGrpSpPr());
  189. groupShapeNonVisualCt.setNvPr(CTApplicationNonVisualDrawingProps.Factory.newInstance());
  190. for (CTShape msShapeCt : msGroupShapeCt.getSpList()) {
  191. List<org.openxmlformats.schemas.presentationml.x2006.main.CTShape> shapes = convertShape(msShapeCt);
  192. groupShapeCt.getSpList().addAll(shapes);
  193. }
  194. Rectangle2D anchor = super.getAnchor();
  195. Rectangle2D interiorAnchor = new Rectangle2D.Double(0, 0, anchor.getWidth(), anchor.getHeight());
  196. XSLFDiagramGroupShape groupShape = new XSLFDiagramGroupShape(groupShapeCt, getSheet(), drawing);
  197. groupShape.setAnchor(anchor);
  198. groupShape.setInteriorAnchor(interiorAnchor);
  199. groupShape.setRotation(super.getRotation());
  200. return groupShape;
  201. }
  202. /**
  203. * Simple wrapper around XSLFGroupShape to enable accessing underlying diagram relations correctly.
  204. * <p>
  205. * Diagrams store relationships to media in `drawing#.xml.rels`. These relationships are accessible using
  206. * {@link #getRelationById(String)}.
  207. */
  208. static class XSLFDiagramGroupShape extends XSLFGroupShape {
  209. private XSLFDiagramDrawing diagramDrawing;
  210. protected XSLFDiagramGroupShape(org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape shape,
  211. XSLFSheet sheet) {
  212. super(shape, sheet);
  213. }
  214. private XSLFDiagramGroupShape(org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape shape,
  215. XSLFSheet sheet,
  216. XSLFDiagramDrawing diagramDrawing) {
  217. super(shape, sheet);
  218. this.diagramDrawing = diagramDrawing;
  219. }
  220. POIXMLDocumentPart getRelationById(String id) {
  221. return diagramDrawing.getRelationById(id);
  222. }
  223. }
  224. }