mirror of https://github.com/apache/poi.git
[bug-66176] Integrate SmartArt diagrams from powerpoint presentations. Thanks to Yaseen.
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1902934 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
aa7eee178c
commit
cc7fcdfab3
|
@ -0,0 +1,137 @@
|
|||
/* ====================================================================
|
||||
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.examples.xslf;
|
||||
|
||||
import org.apache.poi.ooxml.POIXMLDocumentPart;
|
||||
import org.apache.poi.xslf.usermodel.*;
|
||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip;
|
||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Converts SmartArt to openxml shapes and saves the result to the specified output path.
|
||||
*/
|
||||
public class SmartArtConversionDemo {
|
||||
|
||||
private final XMLSlideShow inputPptx;
|
||||
private final XMLSlideShow outputPptx;
|
||||
|
||||
SmartArtConversionDemo(XMLSlideShow inputPptx, XMLSlideShow outputPptx) {
|
||||
this.inputPptx = inputPptx;
|
||||
this.outputPptx = outputPptx;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
if (args.length != 2) {
|
||||
System.out.println("Expected arguments: <inputPath> <outputPath>");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
File inputFile = new File(args[0]);
|
||||
if (!inputFile.exists()) {
|
||||
System.out.printf("Unable to find input file at path: %s", args[0]);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
try (
|
||||
FileInputStream inputPptxStream = new FileInputStream(inputFile);
|
||||
FileOutputStream outputPptxStream = new FileOutputStream(args[1])
|
||||
) {
|
||||
XMLSlideShow inputPptx = new XMLSlideShow(inputPptxStream);
|
||||
XMLSlideShow outputPptx = new XMLSlideShow();
|
||||
SmartArtConversionDemo demo = new SmartArtConversionDemo(inputPptx, outputPptx);
|
||||
demo.convertSmartArt();
|
||||
outputPptx.write(outputPptxStream);
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyAndUpdateImageRelations(XSLFDiagram diagram, XSLFSlide outputSlide) throws IOException {
|
||||
XSLFGroupShape inputGroupShape = diagram.getGroupShape();
|
||||
for (XSLFShape shape : inputGroupShape.getShapes()) {
|
||||
org.openxmlformats.schemas.presentationml.x2006.main.CTShape ctShape
|
||||
= (org.openxmlformats.schemas.presentationml.x2006.main.CTShape) shape.getXmlObject();
|
||||
|
||||
if (ctShape.getSpPr().getBlipFill() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CTBlipFillProperties blipFillProps = ctShape.getSpPr().getBlipFill();
|
||||
CTBlip blip = blipFillProps.getBlip();
|
||||
// In SmartArt diagrams, the references to images/embeds are stored in drawing#.xml.rels. When read by
|
||||
// POI it copies this relationship to the parent slide to allow POI to correctly resolve the images.
|
||||
POIXMLDocumentPart inputPicturePart = diagram.getSheet().getRelationById(blip.getEmbed());
|
||||
|
||||
if (inputPicturePart == null || inputPicturePart.getPackagePart() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
XSLFPictureData inputPictureData = new XSLFPictureData(inputPicturePart.getPackagePart());
|
||||
|
||||
// Copy the input image to the output slides and update the shape to reference the copied image
|
||||
XMLSlideShow outputPptx = outputSlide.getSlideShow();
|
||||
XSLFPictureData outputPictureData = outputPptx.addPicture(
|
||||
inputPicturePart.getPackagePart().getInputStream(), inputPictureData.getType());
|
||||
POIXMLDocumentPart.RelationPart outputRelation = outputSlide.addRelation(null, XSLFRelation.IMAGES, outputPictureData);
|
||||
ctShape.getSpPr().getBlipFill().getBlip().setEmbed(outputRelation.getRelationship().getId());
|
||||
}
|
||||
}
|
||||
|
||||
private static XSLFTheme extractTheme(XMLSlideShow slideShow) {
|
||||
if (!slideShow.getSlideMasters().isEmpty()) {
|
||||
return slideShow.getSlideMasters().get(0).getTheme();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void convertSmartArt() throws IOException {
|
||||
// Copy page size and theme
|
||||
outputPptx.setPageSize(inputPptx.getPageSize());
|
||||
XSLFTheme theme = extractTheme(inputPptx);
|
||||
if (theme != null) {
|
||||
outputPptx.getSlideMasters().get(0).getTheme().getXmlObject().set(theme.getXmlObject());
|
||||
}
|
||||
|
||||
for (XSLFSlide inputSlide : inputPptx.getSlides()) {
|
||||
XSLFSlide outputSlide = outputPptx.createSlide();
|
||||
|
||||
List<XSLFShape> inputShapes = inputSlide.getShapes();
|
||||
for (XSLFShape shape : inputShapes) {
|
||||
if (shape instanceof XSLFDiagram) {
|
||||
copyDiagramToOutput((XSLFDiagram) shape, outputSlide);
|
||||
} else {
|
||||
XSLFAutoShape autoShape = outputSlide.createAutoShape();
|
||||
// Hacky hack. Reassign xml to copy the content over.
|
||||
autoShape.getXmlObject().set(shape.getXmlObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void copyDiagramToOutput(XSLFDiagram inputDiagram, XSLFSlide outputSlide) throws IOException {
|
||||
// This method modifies the underlying xml of the input shapes. We modify the xml structure first, then
|
||||
// assign that to our output shape.
|
||||
copyAndUpdateImageRelations(inputDiagram, outputSlide);
|
||||
XSLFGroupShape inputGroupShape = inputDiagram.getGroupShape();
|
||||
XSLFGroupShape outputGroupShape = outputSlide.createGroup();
|
||||
outputGroupShape.getXmlObject().set(inputGroupShape.getXmlObject());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
/* ====================================================================
|
||||
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 com.microsoft.schemas.office.drawing.x2008.diagram.CTGroupShape;
|
||||
import com.microsoft.schemas.office.drawing.x2008.diagram.CTShape;
|
||||
import org.apache.poi.ooxml.POIXMLDocumentPart;
|
||||
import org.apache.poi.util.Beta;
|
||||
import org.apache.xmlbeans.XmlObject;
|
||||
import org.openxmlformats.schemas.drawingml.x2006.diagram.CTRelIds;
|
||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData;
|
||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties;
|
||||
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph;
|
||||
import org.openxmlformats.schemas.presentationml.x2006.main.CTApplicationNonVisualDrawingProps;
|
||||
import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame;
|
||||
import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShapeNonVisual;
|
||||
import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Representation of a DrawingML Diagram
|
||||
* <p>
|
||||
* This class converts the diagram to an {@link XSLFGroupShape} accessible via {@link #getGroupShape()}. The underlying
|
||||
* {@link XSLFDiagramDrawing} used to create the group shape is accessible via {@link #getDiagramDrawing()}.
|
||||
* <p>
|
||||
* In pptx files, these diagrams are generated by creating SmartArt. When a pptx has SmartArt, a directory with the
|
||||
* following structure is created:
|
||||
*
|
||||
* <pre>
|
||||
* ppt/
|
||||
* diagrams/
|
||||
* data#.xml
|
||||
* drawing#.xml^
|
||||
* colors#.xml
|
||||
* quickStyle#.xml
|
||||
* layout#.xml
|
||||
* rels/
|
||||
* data#.xml.rels
|
||||
* drawing#.xml.rels
|
||||
* </pre>
|
||||
* <p>
|
||||
* ^The `drawing#.xml` file is not in the OpenXML spec. It was added as an extension by Microsoft, namespace:
|
||||
* http://schemas.microsoft.com/office/drawing/2008/diagram
|
||||
* <p>
|
||||
* The drawing#.xml file contains the rendered output of the diagram. This class reads the underlying drawing#.xml and
|
||||
* converts it to a {@link XSLFGroupShape}.
|
||||
* <p>
|
||||
* The data, drawing, colors, and quickStyle files are in the OpenXML spec. These contain the instructions that define
|
||||
* how to render the diagram. Rendering diagrams from these files is not trivial, they support for loops, if/elses, etc.
|
||||
* Integrating such a change into POI would be quite sophisticated and challenging.
|
||||
*
|
||||
* @since POI 5.2.3
|
||||
*/
|
||||
@Beta
|
||||
public class XSLFDiagram extends XSLFGraphicFrame {
|
||||
|
||||
public static final String DRAWINGML_DIAGRAM_URI = "http://schemas.openxmlformats.org/drawingml/2006/diagram";
|
||||
private final XSLFDiagramDrawing _drawing;
|
||||
private final XSLFGroupShape _groupShape;
|
||||
|
||||
/* package protected */ XSLFDiagram(CTGraphicalObjectFrame shape, XSLFSheet sheet) {
|
||||
super(shape, sheet);
|
||||
_drawing = readDiagramDrawing(shape, sheet);
|
||||
_groupShape = initGroupShape(sheet);
|
||||
}
|
||||
|
||||
private static boolean hasTextContent(CTShape msShapeCt) {
|
||||
if (msShapeCt.getTxBody() == null || msShapeCt.getTxXfrm() == null) {
|
||||
return false;
|
||||
}
|
||||
// A shape has text content when there is at least 1 paragraph with 1 paragraph run list
|
||||
List<CTTextParagraph> paragraphs = msShapeCt.getTxBody().getPList();
|
||||
return paragraphs.stream()
|
||||
.flatMap(p -> p.getRList().stream())
|
||||
.anyMatch(run -> run.getT() != null && !run.getT().trim().isEmpty());
|
||||
}
|
||||
|
||||
private static boolean hasBlipEmbed(CTShape msShapeCt) {
|
||||
return msShapeCt != null
|
||||
&& msShapeCt.getSpPr() != null
|
||||
&& msShapeCt.getSpPr().getBlipFill() != null
|
||||
&& msShapeCt.getSpPr().getBlipFill().getBlip() != null
|
||||
&& msShapeCt.getSpPr().getBlipFill().getBlip().getEmbed() != null;
|
||||
}
|
||||
|
||||
private static XSLFDiagramDrawing readDiagramDrawing(CTGraphicalObjectFrame shape, XSLFSheet sheet) {
|
||||
CTGraphicalObjectData graphicData = shape.getGraphic().getGraphicData();
|
||||
XmlObject[] children = graphicData.selectChildren(new QName(DRAWINGML_DIAGRAM_URI, "relIds"));
|
||||
|
||||
if (children.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// CTRelIds doesn't contain a relationship to the drawing#.xml
|
||||
// But it has the same name as the other data#.xml, layout#.xml, etc.
|
||||
CTRelIds relIds = (CTRelIds) children[0];
|
||||
POIXMLDocumentPart dataModelPart = sheet.getRelationById(relIds.getDm());
|
||||
if (dataModelPart == null) {
|
||||
return null;
|
||||
}
|
||||
String dataPartName = dataModelPart.getPackagePart().getPartName().getName();
|
||||
String drawingPartName = dataPartName.replace("data", "drawing");
|
||||
for (POIXMLDocumentPart.RelationPart rp : sheet.getRelationParts()) {
|
||||
if (drawingPartName.equals(rp.getDocumentPart().getPackagePart().getPartName().getName())) {
|
||||
if (rp.getDocumentPart() instanceof XSLFDiagramDrawing) {
|
||||
return rp.getDocumentPart();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the shape has text, two XSLFShapes are created. One shape element and one textbox element.
|
||||
public List<org.openxmlformats.schemas.presentationml.x2006.main.CTShape> convertShape(CTShape msShapeCt, XSLFSheet sheet) {
|
||||
org.openxmlformats.schemas.presentationml.x2006.main.CTShape shapeCt
|
||||
= org.openxmlformats.schemas.presentationml.x2006.main.CTShape.Factory.newInstance();
|
||||
|
||||
// The fields on MS CTShape largely re-use the underlying openxml classes.
|
||||
// We just copy the fields from the MS CTShape to the openxml CTShape
|
||||
shapeCt.setStyle(msShapeCt.getStyle());
|
||||
shapeCt.setSpPr(msShapeCt.getSpPr());
|
||||
|
||||
CTShapeNonVisual nonVisualCt = shapeCt.addNewNvSpPr();
|
||||
nonVisualCt.setCNvPr(msShapeCt.getNvSpPr().getCNvPr());
|
||||
nonVisualCt.setCNvSpPr(msShapeCt.getNvSpPr().getCNvSpPr());
|
||||
nonVisualCt.setNvPr(CTApplicationNonVisualDrawingProps.Factory.newInstance());
|
||||
shapeCt.setNvSpPr(nonVisualCt);
|
||||
|
||||
ArrayList<org.openxmlformats.schemas.presentationml.x2006.main.CTShape> shapes = new ArrayList<>();
|
||||
shapes.add(shapeCt);
|
||||
|
||||
if (hasTextContent(msShapeCt)) {
|
||||
org.openxmlformats.schemas.presentationml.x2006.main.CTShape textShapeCT = convertText(msShapeCt, nonVisualCt);
|
||||
shapes.add(textShapeCT);
|
||||
}
|
||||
|
||||
if (hasBlipEmbed(msShapeCt)) {
|
||||
String embedId = msShapeCt.getSpPr().getBlipFill().getBlip().getEmbed();
|
||||
POIXMLDocumentPart part = _drawing.getRelationById(embedId);
|
||||
if (part != null) {
|
||||
// When reading the blip, POI looks into the `slide#.xml.rels` file. However, the blip relationship is
|
||||
// defined inside `drawing#.xml.rels`. Copy this relationship to the parent.
|
||||
POIXMLDocumentPart.RelationPart updatedRelation = sheet.addRelation(null, XSLFRelation.IMAGES, part);
|
||||
shapeCt.getSpPr().getBlipFill().getBlip().setEmbed(updatedRelation.getRelationship().getId());
|
||||
}
|
||||
}
|
||||
|
||||
return shapes;
|
||||
}
|
||||
|
||||
private org.openxmlformats.schemas.presentationml.x2006.main.CTShape convertText(CTShape msShapeCt, CTShapeNonVisual nonVisualCt) {
|
||||
org.openxmlformats.schemas.presentationml.x2006.main.CTShape textShapeCT
|
||||
= org.openxmlformats.schemas.presentationml.x2006.main.CTShape.Factory.newInstance();
|
||||
|
||||
// SmartArt shapes define a separate `txXfrm` property for the placement of text inside the shape
|
||||
// We can't easily (is it even possible?) set a separate xfrm for the text on the openxml CTShape.
|
||||
// Instead, we create a separate textbox shape with the same xfrm.
|
||||
org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties textShapeProps = textShapeCT.addNewSpPr();
|
||||
textShapeProps.setXfrm(msShapeCt.getTxXfrm());
|
||||
|
||||
textShapeCT.setTxBody(msShapeCt.getTxBody());
|
||||
textShapeCT.setStyle(msShapeCt.getStyle());
|
||||
// Create a copy of the nonVisualCt when setting it for the text box.
|
||||
// If we shared the one object, a consumer may be surprised that updating the text shape properties
|
||||
// also updates the parent shape.
|
||||
textShapeCT.setNvSpPr((CTShapeNonVisual) nonVisualCt.copy());
|
||||
|
||||
return textShapeCT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link XSLFDiagramDrawing} used to create this diagram.
|
||||
* <p>
|
||||
* NOTE: Modifying this drawing will not update the groupShape returned from {@link #getGroupShape()}.
|
||||
*/
|
||||
public XSLFDiagramDrawing getDiagramDrawing() {
|
||||
return _drawing;
|
||||
}
|
||||
|
||||
private XSLFGroupShape initGroupShape(XSLFSheet sheet) {
|
||||
XSLFDiagramDrawing drawing = getDiagramDrawing();
|
||||
if (drawing == null || drawing.getDrawingDocument() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CTGroupShape msGroupShapeCt = drawing.getDrawingDocument().getDrawing().getSpTree();
|
||||
if (msGroupShapeCt == null || msGroupShapeCt.getSpList().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return convertMsGroupToGroupShape(msGroupShapeCt, sheet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the diagram represented as a grouped shape.
|
||||
*/
|
||||
public XSLFGroupShape getGroupShape() {
|
||||
return _groupShape;
|
||||
}
|
||||
|
||||
private XSLFGroupShape convertMsGroupToGroupShape(CTGroupShape msGroupShapeCt, XSLFSheet sheet) {
|
||||
org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape groupShapeCt
|
||||
= org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape.Factory.newInstance();
|
||||
|
||||
CTGroupShapeProperties groupShapePropsCt = groupShapeCt.addNewGrpSpPr();
|
||||
|
||||
CTGroupShapeNonVisual groupShapeNonVisualCt = groupShapeCt.addNewNvGrpSpPr();
|
||||
groupShapeNonVisualCt.setCNvPr(msGroupShapeCt.getNvGrpSpPr().getCNvPr());
|
||||
groupShapeNonVisualCt.setCNvGrpSpPr(msGroupShapeCt.getNvGrpSpPr().getCNvGrpSpPr());
|
||||
groupShapeNonVisualCt.setNvPr(CTApplicationNonVisualDrawingProps.Factory.newInstance());
|
||||
|
||||
for (CTShape msShapeCt : msGroupShapeCt.getSpList()) {
|
||||
List<org.openxmlformats.schemas.presentationml.x2006.main.CTShape> shapes = convertShape(msShapeCt, sheet);
|
||||
groupShapeCt.getSpList().addAll(shapes);
|
||||
}
|
||||
|
||||
Rectangle2D anchor = super.getAnchor();
|
||||
Rectangle2D interiorAnchor = new Rectangle2D.Double(0, 0, anchor.getWidth(), anchor.getHeight());
|
||||
|
||||
XSLFGroupShape groupShape = new XSLFGroupShape(groupShapeCt, getSheet());
|
||||
groupShape.setAnchor(anchor);
|
||||
groupShape.setInteriorAnchor(interiorAnchor);
|
||||
groupShape.setRotation(super.getRotation());
|
||||
return groupShape;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/* ====================================================================
|
||||
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 com.microsoft.schemas.office.drawing.x2008.diagram.DrawingDocument;
|
||||
import org.apache.poi.ooxml.POIXMLDocumentPart;
|
||||
import org.apache.poi.openxml4j.opc.PackagePart;
|
||||
import org.apache.poi.util.Beta;
|
||||
import org.apache.xmlbeans.XmlException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Drawing representation of a SmartArt diagram.
|
||||
*/
|
||||
@Beta
|
||||
public class XSLFDiagramDrawing extends POIXMLDocumentPart {
|
||||
|
||||
private final DrawingDocument _drawingDoc;
|
||||
|
||||
/* package protected */ XSLFDiagramDrawing() {
|
||||
super();
|
||||
_drawingDoc = DrawingDocument.Factory.newInstance();
|
||||
}
|
||||
|
||||
/* package protected */ XSLFDiagramDrawing(PackagePart part) throws XmlException, IOException {
|
||||
super(part);
|
||||
_drawingDoc = readPackagePart(part);
|
||||
}
|
||||
|
||||
private static DrawingDocument readPackagePart(PackagePart part) throws IOException, XmlException {
|
||||
try (InputStream is = part.getInputStream()) {
|
||||
return DrawingDocument.Factory.parse(is);
|
||||
}
|
||||
}
|
||||
|
||||
public DrawingDocument getDrawingDocument() {
|
||||
return _drawingDoc;
|
||||
}
|
||||
}
|
|
@ -53,7 +53,6 @@ import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape;
|
|||
@Beta
|
||||
public class XSLFGraphicFrame extends XSLFShape implements GraphicalFrame<XSLFShape, XSLFTextParagraph> {
|
||||
private static final String DRAWINGML_CHART_URI = "http://schemas.openxmlformats.org/drawingml/2006/chart";
|
||||
private static final String DRAWINGML_DIAGRAM_URI = "http://schemas.openxmlformats.org/drawingml/2006/diagram";
|
||||
private static final Logger LOG = LogManager.getLogger(XSLFGraphicFrame.class);
|
||||
|
||||
/*package*/ XSLFGraphicFrame(CTGraphicalObjectFrame shape, XSLFSheet sheet){
|
||||
|
@ -100,6 +99,8 @@ public class XSLFGraphicFrame extends XSLFShape implements GraphicalFrame<XSLFSh
|
|||
return new XSLFTable(shape, sheet);
|
||||
case XSLFObjectShape.OLE_URI:
|
||||
return new XSLFObjectShape(shape, sheet);
|
||||
case XSLFDiagram.DRAWINGML_DIAGRAM_URI:
|
||||
return new XSLFDiagram(shape, sheet);
|
||||
default:
|
||||
return new XSLFGraphicFrame(shape, sheet);
|
||||
}
|
||||
|
@ -177,7 +178,7 @@ public class XSLFGraphicFrame extends XSLFShape implements GraphicalFrame<XSLFSh
|
|||
*/
|
||||
public boolean hasDiagram() {
|
||||
String uri = getGraphicalData().getUri();
|
||||
return uri.equals(DRAWINGML_DIAGRAM_URI);
|
||||
return uri.equals(XSLFDiagram.DRAWINGML_DIAGRAM_URI);
|
||||
}
|
||||
|
||||
private CTGraphicalObjectData getGraphicalData() {
|
||||
|
@ -211,7 +212,7 @@ public class XSLFGraphicFrame extends XSLFShape implements GraphicalFrame<XSLFSh
|
|||
|
||||
CTGraphicalObjectData data = getGraphicalData();
|
||||
String uri = data.getUri();
|
||||
if(uri.equals(DRAWINGML_DIAGRAM_URI)){
|
||||
if(uri.equals(XSLFDiagram.DRAWINGML_DIAGRAM_URI)){
|
||||
copyDiagram(data, (XSLFGraphicFrame)sh);
|
||||
} if(uri.equals(DRAWINGML_CHART_URI)){
|
||||
copyChart(data, (XSLFGraphicFrame)sh);
|
||||
|
@ -256,7 +257,7 @@ public class XSLFGraphicFrame extends XSLFShape implements GraphicalFrame<XSLFSh
|
|||
|
||||
// TODO should be moved to a sub-class
|
||||
private void copyDiagram(CTGraphicalObjectData objData, XSLFGraphicFrame srcShape){
|
||||
String xpath = "declare namespace dgm='" + DRAWINGML_DIAGRAM_URI + "' $this//dgm:relIds";
|
||||
String xpath = "declare namespace dgm='" + XSLFDiagram.DRAWINGML_DIAGRAM_URI + "' $this//dgm:relIds";
|
||||
XmlObject[] obj = objData.selectPath(xpath);
|
||||
if(obj != null && obj.length == 1) {
|
||||
XSLFSheet sheet = srcShape.getSheet();
|
||||
|
|
|
@ -150,6 +150,13 @@ public final class XSLFRelation extends POIXMLRelation {
|
|||
XSLFChart::new, XSLFChart::new
|
||||
);
|
||||
|
||||
public static final XSLFRelation DIAGRAM_DRAWING = new XSLFRelation(
|
||||
"application/vnd.ms-office.drawingml.diagramDrawing+xml",
|
||||
"http://schemas.microsoft.com/office/2007/relationships/diagramDrawing",
|
||||
"/ppt/diagrams/drawing#.xml",
|
||||
XSLFDiagramDrawing::new, XSLFDiagramDrawing::new
|
||||
);
|
||||
|
||||
public static final XSLFRelation IMAGE_EMF = new XSLFRelation(
|
||||
PictureType.EMF.contentType,
|
||||
IMAGE_PART,
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/* ====================================================================
|
||||
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 org.apache.poi.openxml4j.opc.ContentTypes;
|
||||
import org.apache.poi.sl.usermodel.ColorStyle;
|
||||
import org.apache.poi.sl.usermodel.PaintStyle;
|
||||
import org.apache.poi.sl.usermodel.TextParagraph.TextAlign;
|
||||
import org.apache.poi.xslf.XSLFTestDataSamples;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TestXSLFDiagram {
|
||||
|
||||
private static final String SIMPLE_DIAGRAM = "smartart-simple.pptx";
|
||||
|
||||
private static List<XSLFDiagram> extractDiagrams(XMLSlideShow slideShow) {
|
||||
return slideShow.getSlides()
|
||||
.stream()
|
||||
.flatMap(s -> s.getShapes().stream())
|
||||
.filter(s -> s instanceof XSLFDiagram)
|
||||
.map(s -> (XSLFDiagram) s)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/3607942
|
||||
private static String colorToHex(Color color) {
|
||||
return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
|
||||
}
|
||||
|
||||
private static Color hexToColor(String hex) {
|
||||
return Color.decode(hex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasDiagram() {
|
||||
XMLSlideShow inputPptx = XSLFTestDataSamples.openSampleDocument(SIMPLE_DIAGRAM);
|
||||
List<XSLFDiagram> diagrams = extractDiagrams(inputPptx);
|
||||
assertEquals(1, diagrams.size());
|
||||
|
||||
XSLFDiagram diagram = diagrams.get(0);
|
||||
assertTrue(diagram.hasDiagram());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDiagramContainsShapes() {
|
||||
XMLSlideShow inputPptx = XSLFTestDataSamples.openSampleDocument(SIMPLE_DIAGRAM);
|
||||
List<XSLFDiagram> diagrams = extractDiagrams(inputPptx);
|
||||
assertEquals(1, diagrams.size());
|
||||
|
||||
XSLFDiagram diagram = diagrams.get(0);
|
||||
XSLFGroupShape groupShape = diagram.getGroupShape();
|
||||
assertNotNull(groupShape);
|
||||
|
||||
// The Group gets the same positioning as the SmartArt. This can be much wider/taller than the content inside.
|
||||
assertEquals(groupShape.getAnchor().getWidth(), 113.375, 1E-4);
|
||||
assertEquals(groupShape.getAnchor().getHeight(), 74, 1E-4);
|
||||
assertEquals(groupShape.getAnchor().getX(), -16.75, 1E-4);
|
||||
assertEquals(groupShape.getAnchor().getY(), 5.5, 1E-4);
|
||||
|
||||
List<XSLFShape> shapes = groupShape.getShapes();
|
||||
// 4 shapes, 3 text boxes, one shape does not have any text inside it
|
||||
assertEquals(7, shapes.size());
|
||||
|
||||
// Shape 1 - Yellow Circle - "abc" center aligned
|
||||
String accent4Hex = "#ffc000"; // yellow
|
||||
XSLFAutoShape yellowCircle = (XSLFAutoShape) shapes.get(0);
|
||||
assertTrue(yellowCircle.getText().isEmpty());
|
||||
assertEquals(accent4Hex, colorToHex(yellowCircle.getFillColor()));
|
||||
|
||||
XSLFAutoShape yellowCircleText = (XSLFAutoShape) shapes.get(1);
|
||||
assertEquals(yellowCircleText.getText(), "abc");
|
||||
assertEquals(TextAlign.CENTER, yellowCircleText.getTextParagraphs().get(0).getTextAlign());
|
||||
|
||||
// Shape 2 - Gradient Blue & Purple - "def" left aligned
|
||||
XSLFAutoShape gradientCircle = (XSLFAutoShape) shapes.get(2);
|
||||
assertTrue(gradientCircle.getFillPaint() instanceof PaintStyle.GradientPaint);
|
||||
assertTrue(gradientCircle.getText().isEmpty());
|
||||
|
||||
XSLFAutoShape gradientCircleText = (XSLFAutoShape) shapes.get(3);
|
||||
assertEquals(gradientCircleText.getText(), "def");
|
||||
// Even with left justification, the text is rendered on the right side of the circle because SmartArt defines
|
||||
// a better visual placement for the textbox inside the txXfrm property.
|
||||
assertEquals(1, gradientCircleText.getTextParagraphs().size());
|
||||
XSLFTextParagraph paragraph = gradientCircleText.getTextParagraphs().get(0);
|
||||
assertEquals(TextAlign.LEFT, paragraph.getTextAlign());
|
||||
assertEquals(1, paragraph.getTextRuns().size());
|
||||
XSLFTextRun textRun = paragraph.getTextRuns().get(0);
|
||||
assertTrue(textRun.isBold());
|
||||
assertTrue(textRun.isItalic());
|
||||
|
||||
// Shape 3 - Green Circle with theme color - "ghi" right aligned
|
||||
XSLFAutoShape greenCircle = (XSLFAutoShape) shapes.get(4);
|
||||
ColorStyle greenCircleColorStyle = ((PaintStyle.SolidPaint) greenCircle.getFillPaint()).getSolidColor();
|
||||
// The circle uses the yellow accent4 color but has HSL adjustments that make it green
|
||||
assertEquals(hexToColor(accent4Hex), greenCircleColorStyle.getColor());
|
||||
assertEquals(50004, greenCircleColorStyle.getAlpha()); // 50% transparency
|
||||
assertEquals(6533927, greenCircleColorStyle.getHueOff());
|
||||
assertEquals(6405, greenCircleColorStyle.getLumOff());
|
||||
assertEquals(-27185, greenCircleColorStyle.getSatOff());
|
||||
|
||||
XSLFAutoShape greenCircleText = (XSLFAutoShape) shapes.get(5);
|
||||
assertEquals(greenCircleText.getText(), "ghi");
|
||||
assertEquals(TextAlign.RIGHT, greenCircleText.getTextParagraphs().get(0).getTextAlign());
|
||||
|
||||
// Shape 4 - Circle with Picture Fill - no text
|
||||
XSLFAutoShape pictureShape = (XSLFAutoShape) shapes.get(6);
|
||||
assertTrue(pictureShape.getText().isEmpty());
|
||||
XSLFTexturePaint texturePaint = (XSLFTexturePaint) pictureShape.getFillPaint();
|
||||
assertEquals(ContentTypes.IMAGE_JPEG, texturePaint.getContentType());
|
||||
}
|
||||
}
|
Binary file not shown.
Loading…
Reference in New Issue