From 6bf42e8072e265917a6f306500e8cbe2d959194f Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Thu, 11 Aug 2011 08:38:19 +0000 Subject: [PATCH] initial support for XSLF usermodel API git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1156539 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 1 + .../poi/xslf/usermodel/SlidesAndShapes.java | 86 ++++ src/ooxml/java/org/apache/poi/util/Units.java | 17 + .../org/apache/poi/xslf/XSLFSlideShow.java | 18 +- .../extractor/XSLFPowerPointExtractor.java | 79 ++-- .../poi/xslf/usermodel/DrawingParagraph.java | 6 +- .../poi/xslf/usermodel/DrawingTable.java | 4 +- .../poi/xslf/usermodel/DrawingTableRow.java | 6 +- .../poi/xslf/usermodel/DrawingTextBody.java | 4 +- .../apache/poi/xslf/usermodel/LineCap.java | 19 + .../apache/poi/xslf/usermodel/LineDash.java | 17 + .../poi/xslf/usermodel/LineDecoration.java | 29 ++ .../poi/xslf/usermodel/LineEndLength.java | 14 + .../poi/xslf/usermodel/LineEndWidth.java | 14 + .../poi/xslf/usermodel/Placeholder.java | 36 ++ .../apache/poi/xslf/usermodel/TextAlign.java | 47 ++ .../poi/xslf/usermodel/TextAutofit.java | 59 +++ .../poi/xslf/usermodel/TextDirection.java | 48 ++ .../poi/xslf/usermodel/VerticalAlignment.java | 71 +++ .../poi/xslf/usermodel/XMLSlideShow.java | 340 +++++++++++--- .../poi/xslf/usermodel/XSLFAutoShape.java | 427 ++++++++++++++++++ .../xslf/usermodel/XSLFCommonSlideData.java | 2 + .../xslf/usermodel/XSLFConnectorShape.java | 195 ++++++++ .../poi/xslf/usermodel/XSLFDrawing.java | 84 ++++ .../poi/xslf/usermodel/XSLFFactory.java | 81 ++++ .../poi/xslf/usermodel/XSLFFreeformShape.java | 178 ++++++++ .../poi/xslf/usermodel/XSLFGraphicFrame.java | 79 ++++ .../poi/xslf/usermodel/XSLFGroupShape.java | 220 +++++++++ .../poi/xslf/usermodel/XSLFPictureData.java | 171 +++++++ .../poi/xslf/usermodel/XSLFPictureShape.java | 107 +++++ .../poi/xslf/usermodel/XSLFRelation.java | 68 ++- .../apache/poi/xslf/usermodel/XSLFShape.java | 48 ++ .../apache/poi/xslf/usermodel/XSLFSheet.java | 217 +++++++-- .../poi/xslf/usermodel/XSLFSimpleShape.java | 268 +++++++++++ .../apache/poi/xslf/usermodel/XSLFSlide.java | 186 +++++--- .../poi/xslf/usermodel/XSLFSlideLayout.java | 103 +++++ .../poi/xslf/usermodel/XSLFSlideMaster.java | 90 ++++ .../poi/xslf/usermodel/XSLFTextBox.java | 97 ++++ .../poi/xslf/usermodel/XSLFTextParagraph.java | 334 ++++++++++++++ .../poi/xslf/usermodel/XSLFTextRun.java | 202 +++++++++ .../apache/poi/xslf/XSLFTestDataSamples.java | 2 +- .../poi/xslf/usermodel/TestXSLFAutoShape.java | 267 +++++++++++ .../usermodel/TestXSLFConnectorShape.java | 117 +++++ .../xslf/usermodel/TestXSLFFreeformShape.java | 50 ++ .../xslf/usermodel/TestXSLFGroupShape.java | 99 ++++ .../xslf/usermodel/TestXSLFPictureShape.java | 65 +++ .../poi/xslf/usermodel/TestXSLFShape.java | 101 +++++ .../poi/xslf/usermodel/TestXSLFSheet.java | 66 +++ .../xslf/usermodel/TestXSLFSimpleShape.java | 132 ++++++ .../poi/xslf/usermodel/TestXSLFSlide.java | 102 +++++ .../poi/xslf/usermodel/TestXSLFSlideShow.java | 108 +++++ .../poi/xslf/usermodel/TestXSLFTextBox.java | 37 ++ .../org/apache/poi/xslf/usermodel/empty.pptx | Bin 0 -> 29356 bytes test-data/slideshow/shapes.pptx | Bin 0 -> 64058 bytes 54 files changed, 4995 insertions(+), 223 deletions(-) create mode 100644 src/examples/src/org/apache/poi/xslf/usermodel/SlidesAndShapes.java create mode 100755 src/ooxml/java/org/apache/poi/util/Units.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/LineCap.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/LineDash.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/LineDecoration.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/LineEndLength.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/LineEndWidth.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/Placeholder.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/TextAlign.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/TextAutofit.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/TextDirection.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/VerticalAlignment.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFAutoShape.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFConnectorShape.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFactory.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFreeformShape.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureData.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideLayout.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideMaster.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextBox.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java create mode 100755 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java create mode 100755 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java create mode 100644 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFConnectorShape.java create mode 100755 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFFreeformShape.java create mode 100755 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFGroupShape.java create mode 100644 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFPictureShape.java create mode 100755 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFShape.java create mode 100644 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSheet.java create mode 100644 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java create mode 100755 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java create mode 100755 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlideShow.java create mode 100644 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextBox.java create mode 100755 src/resources/scratchpad/org/apache/poi/xslf/usermodel/empty.pptx create mode 100755 test-data/slideshow/shapes.pptx diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index ac2112f65..bea504085 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + initial support for XSLF usermodel API 51187 - fixed OPCPackage to correctly handle self references 51635 - Improved performance of XSSFSheet#write 47731 - Word Extractor considers text copied from some website as an embedded object diff --git a/src/examples/src/org/apache/poi/xslf/usermodel/SlidesAndShapes.java b/src/examples/src/org/apache/poi/xslf/usermodel/SlidesAndShapes.java new file mode 100644 index 000000000..cb700c613 --- /dev/null +++ b/src/examples/src/org/apache/poi/xslf/usermodel/SlidesAndShapes.java @@ -0,0 +1,86 @@ +/* ==================================================================== + 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.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.io.FileOutputStream; + +/** + * Simple demo that creates a pptx slide show using the XSLF API + * + * @author Yegor Kozlov + */ +public class SlidesAndShapes { + + public static void main(String[] args) throws Exception { + XMLSlideShow ppt = new XMLSlideShow(); + ppt.setPageSize(new Dimension(792, 612)); + + XSLFSlide slide1 = ppt.createSlide(); + XSLFTextBox textBox = slide1.createTextBox(); + XSLFTextRun r1 = textBox.addNewTextParagraph().addNewTextRun(); + r1.setBold(true); + r1.setItalic(true); + r1.setFontColor(Color.yellow); + r1.setFontFamily("Arial"); + r1.setFontSize(24); + r1.setText("Apache"); + XSLFTextRun r2 = textBox.addNewTextParagraph().addNewTextRun(); + r2.setStrikethrough(true); + r2.setUnderline(true); + r2.setText("POI\u2122"); + XSLFTextRun r3 = textBox.addNewTextParagraph().addNewTextRun(); + r3.setFontFamily("Wingdings"); + r3.setText(" Version 3.8"); + + textBox.setAnchor(new Rectangle(50, 50, 200, 100)); + textBox.setLineColor(Color.black); + textBox.setFillColor(Color.orange); + + XSLFAutoShape shape2 = slide1.createAutoShape(); + + shape2.setAnchor(new Rectangle(100, 100, 200, 200)); + + XSLFFreeformShape shape3 = slide1.createFreeform(); + Rectangle rect = new Rectangle(150, 150, 300, 300); + GeneralPath path = new GeneralPath(rect); + path.append(new Ellipse2D.Double(200, 200, 100, 50), false); + shape3.setPath(path); + shape3.setAnchor(path.getBounds2D()); + shape3.setLineColor(Color.black); + shape3.setFillColor(Color.lightGray); + + XSLFSlide slide2 = ppt.createSlide(); + XSLFGroupShape group = slide2.createGroup(); + + group.setAnchor(new Rectangle(0, 0, 792, 612)); + group.setInteriorAnchor(new Rectangle(-10, -10, 20, 20)); + + XSLFAutoShape shape4 = group.createAutoShape(); + shape4.setAnchor(new Rectangle(0, 0, 5, 5)); + shape4.setLineWidth(5); + shape4.setLineColor(Color.black); + + + FileOutputStream out = new FileOutputStream("xslf-demo.pptx"); + ppt.write(out); + out.close(); + } + +} diff --git a/src/ooxml/java/org/apache/poi/util/Units.java b/src/ooxml/java/org/apache/poi/util/Units.java new file mode 100755 index 000000000..59ce34cdf --- /dev/null +++ b/src/ooxml/java/org/apache/poi/util/Units.java @@ -0,0 +1,17 @@ +package org.apache.poi.util; + +/** + * @author Yegor Kozlov + */ +public class Units { + public static final int EMU_PER_PIXEL = 9525; + public static final int EMU_PER_POINT = 12700; + + public static int toEMU(double value){ + return (int)Math.round(EMU_PER_POINT*value); + } + + public static double toPoints(long emu){ + return (double)emu/EMU_PER_POINT; + } +} diff --git a/src/ooxml/java/org/apache/poi/xslf/XSLFSlideShow.java b/src/ooxml/java/org/apache/poi/xslf/XSLFSlideShow.java index 05351c689..d2986dc3f 100644 --- a/src/ooxml/java/org/apache/poi/xslf/XSLFSlideShow.java +++ b/src/ooxml/java/org/apache/poi/xslf/XSLFSlideShow.java @@ -16,20 +16,16 @@ ==================================================================== */ package org.apache.poi.xslf; -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; - import org.apache.poi.POIXMLDocument; -import org.apache.poi.util.Internal; -import org.apache.poi.xslf.usermodel.XMLSlideShow; -import org.apache.poi.xslf.usermodel.XSLFRelation; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; +import org.apache.poi.util.Internal; +import org.apache.poi.xslf.usermodel.XMLSlideShow; +import org.apache.poi.xslf.usermodel.XSLFRelation; import org.apache.xmlbeans.XmlException; import org.openxmlformats.schemas.presentationml.x2006.main.CTCommentList; import org.openxmlformats.schemas.presentationml.x2006.main.CTNotesSlide; @@ -46,6 +42,10 @@ import org.openxmlformats.schemas.presentationml.x2006.main.PresentationDocument import org.openxmlformats.schemas.presentationml.x2006.main.SldDocument; import org.openxmlformats.schemas.presentationml.x2006.main.SldMasterDocument; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + /** * Experimental class to do low level processing of pptx files. * @@ -80,10 +80,10 @@ public class XSLFSlideShow extends POIXMLDocument { for (CTSlideIdListEntry ctSlide : getSlideReferences().getSldIdList()) { PackagePart slidePart = getTargetPart(getCorePart().getRelationship(ctSlide.getId2())); - + for(PackageRelationship rel : slidePart.getRelationshipsByType(OLE_OBJECT_REL_TYPE)) embedds.add(getTargetPart(rel)); // TODO: Add this reference to each slide as well - + for(PackageRelationship rel : slidePart.getRelationshipsByType(PACK_OBJECT_REL_TYPE)) embedds.add(getTargetPart(rel)); } diff --git a/src/ooxml/java/org/apache/poi/xslf/extractor/XSLFPowerPointExtractor.java b/src/ooxml/java/org/apache/poi/xslf/extractor/XSLFPowerPointExtractor.java index d20a35796..773e0f458 100644 --- a/src/ooxml/java/org/apache/poi/xslf/extractor/XSLFPowerPointExtractor.java +++ b/src/ooxml/java/org/apache/poi/xslf/extractor/XSLFPowerPointExtractor.java @@ -26,7 +26,10 @@ import org.apache.poi.xslf.usermodel.XSLFCommonSlideData; import org.apache.poi.xslf.usermodel.XSLFRelation; import org.apache.poi.xslf.usermodel.XSLFSlide; import org.apache.xmlbeans.XmlException; -import org.openxmlformats.schemas.presentationml.x2006.main.*; +import org.openxmlformats.schemas.presentationml.x2006.main.CTComment; +import org.openxmlformats.schemas.presentationml.x2006.main.CTCommentList; +import org.openxmlformats.schemas.presentationml.x2006.main.CTNotesSlide; +import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdListEntry; import java.io.IOException; @@ -42,11 +45,11 @@ public class XSLFPowerPointExtractor extends POIXMLTextExtractor { private boolean notesByDefault = false; public XSLFPowerPointExtractor(XMLSlideShow slideshow) { - super(slideshow._getXSLFSlideShow()); + super(slideshow); this.slideshow = slideshow; } public XSLFPowerPointExtractor(XSLFSlideShow slideshow) throws XmlException, IOException { - this(new XMLSlideShow(slideshow)); + this(new XMLSlideShow(slideshow.getPackage())); } public XSLFPowerPointExtractor(OPCPackage container) throws XmlException, OpenXML4JException, IOException { this(new XSLFSlideShow(container)); @@ -55,7 +58,7 @@ public class XSLFPowerPointExtractor extends POIXMLTextExtractor { public static void main(String[] args) throws Exception { if(args.length < 1) { System.err.println("Use:"); - System.err.println(" HXFPowerPointExtractor "); + System.err.println(" XSLFPowerPointExtractor "); System.exit(1); } POIXMLTextExtractor extractor = @@ -95,41 +98,41 @@ public class XSLFPowerPointExtractor extends POIXMLTextExtractor { StringBuffer text = new StringBuffer(); XSLFSlide[] slides = slideshow.getSlides(); - for(int i = 0; i < slides.length; i++) { - CTSlide rawSlide = slides[i]._getCTSlide(); - CTSlideIdListEntry slideId = slides[i]._getCTSlideId(); - - try { - // For now, still very low level - CTNotesSlide notes = - slideshow._getXSLFSlideShow().getNotes(slideId); - CTCommentList comments = - slideshow._getXSLFSlideShow().getSlideComments(slideId); - - if(slideText) { - extractText(slides[i].getCommonSlideData(), text); - - // Comments too for the slide - if(comments != null) { - for(CTComment comment : comments.getCmList()) { - // TODO - comment authors too - // (They're in another stream) - text.append( - comment.getText() + "\n" - ); - } - } - } + try { + XSLFSlideShow xsl = new XSLFSlideShow(slideshow.getPackage()); + for (int i = 0; i < slides.length; i++) { + CTSlideIdListEntry slideId = slideshow.getCTPresentation().getSldIdLst().getSldIdArray(i); - if(notesText && notes != null) { - extractText(new XSLFCommonSlideData(notes.getCSld()), text); - } - } catch(Exception e) { - throw new RuntimeException(e); - } - } - - return text.toString(); + // For now, still very low level + CTNotesSlide notes = + xsl.getNotes(slideId); + CTCommentList comments = + xsl.getSlideComments(slideId); + + if (slideText) { + extractText(new XSLFCommonSlideData(slides[i].getXmlObject().getCSld()), text); + + // Comments too for the slide + if (comments != null) { + for (CTComment comment : comments.getCmList()) { + // TODO - comment authors too + // (They're in another stream) + text.append( + comment.getText() + "\n" + ); + } + } + } + + if (notesText && notes != null) { + extractText(new XSLFCommonSlideData(notes.getCSld()), text); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + return text.toString(); } private void extractText(XSLFCommonSlideData data, StringBuffer text) { diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingParagraph.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingParagraph.java index f1e84ce6c..2e755583b 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingParagraph.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingParagraph.java @@ -17,11 +17,11 @@ package org.apache.poi.xslf.usermodel; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph; -import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun; -import org.openxmlformats.schemas.drawingml.x2006.main.CTTextLineBreak; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlObject; +import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextLineBreak; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph; public class DrawingParagraph { private final CTTextParagraph p; diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTable.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTable.java index e221cfa7c..c6d7da9f2 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTable.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTable.java @@ -17,11 +17,11 @@ package org.apache.poi.xslf.usermodel; -import java.util.List; - import org.openxmlformats.schemas.drawingml.x2006.main.CTTable; import org.openxmlformats.schemas.drawingml.x2006.main.CTTableRow; +import java.util.List; + public class DrawingTable { private final CTTable table; diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTableRow.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTableRow.java index 213dd638d..27b79ea33 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTableRow.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTableRow.java @@ -17,10 +17,10 @@ package org.apache.poi.xslf.usermodel; -import java.util.List; - -import org.openxmlformats.schemas.drawingml.x2006.main.CTTableRow; import org.openxmlformats.schemas.drawingml.x2006.main.CTTableCell; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTableRow; + +import java.util.List; public class DrawingTableRow { private final CTTableRow row; diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTextBody.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTextBody.java index 68a12a4d1..1f40841d3 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTextBody.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/DrawingTextBody.java @@ -17,11 +17,11 @@ package org.apache.poi.xslf.usermodel; -import java.util.List; - import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody; import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph; +import java.util.List; + public class DrawingTextBody { private final CTTextBody textBody; diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/LineCap.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineCap.java new file mode 100755 index 000000000..140f48bf0 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineCap.java @@ -0,0 +1,19 @@ +package org.apache.poi.xslf.usermodel; + +/** + */ +public enum LineCap { + /** + * Rounded ends - the default + */ + ROUND, + /** + * Square protrudes by half line width + */ + SQUARE, + + /** + * Line ends at end point + */ + FLAT; +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/LineDash.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineDash.java new file mode 100755 index 000000000..83b29be1c --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineDash.java @@ -0,0 +1,17 @@ +package org.apache.poi.xslf.usermodel; + +/** + */ +public enum LineDash { + SOLID, + DOT, + DASH, + LG_DASH, + DASH_DOT, + LG_DASH_DOT, + LG_DASH_DOT_DOT, + SYS_DASH, + SYS_DOT, + SYS_DASH_DOT, + SYS_DASH_DOT_DOT; +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/LineDecoration.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineDecoration.java new file mode 100644 index 000000000..72b28c0b3 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineDecoration.java @@ -0,0 +1,29 @@ +/* ==================================================================== + 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; + +/** + * Represents the shape decoration that appears at the ends of lines. + */ +public enum LineDecoration { + NONE, + TRIANGLE, + STEALTH, + DIAMOND, + OVAL, + ARROW +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/LineEndLength.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineEndLength.java new file mode 100644 index 000000000..a66623bca --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineEndLength.java @@ -0,0 +1,14 @@ +package org.apache.poi.xslf.usermodel; + +/** + * Created by IntelliJ IDEA. + * User: yegor + * Date: Aug 9, 2011 + * Time: 9:39:26 PM + * To change this template use File | Settings | File Templates. + */ +public enum LineEndLength { + SMALL, + MEDIUM, + LARGE +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/LineEndWidth.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineEndWidth.java new file mode 100644 index 000000000..5d2d3b86a --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/LineEndWidth.java @@ -0,0 +1,14 @@ +package org.apache.poi.xslf.usermodel; + +/** + * Created by IntelliJ IDEA. + * User: yegor + * Date: Aug 9, 2011 + * Time: 9:39:26 PM + * To change this template use File | Settings | File Templates. + */ +public enum LineEndWidth { + SMALL, + MEDIUM, + LARGE +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/Placeholder.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/Placeholder.java new file mode 100755 index 000000000..0e958015e --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/Placeholder.java @@ -0,0 +1,36 @@ +/* + * ==================================================================== + * 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; + +/** + * @author Yegor Kozlov + */ +public enum Placeholder { + TITLE, + BODY, + CENTERED_TITLE, + SUBTITLE, + DATETIME, + SLIDE_NUMBER, + FOOTER, + HEADER, + OBJECT, + CHART, + TABLE +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/TextAlign.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/TextAlign.java new file mode 100755 index 000000000..c712dbace --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/TextAlign.java @@ -0,0 +1,47 @@ +/* + * ==================================================================== + * 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; + +/** + * Specified a list of text alignment types + * + * @author Yegor Kozlov + */ +public enum TextAlign { + /** + * Align text to the left margin. + */ + LEFT, + /** + * Align text in the center. + */ + CENTER, + + /** + * Align text to the right margin. + */ + RIGHT, + + /** + * Align text so that it is justified across the whole line. It + * is smart in the sense that it will not justify sentences + * which are short + */ + JUSTIFY +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/TextAutofit.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/TextAutofit.java new file mode 100644 index 000000000..94d6b2435 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/TextAutofit.java @@ -0,0 +1,59 @@ +/* + * ==================================================================== + * 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; + +/** + * Specifies alist of auto-fit types. + *

+ * Autofit specofies that a shape should be auto-fit to fully contain the text described within it. + * Auto-fitting is when text within a shape is scaled in order to contain all the text inside + *

+ * + * @author Yegor Kozlov + */ +public enum TextAutofit { + /** + * Specifies that text within the text body should not be auto-fit to the bounding box. + * Auto-fitting is when text within a text box is scaled in order to remain inside + * the text box. + */ + NONE, + /** + * Specifies that text within the text body should be normally auto-fit to the bounding box. + * Autofitting is when text within a text box is scaled in order to remain inside the text box. + * + *

+ * Example: Consider the situation where a user is building a diagram and needs + * to have the text for each shape that they are using stay within the bounds of the shape. + * An easy way this might be done is by using NORMAL autofit + *

+ */ + NORMAL, + /** + * Specifies that a shape should be auto-fit to fully contain the text described within it. + * Auto-fitting is when text within a shape is scaled in order to contain all the text inside. + * + *

+ * Example: Consider the situation where a user is building a diagram and needs to have + * the text for each shape that they are using stay within the bounds of the shape. + * An easy way this might be done is by using SHAPE autofit + *

+ */ + SHAPE +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/TextDirection.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/TextDirection.java new file mode 100644 index 000000000..3f35ec23a --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/TextDirection.java @@ -0,0 +1,48 @@ +/* + * ==================================================================== + * 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; + +/** + * Vertical Text Types + */ +public enum TextDirection { + /** + * Horizontal text. This should be default. + */ + HORIZONTAL, + /** + * Vertical orientation. + * (each line is 90 degrees rotated clockwise, so it goes + * from top to bottom; each next line is to the left from + * the previous one). + */ + VERTICAL, + /** + * Vertical orientation. + * (each line is 270 degrees rotated clockwise, so it goes + * from bottom to top; each next line is to the right from + * the previous one). + */ + VERTICAL_270, + /** + * Determines if all of the text is vertical + * ("one letter on top of another"). + */ + STACKED; +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/VerticalAlignment.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/VerticalAlignment.java new file mode 100755 index 000000000..fd00a64e2 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/VerticalAlignment.java @@ -0,0 +1,71 @@ +/* + * ==================================================================== + * 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; + +/** + * Specifies a list of available anchoring types for text + * + * @author Yegor Kozlov + */ +public enum VerticalAlignment { + /** + * Anchor the text at the top of the bounding rectangle + */ + TOP, + + /** + * Anchor the text at the middle of the bounding rectangle + */ + MIDDLE, + + /** + * Anchor the text at the bottom of the bounding rectangle. + */ + BOTTOM, + + /** + * Anchor the text so that it is justified vertically. + *

+ * When text is horizontal, this spaces out the actual lines of + * text and is almost always identical in behavior to + * {@link #DISTRIBUTED} (special case: if only 1 line, then anchored at top). + *

+ *

+ * When text is vertical, then it justifies the letters + * vertically. This is different than {@link #DISTRIBUTED}, + * because in some cases such as very little text in a line, + * it will not justify. + *

+ */ + JUSTIFIED, + + /** + * Anchor the text so that it is distributed vertically. + *

+ * When text is horizontal, this spaces out the actual lines + * of text and is almost always identical in behavior to + * {@link #JUSTIFIED} (special case: if only 1 line, then anchored in middle). + *

+ *

+ * When text is vertical, then it distributes the letters vertically. + * This is different than {@link #JUSTIFIED}, because it always forces distribution + * of the words, even if there are only one or two words in a line. + */ + DISTRIBUTED +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java index ab5c670b9..7c2db9bb4 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java @@ -16,20 +16,42 @@ ==================================================================== */ package org.apache.poi.xslf.usermodel; -import java.io.IOException; - +import org.apache.poi.POIXMLDocument; +import org.apache.poi.POIXMLDocumentPart; +import org.apache.poi.POIXMLException; +import org.apache.poi.xslf.XSLFSlideShow; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; -import org.apache.poi.sl.usermodel.MasterSheet; -import org.apache.poi.sl.usermodel.Resources; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackagePartName; +import org.apache.poi.openxml4j.opc.TargetMode; import org.apache.poi.sl.usermodel.Slide; import org.apache.poi.sl.usermodel.SlideShow; -import org.apache.poi.xslf.XSLFSlideShow; +import org.apache.poi.util.Beta; +import org.apache.poi.util.Internal; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; +import org.apache.poi.util.PackageHelper; +import org.apache.poi.util.Units; import org.apache.xmlbeans.XmlException; -import org.openxmlformats.schemas.presentationml.x2006.main.CTSlide; +import org.apache.xmlbeans.XmlOptions; +import org.openxmlformats.schemas.officeDocument.x2006.relationships.STRelationshipId; +import org.openxmlformats.schemas.presentationml.x2006.main.CTPresentation; import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdList; import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdListEntry; -import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMasterIdList; +import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideSize; +import org.openxmlformats.schemas.presentationml.x2006.main.PresentationDocument; + +import java.awt.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; /** * High level representation of a ooxml slideshow. @@ -37,57 +59,261 @@ import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMasterIdList; * they are reading or writing a slideshow. It is also the * top level object for creating new slides/etc. */ -public class XMLSlideShow implements SlideShow { - private XSLFSlideShow slideShow; - private XSLFSlide[] slides; - - public XMLSlideShow(XSLFSlideShow xml) throws XmlException, IOException { - this.slideShow = xml; - - // Build the main masters list - TODO - CTSlideMasterIdList masterIds = slideShow.getSlideMasterReferences(); - - // Build the slides list - CTSlideIdList slideIds = slideShow.getSlideReferences(); - slides = new XSLFSlide[slideIds.getSldIdList().size()]; - for(int i=0; i _slides; + private Map _masters; + protected List _pictures; + + public XMLSlideShow() { + this(empty()); + } + + public XMLSlideShow(OPCPackage pkg) { + super(pkg); + + try { + if(getCorePart().getContentType().equals(XSLFRelation.THEME_MANAGER.getContentType())) { + rebase(getPackage()); + } + + //build a tree of POIXMLDocumentParts, this presentation being the root + load(XSLFFactory.getInstance()); + } catch (Exception e){ + throw new POIXMLException(e); + } + } + + public XMLSlideShow(InputStream is) throws IOException { + this(PackageHelper.open(is)); + } + + static final OPCPackage empty() { + InputStream is = XMLSlideShow.class.getResourceAsStream("empty.pptx"); + if (is == null) { + throw new RuntimeException("Missing resource 'empty.pptx'"); + } + try { + return OPCPackage.open(is); + } catch (Exception e){ + throw new POIXMLException(e); + } + } + + // TODO get rid of this method + @Deprecated + public XSLFSlideShow _getXSLFSlideShow() throws OpenXML4JException, IOException, XmlException{ + return new XSLFSlideShow(getPackage()); + } + + @Override + protected void onDocumentRead() throws IOException { + try { + PresentationDocument doc = + PresentationDocument.Factory.parse(getCorePart().getInputStream()); + _presentation = doc.getPresentation(); + Map shIdMap = new HashMap(); + + _masters = new HashMap(); + for (POIXMLDocumentPart p : getRelations()) { + if (p instanceof XSLFSlide) { + shIdMap.put(p.getPackageRelationship().getId(), (XSLFSlide) p); + } else if (p instanceof XSLFSlideMaster){ + XSLFSlideMaster master = (XSLFSlideMaster)p; + _masters.put(p.getPackageRelationship().getId(), master); + } + } + + _slides = new ArrayList(); + if (_presentation.isSetSldIdLst()) { + List slideIds = _presentation.getSldIdLst().getSldIdList(); + for (CTSlideIdListEntry slId : slideIds) { + XSLFSlide sh = shIdMap.get(slId.getId2()); + if (sh == null) { + _logger.log(POILogger.WARN, "Slide with r:id " + slId.getId() + " was defined, but didn't exist in package, skipping"); + continue; + } + _slides.add(sh); + } + } + } catch (XmlException e) { + throw new POIXMLException(e); + } + } + + + @Override + protected void commit() throws IOException { + XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS); + Map map = new HashMap(); + map.put(STRelationshipId.type.getName().getNamespaceURI(), "r"); + xmlOptions.setSaveSuggestedPrefixes(map); + + PackagePart part = getPackagePart(); + OutputStream out = part.getOutputStream(); + _presentation.save(out, xmlOptions); + out.close(); + } + + /** + * Get the document's embedded files. + */ + public List getAllEmbedds() throws OpenXML4JException { + return Collections.unmodifiableList( + getPackage().getPartsByName(Pattern.compile("/ppt/embeddings/.*?")) + ); + } + + /** + * Returns all Pictures, which are referenced from the document itself. + * @return a {@link List} of {@link PackagePart}. + * The returned {@link List} is unmodifiable. + */ + public List getAllPictures() { + if(_pictures == null){ + List mediaParts = getPackage().getPartsByName(Pattern.compile("/ppt/media/.*?")); + _pictures = new ArrayList(mediaParts.size()); + for(PackagePart part : mediaParts){ + _pictures.add(new XSLFPictureData(part, null)); + } + } + return Collections.unmodifiableList(_pictures); + } + + public XSLFSlide createSlide() { + int slideNumber = 256, cnt = 1; + CTSlideIdList slideList; + if (!_presentation.isSetSldIdLst()) slideList = _presentation.addNewSldIdLst(); + else { + slideList = _presentation.getSldIdLst(); + for(CTSlideIdListEntry slideId : slideList.getSldIdList()){ + slideNumber = (int)Math.max(slideId.getId() + 1, slideNumber); + cnt++; + } + } + + XSLFSlide slide = (XSLFSlide)createRelationship( + XSLFRelation.SLIDE, XSLFFactory.getInstance(), cnt); + + CTSlideIdListEntry slideId = slideList.addNewSldId(); + slideId.setId(slideNumber); + slideId.setId2(slide.getPackageRelationship().getId()); + + String masterId = _presentation.getSldMasterIdLst().getSldMasterIdArray(0).getId2(); + XSLFSlideMaster master = _masters.get(masterId); + + XSLFSlideLayout layout = master.getLayout("blank"); + if(layout == null) throw new IllegalArgumentException("Blank layout was not found"); + + slide.addRelation(layout.getPackageRelationship().getId(), layout); + + PackagePartName ppName = layout.getPackagePart().getPartName(); + slide.getPackagePart().addRelationship(ppName, TargetMode.INTERNAL, + layout.getPackageRelationship().getRelationshipType()); + + _slides.add(slide); + return slide; + } + + public XSLFSlideMaster[] getSlideMasters() { + return _masters.values().toArray(new XSLFSlideMaster[_masters.size()]); + } + + /** + * Return all the slides in the slideshow + */ + public XSLFSlide[] getSlides() { + return _slides.toArray(new XSLFSlide[_slides.size()]); + } + + /** + * + * @param newIndex 0-based index of the slide + */ + public void setSlideOrder(XSLFSlide slide, int newIndex){ + int oldIndex = _slides.indexOf(slide); + if(oldIndex == -1) throw new IllegalArgumentException("Slide not found"); + + // fix the usermodel container + _slides.add(newIndex, _slides.remove(oldIndex)); + + // fix ordering in the low-level xml + List slideIds = _presentation.getSldIdLst().getSldIdList(); + CTSlideIdListEntry oldEntry = slideIds.get(oldIndex); + slideIds.add(newIndex, oldEntry); + slideIds.remove(oldEntry); + } + + public XSLFSlide removeSlide(int index){ + XSLFSlide slide = _slides.remove(index); + removeRelation(slide); + _presentation.getSldIdLst().getSldIdList().remove(index); + return slide; + } + + /** + * Returns the current page size + * + * @return the page size + */ + public Dimension getPageSize(){ + CTSlideSize sz = _presentation.getSldSz(); + int cx = sz.getCx(); + int cy = sz.getCy(); + return new Dimension((int)Units.toPoints(cx), (int)Units.toPoints(cy)); + } + + /** + * Sets the page size to the given Dimension object. + * + * @param pgSize page size + */ + public void setPageSize(Dimension pgSize){ + CTSlideSize sz = CTSlideSize.Factory.newInstance(); + sz.setCx(Units.toEMU(pgSize.getWidth())); + sz.setCy(Units.toEMU(pgSize.getHeight())); + _presentation.setSldSz(sz); + } + + + @Internal + public CTPresentation getCTPresentation(){ + return _presentation; + } + + /** + * Adds a picture to the workbook. + * + * @param pictureData The bytes of the picture + * @param format The format of the picture. + * + * @return the index to this picture (1 based). + * @see XSLFPictureData#PICTURE_TYPE_EMF + * @see XSLFPictureData#PICTURE_TYPE_WMF + * @see XSLFPictureData#PICTURE_TYPE_PICT + * @see XSLFPictureData#PICTURE_TYPE_JPEG + * @see XSLFPictureData#PICTURE_TYPE_PNG + * @see XSLFPictureData#PICTURE_TYPE_DIB + */ + public int addPicture(byte[] pictureData, int format) { + getAllPictures(); + + int imageNumber = getPackage().getPartsByName(Pattern.compile("/ppt/media/.*?")).size() + 1; + XSLFPictureData img = (XSLFPictureData) createRelationship( + XSLFPictureData.RELATIONS[format], XSLFFactory.getInstance(), imageNumber, true); + _pictures.add(img); + try { + OutputStream out = img.getPackagePart().getOutputStream(); + out.write(pictureData); + out.close(); + } catch (IOException e) { + throw new POIXMLException(e); + } + return imageNumber - 1; + } - /** - * Return all the slides in the slideshow - */ - public XSLFSlide[] getSlides() { - return slides; - } - - public Resources getResources() { - // TODO Auto-generated method stub - return null; - } } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFAutoShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFAutoShape.java new file mode 100755 index 000000000..186974997 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFAutoShape.java @@ -0,0 +1,427 @@ +/* + * ==================================================================== + * 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.util.Beta; +import org.apache.poi.util.Units; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPresetGeometry2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor; +import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBodyProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph; +import org.openxmlformats.schemas.drawingml.x2006.main.STShapeType; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextAnchoringType; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextWrappingType; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextVerticalType; +import org.openxmlformats.schemas.presentationml.x2006.main.CTShape; +import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual; +import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; +import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a preset geometric shape. + * + * @author Yegor Kozlov + */ +@Beta +public class XSLFAutoShape extends XSLFSimpleShape { + private final List _paragraphs; + + /*package*/ XSLFAutoShape(CTShape shape, XSLFSheet sheet) { + super(shape, sheet); + + _paragraphs = new ArrayList(); + if (shape.isSetTxBody()) { + CTTextBody txBody = shape.getTxBody(); + for (CTTextParagraph p : txBody.getPList()) { + _paragraphs.add(new XSLFTextParagraph(p)); + } + } + } + + /*package*/ + static XSLFAutoShape create(CTShape shape, XSLFSheet sheet) { + if (shape.getSpPr().isSetCustGeom()) { + return new XSLFFreeformShape(shape, sheet); + } else if (shape.getNvSpPr().getCNvSpPr().isSetTxBox()) { + return new XSLFTextBox(shape, sheet); + } else { + return new XSLFAutoShape(shape, sheet); + } + } + + // textual properties + public String getText() { + StringBuilder out = new StringBuilder(); + for (XSLFTextParagraph p : _paragraphs) { + if (out.length() > 0) out.append('\n'); + out.append(p.getText()); + } + return out.toString(); + } + + public List getTextParagraphs() { + return _paragraphs; + } + + public XSLFTextParagraph addNewTextParagraph() { + CTShape shape = (CTShape) getXmlObject(); + CTTextBody txBody; + if (!shape.isSetTxBody()) { + txBody = shape.addNewTxBody(); + txBody.addNewBodyPr(); + txBody.addNewLstStyle(); + } else { + txBody = shape.getTxBody(); + } + CTTextParagraph p = txBody.addNewP(); + XSLFTextParagraph paragraph = new XSLFTextParagraph(p); + _paragraphs.add(paragraph); + return paragraph; + } + + /** + * @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("AutoShape " + shapeId); + cnv.setId(shapeId + 1); + nvSpPr.addNewCNvSpPr(); + nvSpPr.addNewNvPr(); + CTShapeProperties spPr = ct.addNewSpPr(); + CTPresetGeometry2D prst = spPr.addNewPrstGeom(); + prst.setPrst(STShapeType.RECT); + prst.addNewAvLst(); + return ct; + } + + /** + * Specifies a solid color fill. The shape is filled entirely with the specified color. + * + * @param color the solid color fill. + * The value of null unsets the solidFIll attribute from the underlying xml + */ + public void setFillColor(Color color) { + CTShapeProperties spPr = getSpPr(); + if (color == null) { + if(spPr.isSetSolidFill()) spPr.unsetSolidFill(); + } + else { + CTSolidColorFillProperties fill = spPr.isSetSolidFill() ? spPr.getSolidFill() : spPr.addNewSolidFill(); + + CTSRgbColor rgb = CTSRgbColor.Factory.newInstance(); + rgb.setVal(new byte[]{(byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()}); + + fill.setSrgbClr(rgb); + } + } + + /** + * + * @return solid fill color of null if not set + */ + public Color getFillColor(){ + CTShapeProperties spPr = getSpPr(); + if(!spPr.isSetSolidFill() ) return null; + + CTSolidColorFillProperties fill = spPr.getSolidFill(); + if(!fill.isSetSrgbClr()) { + // TODO for now return null for all colors except explicit RGB + return null; + } + byte[] val = fill.getSrgbClr().getVal(); + return new Color(0xFF & val[0], 0xFF & val[1], 0xFF & val[2]); + } + + /** + * Sets the type of vertical alignment for the text. + * One of the Anchor* constants defined in this class. + * + * @param anchor - the type of alignment. Default is {@link VerticalAlignment#TOP} + */ + public void setVerticalAlignment(VerticalAlignment anchor){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + if(anchor == null) { + if(bodyPr.isSetAnchor()) bodyPr.unsetAnchor(); + } else { + bodyPr.setAnchor(STTextAnchoringType.Enum.forInt(anchor.ordinal() + 1)); + } + } + } + + /** + * Returns the type of vertical alignment for the text. + * + * @return the type of alignment + */ + public VerticalAlignment getVerticalAlignment(){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + STTextAnchoringType.Enum val = shape.getTxBody().getBodyPr().getAnchor(); + if(val != null){ + return VerticalAlignment.values()[val.intValue() - 1]; + } + } + return VerticalAlignment.TOP; + } + + /** + * + * @param orientation vertical orientation of the text + */ + public void setTextDirection(TextDirection orientation){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + if(orientation == null) { + if(bodyPr.isSetVert()) bodyPr.unsetVert(); + } else { + bodyPr.setVert(STTextVerticalType.Enum.forInt(orientation.ordinal() + 1)); + } + } + } + + /** + * @return vertical orientation of the text + */ + public TextDirection getTextDirection(){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + STTextVerticalType.Enum val = shape.getTxBody().getBodyPr().getVert(); + if(val != null){ + return TextDirection.values()[val.intValue() - 1]; + } + } + return TextDirection.HORIZONTAL; + } + /** + * Returns the distance (in points) between the bottom of the text frame + * and the bottom of the inscribed rectangle of the shape that contains the text. + * + * @return the bottom margin or -1 if not set + */ + public double getMarginBottom(){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + return bodyPr.isSetBIns() ? Units.toPoints(bodyPr.getBIns()) : -1; + } + return -1; + } + + /** + * Returns the distance (in points) between the left edge of the text frame + * and the left edge of the inscribed rectangle of the shape that contains + * the text. + * + * @return the left margin + */ + public double getMarginLeft(){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + return bodyPr.isSetLIns() ? Units.toPoints(bodyPr.getLIns()) : -1; + } + return -1; + } + + /** + * Returns the distance (in points) between the right edge of the + * text frame and the right edge of the inscribed rectangle of the shape + * that contains the text. + * + * @return the right margin + */ + public double getMarginRight(){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + return bodyPr.isSetRIns() ? Units.toPoints(bodyPr.getRIns()) : -1; + } + return -1; + } + + /** + * Returns the distance (in points) between the top of the text frame + * and the top of the inscribed rectangle of the shape that contains the text. + * + * @return the top margin + */ + public double getMarginTop(){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + return bodyPr.isSetTIns() ? Units.toPoints(bodyPr.getTIns()) : -1; + } + return -1; + } + + /** + * Sets the botom margin. + * @see #getMarginBottom() + * + * @param margin the bottom margin + */ + public void setMarginBottom(double margin){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + if(margin == -1) bodyPr.unsetBIns(); + else bodyPr.setBIns(Units.toEMU(margin)); + } + } + + /** + * Sets the left margin. + * @see #getMarginLeft() + * + * @param margin the left margin + */ + public void setMarginLeft(double margin){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + if(margin == -1) bodyPr.unsetLIns(); + else bodyPr.setLIns(Units.toEMU(margin)); + } + } + + /** + * Sets the right margin. + * @see #getMarginRight() + * + * @param margin the right margin + */ + public void setMarginRight(double margin){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + if(margin == -1) bodyPr.unsetRIns(); + else bodyPr.setRIns(Units.toEMU(margin)); + } + } + + /** + * Sets the top margin. + * @see #getMarginTop() + * + * @param margin the top margin + */ + public void setMarginTop(double margin){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + if(margin == -1) bodyPr.unsetTIns(); + else bodyPr.setTIns(Units.toEMU(margin)); + } + } + + + /** + * Returns the value indicating word wrap. + * One of the Wrap* constants defined in this class. + * + * @return the value indicating word wrap + */ + public boolean getWordWrap(){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + return shape.getTxBody().getBodyPr().getWrap() == STTextWrappingType.SQUARE; + } + return false; + } + + /** + * Specifies how the text should be wrapped + * + * @param wrap the value indicating how the text should be wrapped + */ + public void setWordWrap(boolean wrap){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + shape.getTxBody().getBodyPr().setWrap(wrap ? STTextWrappingType.SQUARE : STTextWrappingType.NONE); + } + } + + /** + * + * Specifies that a shape should be auto-fit to fully contain the text described within it. + * Auto-fitting is when text within a shape is scaled in order to contain all the text inside + * + * @param value type of autofit + */ + public void setTextAutofit(TextAutofit value){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + if(bodyPr.isSetSpAutoFit()) bodyPr.unsetSpAutoFit(); + if(bodyPr.isSetNoAutofit()) bodyPr.unsetNoAutofit(); + if(bodyPr.isSetNormAutofit()) bodyPr.unsetNormAutofit(); + + switch(value){ + case NONE: bodyPr.addNewNoAutofit(); break; + case NORMAL: bodyPr.addNewNormAutofit(); break; + case SHAPE: bodyPr.addNewSpAutoFit(); break; + } + } + } + + /** + * + * @return type of autofit + */ + public TextAutofit getTextAutofit(){ + CTShape shape = (CTShape) getXmlObject(); + if (shape.isSetTxBody()) { + CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); + if(bodyPr.isSetNoAutofit()) return TextAutofit.NONE; + else if (bodyPr.isSetNormAutofit()) return TextAutofit.NORMAL; + else if (bodyPr.isSetSpAutoFit()) return TextAutofit.SHAPE; + } + return TextAutofit.NORMAL; + } + + + @Override + void onCopy(XSLFSheet srcSheet){ + CTShape shape = (CTShape) getXmlObject(); + if (!shape.isSetTxBody()) return; + + CTPlaceholder ph = shape.getNvSpPr().getNvPr().getPh(); + if(ph == null || !ph.isSetType()) return; + + if(ph.getType() == STPlaceholderType.TITLE){ + + } + } + +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFCommonSlideData.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFCommonSlideData.java index 22cde4f9b..a56128155 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFCommonSlideData.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFCommonSlideData.java @@ -18,6 +18,7 @@ package org.apache.poi.xslf.usermodel; import org.apache.poi.POIXMLException; +import org.apache.poi.util.Beta; import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlObject; @@ -34,6 +35,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +@Beta public class XSLFCommonSlideData { private final CTCommonSlideData data; diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFConnectorShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFConnectorShape.java new file mode 100755 index 000000000..d93c1351f --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFConnectorShape.java @@ -0,0 +1,195 @@ +/* + * ==================================================================== + * 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.util.Beta; +import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPresetGeometry2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.STShapeType; +import org.openxmlformats.schemas.drawingml.x2006.main.CTLineEndProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineEndType; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineEndWidth; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineEndLength; +import org.openxmlformats.schemas.presentationml.x2006.main.CTConnector; +import org.openxmlformats.schemas.presentationml.x2006.main.CTConnectorNonVisual; + +/** + * + * Specifies a connection shape. + * + * @author Yegor Kozlov + */ +@Beta +public class XSLFConnectorShape extends XSLFSimpleShape { + + /*package*/ XSLFConnectorShape(CTConnector shape, XSLFSheet sheet){ + super(shape, sheet); + } + + /** + * @param shapeId 1-based shapeId + */ + static CTConnector prototype(int shapeId) { + CTConnector ct = CTConnector.Factory.newInstance(); + CTConnectorNonVisual nvSpPr = ct.addNewNvCxnSpPr(); + CTNonVisualDrawingProps cnv = nvSpPr.addNewCNvPr(); + cnv.setName("Connector " + shapeId); + cnv.setId(shapeId + 1); + nvSpPr.addNewCNvCxnSpPr(); + nvSpPr.addNewNvPr(); + CTShapeProperties spPr = ct.addNewSpPr(); + CTPresetGeometry2D prst = spPr.addNewPrstGeom(); + prst.setPrst(STShapeType.LINE); + prst.addNewAvLst(); + CTLineProperties ln = spPr.addNewLn(); + return ct; + } + + /** + * Specifies the line end decoration, such as a triangle or arrowhead. + */ + public void setLineHeadDecoration(LineDecoration style){ + CTLineProperties ln = getSpPr().getLn(); + CTLineEndProperties lnEnd = ln.isSetHeadEnd() ? ln.getHeadEnd() : ln.addNewHeadEnd(); + if(style == null){ + if(lnEnd.isSetType()) lnEnd.unsetType(); + } else { + lnEnd.setType(STLineEndType.Enum.forInt(style.ordinal() + 1)); + } + } + + public LineDecoration getLineHeadDecoration(){ + CTLineProperties ln = getSpPr().getLn(); + if(!ln.isSetHeadEnd()) return LineDecoration.NONE; + + STLineEndType.Enum end = ln.getHeadEnd().getType(); + return end == null ? LineDecoration.NONE : LineDecoration.values()[end.intValue() - 1]; + } + + /** + * specifies decorations which can be added to the head of a line. + */ + public void setLineHeadWidth(LineEndWidth style){ + CTLineProperties ln = getSpPr().getLn(); + CTLineEndProperties lnEnd = ln.isSetHeadEnd() ? ln.getHeadEnd() : ln.addNewHeadEnd(); + if(style == null){ + if(lnEnd.isSetW()) lnEnd.unsetW(); + } else { + lnEnd.setW(STLineEndWidth.Enum.forInt(style.ordinal() + 1)); + } + } + + public LineEndWidth getLineHeadWidth(){ + CTLineProperties ln = getSpPr().getLn(); + if(!ln.isSetHeadEnd()) return null; + + STLineEndWidth.Enum w = ln.getHeadEnd().getW(); + return w == null ? null : LineEndWidth.values()[w.intValue() - 1]; + } + + /** + * Specifies the line end width in relation to the line width. + */ + public void setLineHeadLength(LineEndLength style){ + CTLineProperties ln = getSpPr().getLn(); + CTLineEndProperties lnEnd = ln.isSetHeadEnd() ? ln.getHeadEnd() : ln.addNewHeadEnd(); + + if(style == null){ + if(lnEnd.isSetLen()) lnEnd.unsetLen(); + } else { + lnEnd.setLen(STLineEndLength.Enum.forInt(style.ordinal() + 1)); + } + } + + public LineEndLength getLineHeadLength(){ + CTLineProperties ln = getSpPr().getLn(); + if(!ln.isSetHeadEnd()) return null; + + STLineEndLength.Enum len = ln.getHeadEnd().getLen(); + return len == null ? null : LineEndLength.values()[len.intValue() - 1]; + } + + /** + * Specifies the line end decoration, such as a triangle or arrowhead. + */ + public void setLineTailDecoration(LineDecoration style){ + CTLineProperties ln = getSpPr().getLn(); + CTLineEndProperties lnEnd = ln.isSetTailEnd() ? ln.getTailEnd() : ln.addNewTailEnd(); + if(style == null){ + if(lnEnd.isSetType()) lnEnd.unsetType(); + } else { + lnEnd.setType(STLineEndType.Enum.forInt(style.ordinal() + 1)); + } + } + + public LineDecoration getLineTailDecoration(){ + CTLineProperties ln = getSpPr().getLn(); + if(!ln.isSetTailEnd()) return LineDecoration.NONE; + + STLineEndType.Enum end = ln.getTailEnd().getType(); + return end == null ? LineDecoration.NONE : LineDecoration.values()[end.intValue() - 1]; + } + + /** + * specifies decorations which can be added to the tail of a line. + */ + public void setLineTailWidth(LineEndWidth style){ + CTLineProperties ln = getSpPr().getLn(); + CTLineEndProperties lnEnd = ln.isSetTailEnd() ? ln.getTailEnd() : ln.addNewTailEnd(); + if(style == null){ + if(lnEnd.isSetW()) lnEnd.unsetW(); + } else { + lnEnd.setW(STLineEndWidth.Enum.forInt(style.ordinal() + 1)); + } + } + + public LineEndWidth getLineTailWidth(){ + CTLineProperties ln = getSpPr().getLn(); + if(!ln.isSetTailEnd()) return null; + + STLineEndWidth.Enum w = ln.getTailEnd().getW(); + return w == null ? null : LineEndWidth.values()[w.intValue() - 1]; + } + + /** + * Specifies the line end width in relation to the line width. + */ + public void setLineTailLength(LineEndLength style){ + CTLineProperties ln = getSpPr().getLn(); + CTLineEndProperties lnEnd = ln.isSetTailEnd() ? ln.getTailEnd() : ln.addNewTailEnd(); + + if(style == null){ + if(lnEnd.isSetLen()) lnEnd.unsetLen(); + } else { + lnEnd.setLen(STLineEndLength.Enum.forInt(style.ordinal() + 1)); + } + } + + public LineEndLength getLineTailLength(){ + CTLineProperties ln = getSpPr().getLn(); + if(!ln.isSetTailEnd()) return null; + + STLineEndLength.Enum len = ln.getTailEnd().getLen(); + return len == null ? null : LineEndLength.values()[len.intValue() - 1]; + } + +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java new file mode 100755 index 000000000..2f72bfc96 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java @@ -0,0 +1,84 @@ +package org.apache.poi.xslf.usermodel; + +import org.apache.poi.sl.usermodel.ShapeContainer; +import org.apache.poi.util.Beta; +import org.apache.xmlbeans.XmlObject; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.presentationml.x2006.main.CTConnector; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape; +import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture; +import org.openxmlformats.schemas.presentationml.x2006.main.CTShape; + +import java.awt.*; + + +/** + * @author Yegor Kozlov + */ +@Beta +public class XSLFDrawing { + private XSLFSheet _sheet; + private int _shapeId = 1; + private CTGroupShape _spTree; + + /*package*/ XSLFDrawing(XSLFSheet sheet, CTGroupShape spTree){ + _sheet = sheet; + _spTree = spTree; + XmlObject[] cNvPr = sheet.getSpTree().selectPath( + "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' .//*/p:cNvPr"); + for(XmlObject o : cNvPr) { + CTNonVisualDrawingProps p = (CTNonVisualDrawingProps)o; + _shapeId = (int)Math.max(_shapeId, p.getId()); + } + } + + public XSLFAutoShape createAutoShape(){ + CTShape sp = _spTree.addNewSp(); + sp.set(XSLFAutoShape.prototype(_shapeId++)); + XSLFAutoShape shape = new XSLFAutoShape(sp, _sheet); + shape.setAnchor(new Rectangle()); + return shape; + } + + public XSLFFreeformShape createFreeform(){ + CTShape sp = _spTree.addNewSp(); + sp.set(XSLFFreeformShape.prototype(_shapeId++)); + XSLFFreeformShape shape = new XSLFFreeformShape(sp, _sheet); + shape.setAnchor(new Rectangle()); + return shape; + } + + public XSLFTextBox createTextBox(){ + CTShape sp = _spTree.addNewSp(); + sp.set(XSLFTextBox.prototype(_shapeId++)); + XSLFTextBox shape = new XSLFTextBox(sp, _sheet); + shape.setAnchor(new Rectangle()); + return shape; + } + + public XSLFConnectorShape createConnector(){ + CTConnector sp = _spTree.addNewCxnSp(); + sp.set(XSLFConnectorShape.prototype(_shapeId++)); + XSLFConnectorShape shape = new XSLFConnectorShape(sp, _sheet); + shape.setAnchor(new Rectangle()); + shape.setLineColor(Color.black); + shape.setLineWidth(0.75); + return shape; + } + + public XSLFGroupShape createGroup(){ + CTGroupShape obj = _spTree.addNewGrpSp(); + obj.set(XSLFGroupShape.prototype(_shapeId++)); + XSLFGroupShape shape = new XSLFGroupShape(obj, _sheet); + shape.setAnchor(new Rectangle()); + return shape; + } + + public XSLFPictureShape createPicture(String rel){ + CTPicture obj = _spTree.addNewPic(); + obj.set(XSLFPictureShape.prototype(_shapeId++, rel)); + XSLFPictureShape shape = new XSLFPictureShape(obj, _sheet); + shape.setAnchor(new Rectangle()); + return shape; + } +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFactory.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFactory.java new file mode 100755 index 000000000..cb7f32918 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFactory.java @@ -0,0 +1,81 @@ +/* + * ==================================================================== + * 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.POIXMLDocumentPart; +import org.apache.poi.POIXMLException; +import org.apache.poi.POIXMLFactory; +import org.apache.poi.POIXMLRelation; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.util.Beta; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + +import java.lang.reflect.Constructor; + +/** + * Instantiates sub-classes of POIXMLDocumentPart depending on their relationship type + * + * @author Yegor Kozlov + */ +@Beta +public final class XSLFFactory extends POIXMLFactory { + private static final POILogger logger = POILogFactory.getLogger(XSLFFactory.class); + + private XSLFFactory(){ + + } + + private static final XSLFFactory inst = new XSLFFactory(); + + public static XSLFFactory getInstance(){ + return inst; + } + + @Override + public POIXMLDocumentPart createDocumentPart(POIXMLDocumentPart parent, PackageRelationship rel, PackagePart part){ + POIXMLRelation descriptor = XSLFRelation.getInstance(rel.getRelationshipType()); + if(descriptor == null || descriptor.getRelationClass() == null){ + logger.log(POILogger.DEBUG, "using default POIXMLDocumentPart for " + rel.getRelationshipType()); + return new POIXMLDocumentPart(part, rel); + } + + try { + Class cls = descriptor.getRelationClass(); + Constructor constructor = cls.getDeclaredConstructor(PackagePart.class, PackageRelationship.class); + return constructor.newInstance(part, rel); + } catch (Exception e){ + throw new POIXMLException(e); + } + } + + @Override + public POIXMLDocumentPart newDocumentPart(POIXMLRelation descriptor){ + try { + Class cls = descriptor.getRelationClass(); + Constructor constructor = cls.getDeclaredConstructor(); + return constructor.newInstance(); + } catch (Exception e){ + throw new POIXMLException(e); + } + } + +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFreeformShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFreeformShape.java new file mode 100755 index 000000000..a494a3cab --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFFreeformShape.java @@ -0,0 +1,178 @@ +/* + * ==================================================================== + * 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.sl.usermodel.ShapeContainer; +import org.apache.poi.util.Beta; +import org.apache.poi.util.Units; +import org.apache.xmlbeans.XmlObject; +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.CTShapeProperties; +import org.openxmlformats.schemas.presentationml.x2006.main.CTShape; +import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual; + +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; + +/** + * Represents a custom geometric shape. + * This shape will consist of a series of lines and curves described within a creation path. + * + * @author Yegor Kozlov + */ +@Beta +public class XSLFFreeformShape extends XSLFAutoShape { + + /*package*/ XSLFFreeformShape(CTShape shape, XSLFSheet sheet) { + super(shape, sheet); + } + + /** + * Set the shape path + * + * @param path shape outline + * @return the number of points written + */ + public int setPath(GeneralPath path) { + CTPath2D ctPath = CTPath2D.Factory.newInstance(); + + Rectangle2D bounds = path.getBounds2D(); + int x0 = Units.toEMU(bounds.getX()); + int y0 = Units.toEMU(bounds.getY()); + PathIterator it = path.getPathIterator(new AffineTransform()); + int numPoints = 0; + ctPath.setH(Units.toEMU(bounds.getHeight())); + ctPath.setW(Units.toEMU(bounds.getWidth())); + while (!it.isDone()) { + double[] vals = new double[6]; + int type = it.currentSegment(vals); + switch (type) { + case PathIterator.SEG_MOVETO: + CTAdjPoint2D mv = ctPath.addNewMoveTo().addNewPt(); + mv.setX(Units.toEMU(vals[0]) - x0); + mv.setY(Units.toEMU(vals[1]) - y0); + numPoints++; + break; + case PathIterator.SEG_LINETO: + CTAdjPoint2D ln = ctPath.addNewLnTo().addNewPt(); + ln.setX(Units.toEMU(vals[0]) - x0); + ln.setY(Units.toEMU(vals[1]) - y0); + numPoints++; + break; + case PathIterator.SEG_CUBICTO: + CTPath2DCubicBezierTo bez = ctPath.addNewCubicBezTo(); + CTAdjPoint2D p1 = bez.addNewPt(); + p1.setX(Units.toEMU(vals[0]) - x0); + p1.setY(Units.toEMU(vals[1]) - y0); + CTAdjPoint2D p2 = bez.addNewPt(); + p2.setX(Units.toEMU(vals[2]) - x0); + p2.setY(Units.toEMU(vals[3]) - y0); + CTAdjPoint2D p3 = bez.addNewPt(); + p3.setX(Units.toEMU(vals[4]) - x0); + p3.setY(Units.toEMU(vals[5]) - y0); + numPoints += 3; + break; + case PathIterator.SEG_CLOSE: + numPoints++; + ctPath.addNewClose(); + break; + } + it.next(); + } + getSpPr().getCustGeom().getPathLst().setPathArray(new CTPath2D[]{ctPath}); + setAnchor(bounds); + return numPoints; + } + + public GeneralPath getPath() { + GeneralPath path = new GeneralPath(); + Rectangle2D bounds = getAnchor(); + int x0 = Units.toEMU(bounds.getX()); + int y0 = Units.toEMU(bounds.getY()); + CTCustomGeometry2D geom = getSpPr().getCustGeom(); + for(CTPath2D spPath : geom.getPathLst().getPathList()){ + for(XmlObject ch : spPath.selectPath("*")){ + if(ch instanceof CTPath2DMoveTo){ + CTAdjPoint2D pt = ((CTPath2DMoveTo)ch).getPt(); + path.moveTo(Units.toPoints((Long)pt.getX() + x0), + Units.toPoints((Long)pt.getY() + y0)); + } else if (ch instanceof CTPath2DLineTo){ + CTAdjPoint2D pt = ((CTPath2DLineTo)ch).getPt(); + path.lineTo(Units.toPoints((Long)pt.getX() + x0), + Units.toPoints((Long)pt.getY() + y0)); + } else if (ch instanceof CTPath2DCubicBezierTo){ + CTPath2DCubicBezierTo bez = ((CTPath2DCubicBezierTo)ch); + CTAdjPoint2D pt1 = bez.getPtArray(0); + CTAdjPoint2D pt2 = bez.getPtArray(1); + CTAdjPoint2D pt3 = bez.getPtArray(2); + path.curveTo( + Units.toPoints((Long) pt1.getX() + x0), + Units.toPoints((Long) pt1.getY() + y0), + Units.toPoints((Long) pt2.getX() + x0), + Units.toPoints((Long) pt2.getY() + y0), + Units.toPoints((Long) pt3.getX() + x0), + Units.toPoints((Long) pt3.getY() + y0) + ); + + } else if (ch instanceof CTPath2DClose){ + path.closePath(); + } + } + } + + return path; + } + /** + * @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 + 1); + 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; + } + +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java new file mode 100755 index 000000000..0abad44f3 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java @@ -0,0 +1,79 @@ +/* + * ==================================================================== + * 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.sl.usermodel.Shape; +import org.apache.poi.sl.usermodel.ShapeContainer; +import org.apache.poi.sl.usermodel.ShapeGroup; +import org.apache.poi.util.Beta; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame; + +import java.awt.geom.Rectangle2D; + +/** + * @author Yegor Kozlov + */ +@Beta +public class XSLFGraphicFrame extends XSLFShape { + private final CTGraphicalObjectFrame _shape; + private final XSLFSheet _sheet; + + /*package*/ XSLFGraphicFrame(CTGraphicalObjectFrame shape, XSLFSheet sheet){ + _shape = shape; + _sheet = sheet; + } + + public CTGraphicalObjectFrame getXmlObject(){ + return _shape; + } + + public int getShapeType(){ + throw new RuntimeException("NotImplemented"); + } + + public int getShapeId(){ + return (int)_shape.getNvGraphicFramePr().getCNvPr().getId(); + } + + public String getShapeName(){ + return _shape.getNvGraphicFramePr().getCNvPr().getName(); + } + + public Rectangle2D getAnchor(){ + throw new RuntimeException("NotImplemented"); + } + + public void setAnchor(Rectangle2D anchor){ + throw new RuntimeException("NotImplemented"); + } + + public ShapeGroup getParent(){ + throw new RuntimeException("NotImplemented"); + } + + public Shape[] getShapes(){ + throw new RuntimeException("NotImplemented"); + } + + + public boolean removeShape(Shape shape){ + throw new RuntimeException("NotImplemented"); + } +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java new file mode 100755 index 000000000..804420854 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGroupShape.java @@ -0,0 +1,220 @@ +/* + * ==================================================================== + * 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.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.TargetMode; +import org.apache.poi.sl.usermodel.Shape; +import org.apache.poi.sl.usermodel.ShapeContainer; +import org.apache.poi.sl.usermodel.ShapeGroup; +import org.apache.poi.util.Beta; +import org.apache.poi.util.Units; +import org.apache.xmlbeans.XmlObject; +import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupTransform2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D; +import org.openxmlformats.schemas.presentationml.x2006.main.CTConnector; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShapeNonVisual; +import org.openxmlformats.schemas.presentationml.x2006.main.CTShape; + +import java.awt.geom.Rectangle2D; +import java.util.List; +import java.util.regex.Pattern; + +/** + * Represents a group shape that consists of many shapes grouped together. + * + * @author Yegor Kozlov + */ +@Beta +public class XSLFGroupShape extends XSLFShape { + private final CTGroupShape _shape; + private final XSLFSheet _sheet; + private final List _shapes; + private final CTGroupShapeProperties _spPr; + private XSLFDrawing _drawing; + + /*package*/ XSLFGroupShape(CTGroupShape shape, XSLFSheet sheet){ + _shape = shape; + _sheet = sheet; + + _shapes = _sheet.buildShapes(_shape); + _spPr = shape.getGrpSpPr(); + } + + public CTGroupShape getXmlObject(){ + return _shape; + } + + public Rectangle2D getAnchor(){ + CTGroupTransform2D xfrm = _spPr.getXfrm(); + CTPoint2D off = xfrm.getOff(); + long x = off.getX(); + long y = off.getY(); + CTPositiveSize2D ext = xfrm.getExt(); + long cx = ext.getCx(); + long cy = ext.getCy(); + return new Rectangle2D.Double( + Units.toPoints(x), Units.toPoints(y), + Units.toPoints(cx), Units.toPoints(cy)); + } + + public void setAnchor(Rectangle2D anchor){ + CTGroupTransform2D xfrm = _spPr.isSetXfrm() ? _spPr.getXfrm() : _spPr.addNewXfrm(); + CTPoint2D off = xfrm.isSetOff() ? xfrm.getOff() : xfrm.addNewOff(); + long x = Units.toEMU(anchor.getX()); + long y = Units.toEMU(anchor.getY()); + off.setX(x); + off.setY(y); + CTPositiveSize2D ext = xfrm.isSetExt() ? xfrm.getExt() : xfrm.addNewExt(); + long cx = Units.toEMU(anchor.getWidth()); + long cy = Units.toEMU(anchor.getHeight()); + ext.setCx(cx); + ext.setCy(cy); + } + + public Rectangle2D getInteriorAnchor(){ + CTGroupTransform2D xfrm = _spPr.getXfrm(); + CTPoint2D off = xfrm.getChOff(); + long x = off.getX(); + long y = off.getY(); + CTPositiveSize2D ext = xfrm.getChExt(); + long cx = ext.getCx(); + long cy = ext.getCy(); + return new Rectangle2D.Double( + Units.toPoints(x), Units.toPoints(y), + Units.toPoints(cx), Units.toPoints(cy)); + } + + public void setInteriorAnchor(Rectangle2D anchor){ + CTGroupTransform2D xfrm = _spPr.isSetXfrm() ? _spPr.getXfrm() : _spPr.addNewXfrm(); + CTPoint2D off = xfrm.isSetChOff() ? xfrm.getChOff() : xfrm.addNewChOff(); + long x = Units.toEMU(anchor.getX()); + long y = Units.toEMU(anchor.getY()); + off.setX(x); + off.setY(y); + CTPositiveSize2D ext = xfrm.isSetChExt() ? xfrm.getChExt() : xfrm.addNewChExt(); + long cx = Units.toEMU(anchor.getWidth()); + long cy = Units.toEMU(anchor.getHeight()); + ext.setCx(cx); + ext.setCy(cy); + } + + public XSLFShape[] getShapes(){ + return _shapes.toArray(new XSLFShape[_shapes.size()]); + } + + public boolean removeShape(XSLFShape xShape) { + XmlObject obj = xShape.getXmlObject(); + if(obj instanceof CTShape){ + _shape.getSpList().remove(obj); + } else if (obj instanceof CTGroupShape){ + _shape.getGrpSpList().remove(obj); + } else if (obj instanceof CTConnector){ + _shape.getCxnSpList().remove(obj); + } else { + throw new IllegalArgumentException("Unsupported shape: " + xShape); + } + return _shapes.remove(xShape); + } + + public String getShapeName(){ + return _shape.getNvGrpSpPr().getCNvPr().getName(); + } + + public int getShapeId(){ + return (int)_shape.getNvGrpSpPr().getCNvPr().getId(); + } + + /** + * @param shapeId 1-based shapeId + */ + static CTGroupShape prototype(int shapeId) { + CTGroupShape ct = CTGroupShape.Factory.newInstance(); + CTGroupShapeNonVisual nvSpPr = ct.addNewNvGrpSpPr(); + CTNonVisualDrawingProps cnv = nvSpPr.addNewCNvPr(); + cnv.setName("Group " + shapeId); + cnv.setId(shapeId + 1); + + nvSpPr.addNewCNvGrpSpPr(); + nvSpPr.addNewNvPr(); + ct.addNewGrpSpPr(); + return ct; + } + + // shape factory methods + private XSLFDrawing getDrawing(){ + if(_drawing == null) { + _drawing = new XSLFDrawing(_sheet, _shape); + } + return _drawing; + } + + public XSLFAutoShape createAutoShape(){ + XSLFAutoShape sh = getDrawing().createAutoShape(); + _shapes.add(sh); + return sh; + } + + public XSLFFreeformShape createFreeform(){ + XSLFFreeformShape sh = getDrawing().createFreeform(); + _shapes.add(sh); + return sh; + } + + public XSLFTextBox createTextBox(){ + XSLFTextBox sh = getDrawing().createTextBox(); + _shapes.add(sh); + return sh; + } + + public XSLFConnectorShape createConnector(){ + XSLFConnectorShape sh = getDrawing().createConnector(); + _shapes.add(sh); + return sh; + } + + public XSLFGroupShape createGroup(){ + XSLFGroupShape sh = getDrawing().createGroup(); + _shapes.add(sh); + return sh; + } + + public XSLFPictureShape createPicture(int pictureIndex){ + + List pics = _sheet.getPackagePart().getPackage() + .getPartsByName(Pattern.compile("/ppt/media/.*?")); + + PackagePart pic = pics.get(pictureIndex); + + PackageRelationship rel = _sheet.getPackagePart().addRelationship( + pic.getPartName(), TargetMode.INTERNAL, XSLFRelation.IMAGES.getRelation()); + + XSLFPictureShape sh = getDrawing().createPicture(rel.getId()); + sh.resize(); + _shapes.add(sh); + return sh; + } + +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureData.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureData.java new file mode 100644 index 000000000..6fd151ff1 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureData.java @@ -0,0 +1,171 @@ +/* + * ==================================================================== + * 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.POIXMLDocumentPart; +import org.apache.poi.POIXMLException; +import org.apache.poi.POIXMLRelation; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.util.Beta; +import org.apache.poi.util.IOUtils; + +import java.io.IOException; + +/** + * Instantiates sub-classes of POIXMLDocumentPart depending on their relationship type + * + * @author Yegor Kozlov + */ +@Beta +public final class XSLFPictureData extends POIXMLDocumentPart { + /** + * Extended windows meta file + */ + public static final int PICTURE_TYPE_EMF = 2; + + /** + * Windows Meta File + */ + public static final int PICTURE_TYPE_WMF = 3; + + /** + * Mac PICT format + */ + public static final int PICTURE_TYPE_PICT = 4; + + /** + * JPEG format + */ + public static final int PICTURE_TYPE_JPEG = 5; + + /** + * PNG format + */ + public static final int PICTURE_TYPE_PNG = 6; + + /** + * Device independent bitmap + */ + public static final int PICTURE_TYPE_DIB = 7; + + /** + * GIF image format + */ + public static final int PICTURE_TYPE_GIF = 8; + + /** + * Relationships for each known picture type + */ + protected static final POIXMLRelation[] RELATIONS; + + static { + RELATIONS = new POIXMLRelation[9]; + RELATIONS[PICTURE_TYPE_EMF] = XSLFRelation.IMAGE_EMF; + RELATIONS[PICTURE_TYPE_WMF] = XSLFRelation.IMAGE_WMF; + RELATIONS[PICTURE_TYPE_PICT] = XSLFRelation.IMAGE_PICT; + RELATIONS[PICTURE_TYPE_JPEG] = XSLFRelation.IMAGE_JPEG; + RELATIONS[PICTURE_TYPE_PNG] = XSLFRelation.IMAGE_PNG; + RELATIONS[PICTURE_TYPE_DIB] = XSLFRelation.IMAGE_DIB; + RELATIONS[PICTURE_TYPE_GIF] = XSLFRelation.IMAGE_GIF; + } + + private Long checksum = null; + + /** + * Create a new XSLFGraphicData node + */ + protected XSLFPictureData() { + super(); + } + + /** + * Construct XSLFPictureData from a package part + * + * @param part the package part holding the drawing data, + * @param rel the package relationship holding this drawing, + * the relationship type must be http://schemas.openxmlformats.org/officeDocument/2006/relationships/image + */ + public XSLFPictureData(PackagePart part, PackageRelationship rel) { + super(part, rel); + } + + /** + * Gets the picture data as a byte array. + *

+ * Note, that this call might be expensive since all the picture data is copied into a temporary byte array. + * You can grab the picture data directly from the underlying package part as follows: + *
+ * + * InputStream is = getPackagePart().getInputStream(); + * + *

+ * + * @return the Picture data. + */ + public byte[] getData() { + try { + return IOUtils.toByteArray(getPackagePart().getInputStream()); + } catch (IOException e) { + throw new POIXMLException(e); + } + } + + /** + * Returns the file name of the image, eg image7.jpg . The original filename + * isn't always available, but if it can be found it's likely to be in the + * CTDrawing + */ + public String getFileName() { + String name = getPackagePart().getPartName().getName(); + if (name == null) + return null; + return name.substring(name.lastIndexOf('/') + 1); + } + + /** + * Suggests a file extension for this image. + * + * @return the file extension. + */ + public String suggestFileExtension() { + return getPackagePart().getPartName().getExtension(); + } + + /** + * Return an integer constant that specifies type of this picture + * + * @return an integer constant that specifies type of this picture + */ + public int getPictureType() { + String contentType = getPackagePart().getContentType(); + for (int i = 0; i < RELATIONS.length; i++) { + if (RELATIONS[i] == null) { + continue; + } + + if (RELATIONS[i].getContentType().equals(contentType)) { + return i; + } + } + return 0; + } + +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java new file mode 100755 index 000000000..c2ce2bd96 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java @@ -0,0 +1,107 @@ +/* + * ==================================================================== + * 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.POIXMLDocumentPart; +import org.apache.poi.sl.usermodel.ShapeContainer; +import org.apache.poi.util.Beta; +import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip; +import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPresetGeometry2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.STShapeType; +import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture; +import org.openxmlformats.schemas.presentationml.x2006.main.CTPictureNonVisual; + +import javax.imageio.ImageIO; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; + +/** + * @author Yegor Kozlov + */ +@Beta +public class XSLFPictureShape extends XSLFSimpleShape { + private XSLFPictureData _data; + + /*package*/ XSLFPictureShape(CTPicture shape, XSLFSheet sheet) { + super(shape, sheet); + } + + + /** + * @param shapeId 1-based shapeId + * @param rel relationship to the picture data in the ooxml package + */ + static CTPicture prototype(int shapeId, String rel) { + CTPicture ct = CTPicture.Factory.newInstance(); + CTPictureNonVisual nvSpPr = ct.addNewNvPicPr(); + CTNonVisualDrawingProps cnv = nvSpPr.addNewCNvPr(); + cnv.setName("Picture " + shapeId); + cnv.setId(shapeId + 1); + nvSpPr.addNewCNvPicPr().addNewPicLocks().setNoChangeAspect(true); + nvSpPr.addNewNvPr(); + + CTBlipFillProperties blipFill = ct.addNewBlipFill(); + CTBlip blip = blipFill.addNewBlip(); + blip.setEmbed(rel); + blipFill.addNewStretch().addNewFillRect(); + + CTShapeProperties spPr = ct.addNewSpPr(); + CTPresetGeometry2D prst = spPr.addNewPrstGeom(); + prst.setPrst(STShapeType.RECT); + prst.addNewAvLst(); + return ct; + } + + /** + * Resize this picture to the default size. + * For PNG and JPEG resizes the image to 100%, + * for other types sets the default size of 200x200 pixels. + */ + public void resize() { + XSLFPictureData pict = getPictureData(); + + try { + BufferedImage img = ImageIO.read(new ByteArrayInputStream(pict.getData())); + setAnchor(new Rectangle2D.Double(0, 0, img.getWidth(), img.getHeight())); + } + catch (Exception e) { + //default size is 200x200 + setAnchor(new java.awt.Rectangle(50, 50, 200, 200)); + } + } + + public XSLFPictureData getPictureData() { + if(_data == null){ + CTPicture ct = (CTPicture)getXmlObject(); + String blipId = ct.getBlipFill().getBlip().getEmbed(); + + for (POIXMLDocumentPart part : getSheet().getRelations()) { + if(part.getPackageRelationship().getId().equals(blipId)){ + _data = (XSLFPictureData)part; + } + } + } + return _data; + } +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFRelation.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFRelation.java index 29cf65809..c546d93df 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFRelation.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFRelation.java @@ -16,14 +16,16 @@ ==================================================================== */ package org.apache.poi.xslf.usermodel; -import java.util.HashMap; -import java.util.Map; - import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.POIXMLRelation; +import org.apache.poi.util.Beta; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; +import java.util.HashMap; +import java.util.Map; + +@Beta public class XSLFRelation extends POIXMLRelation { private static POILogger log = POILogFactory.getLogger(XSLFRelation.class); @@ -78,16 +80,23 @@ public class XSLFRelation extends POIXMLRelation { "application/vnd.openxmlformats-officedocument.presentationml.slide+xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide", "/ppt/slides/slide#.xml", - null + XSLFSlide.class ); public static final XSLFRelation SLIDE_LAYOUT = new XSLFRelation( "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout", "/ppt/slideLayouts/slideLayout#.xml", - null + XSLFSlideLayout.class ); + public static final XSLFRelation SLIDE_MASTER = new XSLFRelation( + "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster", + "/ppt/slideMasters/slideMaster#.xml", + XSLFSlideMaster.class + ); + public static final XSLFRelation COMMENTS = new XSLFRelation( "application/vnd.openxmlformats-officedocument.presentationml.comments+xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments", @@ -108,6 +117,55 @@ public class XSLFRelation extends POIXMLRelation { null ); + public static final XSLFRelation IMAGE_EMF = new XSLFRelation( + "image/x-emf", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + "/ppt/media/image#.emf", + XSLFPictureData.class + ); + public static final XSLFRelation IMAGE_WMF = new XSLFRelation( + "image/x-wmf", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + "/ppt/media/image#.wmf", + XSLFPictureData.class + ); + public static final XSLFRelation IMAGE_PICT = new XSLFRelation( + "image/pict", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + "/ppt/media/image#.pict", + XSLFPictureData.class + ); + public static final XSLFRelation IMAGE_JPEG = new XSLFRelation( + "image/jpeg", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + "/ppt/media/image#.jpeg", + XSLFPictureData.class + ); + public static final XSLFRelation IMAGE_PNG = new XSLFRelation( + "image/png", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + "/ppt/media/image#.png", + XSLFPictureData.class + ); + public static final XSLFRelation IMAGE_DIB = new XSLFRelation( + "image/dib", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + "/ppt/media/image#.dib", + XSLFPictureData.class + ); + public static final XSLFRelation IMAGE_GIF = new XSLFRelation( + "image/gif", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + "/ppt/media/image#.gif", + XSLFPictureData.class + ); + + public static final XSLFRelation IMAGES = new XSLFRelation( + null, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + null, + null + ); private XSLFRelation(String type, String rel, String defaultName, Class cls) { super(type, rel, defaultName, cls); diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java new file mode 100755 index 000000000..b1b65eac4 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java @@ -0,0 +1,48 @@ +/* + * ==================================================================== + * 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.sl.usermodel.Shape; +import org.apache.poi.util.Beta; +import org.apache.xmlbeans.XmlObject; + +import java.awt.geom.Rectangle2D; + +/** + * @author Yegor Kozlov + */ +@Beta +public abstract class XSLFShape { + + + public abstract Rectangle2D getAnchor(); + + public abstract void setAnchor(Rectangle2D anchor); + + public abstract XmlObject getXmlObject(); + + public abstract String getShapeName(); + + public abstract int getShapeId(); + + void onCopy(XSLFSheet srcSheet){ + + } +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java index 3bbd099c0..02ce9fdb7 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java @@ -16,44 +16,199 @@ ==================================================================== */ package org.apache.poi.xslf.usermodel; -import org.apache.poi.sl.usermodel.Background; -import org.apache.poi.sl.usermodel.MasterSheet; -import org.apache.poi.sl.usermodel.Shape; -import org.apache.poi.sl.usermodel.Sheet; -import org.apache.poi.sl.usermodel.SlideShow; +import org.apache.poi.POIXMLDocumentPart; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.TargetMode; +import org.apache.poi.util.Beta; +import org.apache.xmlbeans.XmlObject; +import org.apache.xmlbeans.XmlOptions; +import org.openxmlformats.schemas.officeDocument.x2006.relationships.STRelationshipId; +import org.openxmlformats.schemas.presentationml.x2006.main.CTConnector; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape; +import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture; +import org.openxmlformats.schemas.presentationml.x2006.main.CTShape; -public abstract class XSLFSheet implements Sheet { - private SlideShow slideShow; - protected XSLFSheet(SlideShow parent) { - this.slideShow = parent; +import javax.xml.namespace.QName; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +@Beta +public abstract class XSLFSheet extends POIXMLDocumentPart { + private XSLFDrawing _drawing; + private List _shapes; + private CTGroupShape _spTree; + + public XSLFSheet(){ + super(); + } + + public XSLFSheet(PackagePart part, PackageRelationship rel){ + super(part, rel); + } + + public XMLSlideShow getSlideShow() { + return (XMLSlideShow)getParent(); } - public Background getBackground() { - // TODO Auto-generated method stub - return null; - } + protected List buildShapes(CTGroupShape spTree){ + List shapes = new ArrayList(); + for(XmlObject ch : spTree.selectPath("*")){ + if(ch instanceof CTShape){ // simple shape + XSLFAutoShape shape = XSLFAutoShape.create((CTShape)ch, this); + shapes.add(shape); + } else if (ch instanceof CTGroupShape){ + shapes.add(new XSLFGroupShape((CTGroupShape)ch, this)); + } else if (ch instanceof CTConnector){ + shapes.add(new XSLFConnectorShape((CTConnector)ch, this)); + } else if (ch instanceof CTPicture){ + shapes.add(new XSLFPictureShape((CTPicture)ch, this)); + } else if (ch instanceof CTGraphicalObjectFrame){ + shapes.add(new XSLFGraphicFrame((CTGraphicalObjectFrame)ch, this)); + } + } + return shapes; + } - public MasterSheet getMasterSheet() { - // TODO Auto-generated method stub - return null; - } + public abstract XmlObject getXmlObject(); - public SlideShow getSlideShow() { - return slideShow; - } - public void addShape(Shape shape) { - // TODO Auto-generated method stub + private XSLFDrawing getDrawing(){ + if(_drawing == null) { + _drawing = new XSLFDrawing(this, getSpTree()); + } + return _drawing; + } - } + private List getShapeList(){ + if(_shapes == null){ + _shapes = buildShapes(getSpTree()); + } + return _shapes; + } - public Shape[] getShapes() { - // TODO Auto-generated method stub - return null; - } + // shape factory methods + + public XSLFAutoShape createAutoShape(){ + List shapes = getShapeList(); + XSLFAutoShape sh = getDrawing().createAutoShape(); + shapes.add(sh); + return sh; + } + + public XSLFFreeformShape createFreeform(){ + List shapes = getShapeList(); + XSLFFreeformShape sh = getDrawing().createFreeform(); + shapes.add(sh); + return sh; + } + + public XSLFTextBox createTextBox(){ + List shapes = getShapeList(); + XSLFTextBox sh = getDrawing().createTextBox(); + shapes.add(sh); + return sh; + } + + public XSLFConnectorShape createConnector(){ + List shapes = getShapeList(); + XSLFConnectorShape sh = getDrawing().createConnector(); + shapes.add(sh); + return sh; + } + + public XSLFGroupShape createGroup(){ + List shapes = getShapeList(); + XSLFGroupShape sh = getDrawing().createGroup(); + shapes.add(sh); + return sh; + } + + public XSLFPictureShape createPicture(int pictureIndex){ + List pics = getPackagePart().getPackage() + .getPartsByName(Pattern.compile("/ppt/media/.*?")); + + PackagePart pic = pics.get(pictureIndex); + + PackageRelationship rel = getPackagePart().addRelationship( + pic.getPartName(), TargetMode.INTERNAL, XSLFRelation.IMAGES.getRelation()); + addRelation(rel.getId(), new XSLFPictureData(pic, rel)); + + XSLFPictureShape sh = getDrawing().createPicture(rel.getId()); + sh.resize(); + + getShapeList().add(sh); + return sh; + } + + public XSLFShape[] getShapes(){ + return getShapeList().toArray(new XSLFShape[_shapes.size()]); + } + + public boolean removeShape(XSLFShape xShape) { + XmlObject obj = xShape.getXmlObject(); + CTGroupShape spTree = getSpTree(); + if(obj instanceof CTShape){ + spTree.getSpList().remove(obj); + } else if (obj instanceof CTGroupShape){ + spTree.getGrpSpList().remove(obj); + } else if (obj instanceof CTConnector){ + spTree.getCxnSpList().remove(obj); + } else { + throw new IllegalArgumentException("Unsupported shape: " + xShape); + } + return getShapeList().remove(xShape); + } + + protected abstract String getRootElementName(); + + protected CTGroupShape getSpTree(){ + if(_spTree == null) { + XmlObject root = getXmlObject(); + XmlObject[] sp = root.selectPath( + "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' .//*/p:spTree"); + if(sp.length == 0) throw new IllegalStateException("CTGroupShape was not found"); + _spTree = (CTGroupShape)sp[0]; + } + return _spTree; + } + + protected final void commit() throws IOException { + XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS); + + Map map = new HashMap(); + map.put(STRelationshipId.type.getName().getNamespaceURI(), "r"); + map.put("http://schemas.openxmlformats.org/drawingml/2006/main", "a"); + map.put("http://schemas.openxmlformats.org/presentationml/2006/main", "p"); + xmlOptions.setSaveSuggestedPrefixes(map); + String docName = getRootElementName(); + if(docName != null) { + xmlOptions.setSaveSyntheticDocumentElement( + new QName("http://schemas.openxmlformats.org/presentationml/2006/main", docName)); + } + + PackagePart part = getPackagePart(); + OutputStream out = part.getOutputStream(); + getXmlObject().save(out, xmlOptions); + out.close(); + } + + /** + * Set the contents of this sheet to be a copy of the source sheet. + * + * @param src the source sheet to copy data from + */ + public void copy(XSLFSheet src){ + _shapes = null; + _spTree = null; + _drawing = null; + getXmlObject().set(src.getXmlObject()); + } - public boolean removeShape(Shape shape) { - // TODO Auto-generated method stub - return false; - } } \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java new file mode 100755 index 000000000..3b624dc0a --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java @@ -0,0 +1,268 @@ +/* + * ==================================================================== + * 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.xslf.usermodel.LineCap; +import org.apache.poi.xslf.usermodel.LineDash; +import org.apache.poi.util.Beta; +import org.apache.poi.util.Units; +import org.apache.xmlbeans.XmlObject; +import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPresetLineDashProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor; +import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineCap; +import org.openxmlformats.schemas.drawingml.x2006.main.STPresetLineDashVal; +import org.openxmlformats.schemas.drawingml.x2006.main.STShapeType; + +import java.awt.*; +import java.awt.geom.Rectangle2D; + +/** + * @author Yegor Kozlov + */ +@Beta +public abstract class XSLFSimpleShape extends XSLFShape { + private final XmlObject _shape; + private final XSLFSheet _sheet; + private CTShapeProperties _spPr; + private CTNonVisualDrawingProps _nvPr; + + /*package*/ XSLFSimpleShape(XmlObject shape, XSLFSheet sheet){ + _shape = shape; + _sheet = sheet; + } + + public XmlObject getXmlObject(){ + return _shape; + } + + public XSLFSheet getSheet(){ + return _sheet; + } + /** + * TODO match STShapeType with {@link org.apache.poi.sl.usermodel.ShapeTypes} + */ + public int getShapeType() { + STShapeType.Enum stEnum = getSpPr().getPrstGeom().getPrst(); + return stEnum.intValue(); + } + + public String getShapeName() { + return getNvPr().getName(); + } + + public int getShapeId() { + return (int)getNvPr().getId(); + } + + protected CTNonVisualDrawingProps getNvPr(){ + if(_nvPr == null){ + XmlObject[] rs = _shape.selectPath( + "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' .//*/p:cNvPr"); + if(rs.length != 0) { + _nvPr = (CTNonVisualDrawingProps)rs[0]; + } + } + return _nvPr; + } + + protected CTShapeProperties getSpPr(){ + if(_spPr == null) { + for(XmlObject obj : _shape.selectPath("*")){ + if(obj instanceof CTShapeProperties){ + _spPr = (CTShapeProperties)obj; + } + } + } + if(_spPr == null) { + throw new IllegalStateException("CTShapeProperties was not found."); + } + return _spPr; + } + + public Rectangle2D getAnchor(){ + CTTransform2D xfrm = getSpPr().getXfrm(); + CTPoint2D off = xfrm.getOff(); + long x = off.getX(); + long y = off.getY(); + CTPositiveSize2D ext = xfrm.getExt(); + long cx = ext.getCx(); + long cy = ext.getCy(); + return new Rectangle2D.Double( + Units.toPoints(x), Units.toPoints(y), + Units.toPoints(cx), Units.toPoints(cy)); + } + + public void setAnchor(Rectangle2D anchor){ + CTTransform2D xfrm = getSpPr().isSetXfrm() ? getSpPr().getXfrm() : getSpPr().addNewXfrm(); + CTPoint2D off = xfrm.isSetOff() ? xfrm.getOff() : xfrm.addNewOff(); + long x = Units.toEMU(anchor.getX()); + long y = Units.toEMU(anchor.getY()); + off.setX(x); + off.setY(y); + CTPositiveSize2D ext = xfrm.isSetExt() ? xfrm.getExt() : xfrm.addNewExt(); + long cx = Units.toEMU(anchor.getWidth()); + long cy = Units.toEMU(anchor.getHeight()); + ext.setCx(cx); + ext.setCy(cy); + } + + /** + * Rotate this shape. + *

+ * Positive angles are clockwise (i.e., towards the positive y axis); + * negative angles are counter-clockwise (i.e., towards the negative y axis). + *

+ * + * @param theta the rotation angle in degrees. + */ + public void setRotation(double theta){ + CTTransform2D xfrm = getSpPr().getXfrm(); + xfrm.setRot((int)(theta*60000)); + } + + /** + * Rotation angle in degrees + *

+ * Positive angles are clockwise (i.e., towards the positive y axis); + * negative angles are counter-clockwise (i.e., towards the negative y axis). + *

+ * + * @return rotation angle in degrees + */ + public double getRotation(){ + CTTransform2D xfrm = getSpPr().getXfrm(); + return (double)xfrm.getRot()/60000; + } + + public void setFlipHorizontal(boolean flip){ + CTTransform2D xfrm = getSpPr().getXfrm(); + xfrm.setFlipH(flip); + } + + public void setFlipVertical(boolean flip){ + CTTransform2D xfrm = getSpPr().getXfrm(); + xfrm.setFlipV(flip); + } + /** + * Whether the shape is horizontally flipped + * + * @return whether the shape is horizontally flipped + */ + public boolean getFlipHorizontal(){ + return getSpPr().getXfrm().getFlipH(); + } + + public boolean getFlipVertical(){ + return getSpPr().getXfrm().getFlipV(); + } + + public void setLineColor(Color color){ + CTShapeProperties spPr = getSpPr(); + if(color == null) { + if(spPr.isSetLn() && spPr.getLn().isSetSolidFill()) spPr.getLn().unsetSolidFill(); + } + else { + CTLineProperties ln = spPr.isSetLn() ? spPr.getLn() : spPr.addNewLn(); + + CTSRgbColor rgb = CTSRgbColor.Factory.newInstance(); + rgb.setVal(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()}); + + CTSolidColorFillProperties fill = ln.isSetSolidFill() ? ln.getSolidFill() : ln.addNewSolidFill(); + fill.setSrgbClr(rgb); + } + } + + public Color getLineColor(){ + CTShapeProperties spPr = getSpPr(); + if(!spPr.isSetLn() || !spPr.getLn().isSetSolidFill()) return null; + + CTSRgbColor rgb = spPr.getLn().getSolidFill().getSrgbClr(); + byte[] val = rgb.getVal(); + return new Color(0xFF & val[0], 0xFF & val[1], 0xFF & val[2]); + } + + public void setLineWidth(double width){ + CTShapeProperties spPr = getSpPr(); + if(width == 0.) { + if(spPr.isSetLn()) spPr.getLn().unsetW(); + } + else { + CTLineProperties ln = spPr.isSetLn() ? spPr.getLn() : spPr.addNewLn(); + ln.setW(Units.toEMU(width)); + } + } + + public double getLineWidth(){ + CTShapeProperties spPr = getSpPr(); + CTLineProperties ln = spPr.getLn(); + if(ln == null || !ln.isSetW()) return 0; + + return Units.toPoints(ln.getW()); + } + + public void setLineDash(LineDash dash){ + CTShapeProperties spPr = getSpPr(); + if(dash == null) { + if(spPr.isSetLn()) spPr.getLn().unsetPrstDash(); + } + else { + CTPresetLineDashProperties val = CTPresetLineDashProperties.Factory.newInstance(); + val.setVal(STPresetLineDashVal.Enum.forInt(dash.ordinal() + 1)); + CTLineProperties ln = spPr.isSetLn() ? spPr.getLn() : spPr.addNewLn(); + ln.setPrstDash(val); + } + } + + public LineDash getLineDash(){ + CTShapeProperties spPr = getSpPr(); + CTLineProperties ln = spPr.getLn(); + if(ln == null || !ln.isSetPrstDash()) return null; + + CTPresetLineDashProperties dash = ln.getPrstDash(); + return LineDash.values()[dash.getVal().intValue() - 1]; + } + + public void setLineCap(LineCap cap){ + CTShapeProperties spPr = getSpPr(); + if(cap == null) { + if(spPr.isSetLn()) spPr.getLn().unsetCap(); + } + else { + CTLineProperties ln = spPr.isSetLn() ? spPr.getLn() : spPr.addNewLn(); + ln.setCap(STLineCap.Enum.forInt(cap.ordinal() + 1)); + } + } + + public LineCap getLineCap(){ + CTShapeProperties spPr = getSpPr(); + CTLineProperties ln = spPr.getLn(); + if(ln == null || !ln.isSetCap()) return null; + + return LineCap.values()[ln.getCap().intValue() - 1]; + } + +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlide.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlide.java index 29af4941e..f864344d7 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlide.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlide.java @@ -16,82 +16,124 @@ ==================================================================== */ package org.apache.poi.xslf.usermodel; -import org.apache.poi.sl.usermodel.Notes; +import org.apache.poi.POIXMLDocumentPart; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.PackagePartName; +import org.apache.poi.openxml4j.opc.TargetMode; import org.apache.poi.sl.usermodel.Slide; -import org.apache.poi.sl.usermodel.SlideShow; -import org.apache.poi.util.Internal; +import org.apache.poi.util.Beta; +import org.apache.xmlbeans.XmlException; +import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupTransform2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D; +import org.openxmlformats.schemas.presentationml.x2006.main.CTCommonSlideData; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShapeNonVisual; import org.openxmlformats.schemas.presentationml.x2006.main.CTSlide; -import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideIdListEntry; +import org.openxmlformats.schemas.presentationml.x2006.main.SldDocument; +import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMasterIdListEntry; -public class XSLFSlide extends XSLFSheet implements Slide { - private CTSlide slide; - private CTSlideIdListEntry slideId; - private XSLFCommonSlideData data; - - public XSLFSlide(CTSlide slide, CTSlideIdListEntry slideId, SlideShow parent) { - super(parent); - this.slide = slide; - this.slideId = slideId; - this.data = new XSLFCommonSlideData(slide.getCSld()); - } - - /** - * While developing only! - */ - @Internal - public CTSlide _getCTSlide() { - return slide; - } - /** - * While developing only! - */ - @Internal - public CTSlideIdListEntry _getCTSlideId() { - return slideId; - } - +import java.io.IOException; +import java.util.List; +import java.util.regex.Pattern; - public boolean getFollowMasterBackground() { - // TODO Auto-generated method stub - return false; - } +@Beta +public final class XSLFSlide extends XSLFSheet { + private final CTSlide _slide; + private XSLFSlideLayout _layout; - public boolean getFollowMasterColourScheme() { - // TODO Auto-generated method stub - return false; - } - - public boolean getFollowMasterObjects() { - // TODO Auto-generated method stub - return false; - } - - public Notes getNotes() { - // TODO Auto-generated method stub - return null; - } - - public void setFollowMasterBackground(boolean follow) { - // TODO Auto-generated method stub - - } - - public void setFollowMasterColourScheme(boolean follow) { - // TODO Auto-generated method stub - - } - - public void setFollowMasterObjects(boolean follow) { - // TODO Auto-generated method stub - - } - - public void setNotes(Notes notes) { - // TODO Auto-generated method stub - - } - - public XSLFCommonSlideData getCommonSlideData() { - return data; + /** + * Create a new slide + */ + XSLFSlide() { + super(); + _slide = prototype(); } + + /** + * Construct a SpreadsheetML drawing from a package part + * + * @param part the package part holding the drawing data, + * the content type must be application/vnd.openxmlformats-officedocument.drawing+xml + * @param rel the package relationship holding this drawing, + * the relationship type must be http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing + */ + XSLFSlide(PackagePart part, PackageRelationship rel) throws IOException, XmlException { + super(part, rel); + + SldDocument doc = + SldDocument.Factory.parse(getPackagePart().getInputStream()); + _slide = doc.getSld(); + } + + + private static CTSlide prototype(){ + CTSlide ctSlide = CTSlide.Factory.newInstance(); + CTCommonSlideData cSld = ctSlide.addNewCSld(); + CTGroupShape spTree = cSld.addNewSpTree(); + + CTGroupShapeNonVisual nvGrpSpPr = spTree.addNewNvGrpSpPr(); + CTNonVisualDrawingProps cnvPr = nvGrpSpPr.addNewCNvPr(); + cnvPr.setId(1); + cnvPr.setName(""); + nvGrpSpPr.addNewCNvGrpSpPr(); + nvGrpSpPr.addNewNvPr(); + + CTGroupShapeProperties grpSpr = spTree.addNewGrpSpPr(); + CTGroupTransform2D xfrm = grpSpr.addNewXfrm(); + CTPoint2D off = xfrm.addNewOff(); + off.setX(0); + off.setY(0); + CTPositiveSize2D ext = xfrm.addNewExt(); + ext.setCx(0); + ext.setCy(0); + CTPoint2D choff = xfrm.addNewChOff(); + choff.setX(0); + choff.setY(0); + CTPositiveSize2D chExt = xfrm.addNewChExt(); + chExt.setCx(0); + chExt.setCy(0); + ctSlide.addNewClrMapOvr().addNewMasterClrMapping(); + return ctSlide; + } + + @Override + public CTSlide getXmlObject() { + return _slide; + } + + @Override + protected String getRootElementName(){ + return "sld"; + } + + public XSLFSlideMaster getMasterSheet(){ + return getSlideLayout().getSlideMaster(); + } + + public XSLFSlideLayout getSlideLayout(){ + if(_layout == null){ + for (POIXMLDocumentPart p : getRelations()) { + if (p instanceof XSLFSlideLayout){ + _layout = (XSLFSlideLayout)p; + } + } + } + if(_layout == null) { + throw new IllegalArgumentException("SlideLayout was not found for " + this.toString()); + } + return _layout; + } + + public void setFollowMasterBackground(boolean value){ + _slide.setShowMasterSp(value); + } + + public boolean getFollowMasterBackground(){ + return !_slide.isSetShowMasterSp() || _slide.getShowMasterSp(); + } + } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideLayout.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideLayout.java new file mode 100755 index 000000000..871ece50d --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideLayout.java @@ -0,0 +1,103 @@ +/* ==================================================================== + 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.POIXMLDocumentPart; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.util.Beta; +import org.apache.poi.util.Internal; +import org.apache.xmlbeans.XmlException; +import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideLayout; +import org.openxmlformats.schemas.presentationml.x2006.main.SldLayoutDocument; +import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMaster; + +import java.io.IOException; + +@Beta +public class XSLFSlideLayout extends XSLFSheet { + private CTSlideLayout _layout; + private XSLFSlideMaster _master; + + XSLFSlideLayout() { + super(); + _layout = CTSlideLayout.Factory.newInstance(); + } + + public XSLFSlideLayout(PackagePart part, PackageRelationship rel) throws IOException, XmlException { + super(part, rel); + SldLayoutDocument doc = + SldLayoutDocument.Factory.parse(getPackagePart().getInputStream()); + _layout = doc.getSldLayout(); + } + + + public String getName(){ + return _layout.getCSld().getName(); + } + + /** + * While developing only! + */ + @Internal + public CTSlideLayout getXmlObject() { + return _layout; + } + + @Override + protected String getRootElementName(){ + return "sldLayout"; + } + + /** + * Slide master object associated with this layout. + *

+ * Within a slide master slide are contained all elements + * that describe the objects and their corresponding formatting + * for within a presentation slide. + *

+ *

+ * Within a slide master slide are two main elements. + * The cSld element specifies the common slide elements such as shapes and + * their attached text bodies. Then the txStyles element specifies the + * formatting for the text within each of these shapes. The other properties + * within a slide master slide specify other properties for within a presentation slide + * such as color information, headers and footers, as well as timing and + * transition information for all corresponding presentation slides. + *

+ * + * @return slide master. Never null. + * @throws IllegalStateException if slide master was not found + */ + public XSLFSlideMaster getSlideMaster(){ + if(_master == null){ + for (POIXMLDocumentPart p : getRelations()) { + if (p instanceof XSLFSlideMaster){ + _master = (XSLFSlideMaster)p; + } + } + } + if(_master == null) { + throw new IllegalStateException("SlideMaster was not found for " + this.toString()); + } + return _master; + } + + public XMLSlideShow getSlideShow() { + return (XMLSlideShow)getParent().getParent(); + } +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideMaster.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideMaster.java new file mode 100755 index 000000000..d7c86407a --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideMaster.java @@ -0,0 +1,90 @@ +/* ==================================================================== + 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.POIXMLDocumentPart; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.sl.usermodel.MasterSheet; +import org.apache.poi.util.Beta; +import org.apache.xmlbeans.XmlException; +import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMaster; +import org.openxmlformats.schemas.presentationml.x2006.main.SldMasterDocument; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** +* Slide master object associated with this layout. +*

+* Within a slide master slide are contained all elements +* that describe the objects and their corresponding formatting +* for within a presentation slide. +*

+*

+* Within a slide master slide are two main elements. +* The cSld element specifies the common slide elements such as shapes and +* their attached text bodies. Then the txStyles element specifies the +* formatting for the text within each of these shapes. The other properties +* within a slide master slide specify other properties for within a presentation slide +* such as color information, headers and footers, as well as timing and +* transition information for all corresponding presentation slides. +*

+ * + * @author Yegor Kozlov +*/ +@Beta + public class XSLFSlideMaster extends XSLFSheet { + private CTSlideMaster _slide; + private Map _layouts; + + XSLFSlideMaster() { + super(); + _slide = CTSlideMaster.Factory.newInstance(); + } + + protected XSLFSlideMaster(PackagePart part, PackageRelationship rel) throws IOException, XmlException { + super(part, rel); + SldMasterDocument doc = + SldMasterDocument.Factory.parse(getPackagePart().getInputStream()); + _slide = doc.getSldMaster(); + } + + @Override + public CTSlideMaster getXmlObject() { + return _slide; + } + + @Override + protected String getRootElementName(){ + return "sldMaster"; + } + + public XSLFSlideLayout getLayout(String name){ + if(_layouts == null){ + _layouts = new HashMap(); + for (POIXMLDocumentPart p : getRelations()) { + if (p instanceof XSLFSlideLayout){ + XSLFSlideLayout layout = (XSLFSlideLayout)p; + _layouts.put(layout.getName().toLowerCase(), layout); + } + } + } + return _layouts.get(name); + } +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextBox.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextBox.java new file mode 100755 index 000000000..c009f5492 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextBox.java @@ -0,0 +1,97 @@ +/* + * ==================================================================== + * 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.sl.usermodel.ShapeContainer; +import org.apache.poi.util.Beta; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPresetGeometry2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody; +import org.openxmlformats.schemas.drawingml.x2006.main.STShapeType; +import org.openxmlformats.schemas.presentationml.x2006.main.CTApplicationNonVisualDrawingProps; +import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; +import org.openxmlformats.schemas.presentationml.x2006.main.CTShape; +import org.openxmlformats.schemas.presentationml.x2006.main.CTShapeNonVisual; +import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType; + + +/** + * @author Yegor Kozlov + */ +@Beta +public class XSLFTextBox extends XSLFAutoShape { + + /*package*/ XSLFTextBox(CTShape shape, XSLFSheet sheet){ + super(shape, sheet); + } + + /** + * + * @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("TextBox " + shapeId); + cnv.setId(shapeId + 1); + nvSpPr.addNewCNvSpPr().setTxBox(true); + nvSpPr.addNewNvPr(); + CTShapeProperties spPr = ct.addNewSpPr(); + CTPresetGeometry2D prst = spPr.addNewPrstGeom(); + prst.setPrst(STShapeType.RECT); + prst.addNewAvLst(); + CTTextBody txBody = ct.addNewTxBody(); + txBody.addNewBodyPr(); + txBody.addNewLstStyle(); + + return ct; + } + + /** + * Specifies that the corresponding shape should be represented by the generating application + * as a placeholder. When a shape is considered a placeholder by the generating application + * it can have special properties to alert the user that they may enter content into the shape. + * Different types of placeholders are allowed and can be specified by using the placeholder + * type attribute for this element + * + * @param placeholder + */ + public void setPlaceholder(Placeholder placeholder){ + CTShape sh = (CTShape)getXmlObject(); + CTApplicationNonVisualDrawingProps nv = sh.getNvSpPr().getNvPr(); + if(placeholder == null) { + if(nv.isSetPh()) nv.unsetPh(); + } else { + nv.addNewPh().setType(STPlaceholderType.Enum.forInt(placeholder.ordinal() + 1)); + } + } + + public Placeholder getPlaceholder(){ + CTShape sh = (CTShape)getXmlObject(); + CTPlaceholder ph = sh.getNvSpPr().getNvPr().getPh(); + if(ph == null) return null; + else { + int val = ph.getType().intValue(); + return Placeholder.values()[val - 1]; + } + } +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java new file mode 100755 index 000000000..9317298ee --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java @@ -0,0 +1,334 @@ +/* ==================================================================== + 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.util.Beta; +import org.apache.poi.util.Internal; +import org.apache.poi.util.Units; +import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextSpacing; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextAlignType; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a paragraph of text within the containing text body. + * The paragraph is the highest level text separation mechanism. + * + * @author Yegor Kozlov + * @since POI-3.8 + */ +@Beta +public class XSLFTextParagraph implements Iterable{ + private final CTTextParagraph _p; + private final List _runs; + + XSLFTextParagraph(CTTextParagraph p){ + _p = p; + _runs = new ArrayList(); + for (CTRegularTextRun r : _p.getRList()) { + _runs.add(new XSLFTextRun(r)); + } + } + + public String getText(){ + StringBuilder out = new StringBuilder(); + for (CTRegularTextRun r : _p.getRList()) { + out.append(r.getT()); + } + return out.toString(); + } + + @Internal + public CTTextParagraph getXmlObject(){ + return _p; + } + + public List getTextRuns(){ + return _runs; + } + + public Iterator iterator(){ + return _runs.iterator(); + } + + public XSLFTextRun addNewTextRun(){ + CTRegularTextRun r = _p.addNewR(); + XSLFTextRun run = new XSLFTextRun(r); + _runs.add(run); + return run; + } + + public void addLineBreak(){ + _p.addNewBr(); + } + + /** + * Returns the alignment that is applied to the paragraph. + * + * If this attribute is omitted, then a value of left is implied. + * @return ??? alignment that is applied to the paragraph + */ + public TextAlign getTextAlign(){ + CTTextParagraphProperties pr = _p.getPPr(); + if(pr == null || !pr.isSetAlgn()) return TextAlign.LEFT; + + return TextAlign.values()[pr.getAlgn().intValue() - 1]; + } + + /** + * Specifies the alignment that is to be applied to the paragraph. + * Possible values for this include left, right, centered, justified and distributed, + * see {@link org.apache.poi.xslf.usermodel.TextAlign}. + * + * @param align text align + */ + public void setTextAlign(TextAlign align){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + if(align == null) { + if(pr.isSetAlgn()) pr.unsetAlgn(); + } else { + pr.setAlgn(STTextAlignType.Enum.forInt(align.ordinal() + 1)); + } + } + + /** + * Specifies the indent size that will be applied to the first line of text in the paragraph. + * + * @param value the indent in points. The value of -1 unsets the indent attribute + * from the underlying xml bean. + */ + public void setIndent(double value){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + if(value == -1) { + if(pr.isSetIndent()) pr.unsetIndent(); + } else { + pr.setIndent(Units.toEMU(value)); + } + } + + /** + * + * @return the indent applied to the first line of text in the paragraph. + */ + public double getIndent(){ + CTTextParagraphProperties pr = _p.getPPr(); + if(pr == null || !pr.isSetIndent()) return 0; + + return Units.toPoints(pr.getIndent()); + } + + /** + * Specifies the left margin of the paragraph. This is specified in addition to the text body + * inset and applies only to this text paragraph. That is the text body Inset and the LeftMargin + * attributes are additive with respect to the text position. + * + * @param value the left margin of the paragraph + */ + public void setLeftMargin(double value){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + pr.setMarL(Units.toEMU(value)); + } + + /** + * + * @return the left margin of the paragraph + */ + public double getLeftMargin(){ + CTTextParagraphProperties pr = _p.getPPr(); + if(pr == null || !pr.isSetMarL()) return 0; + + return Units.toPoints(pr.getMarL()); + } + + /** + * This element specifies the vertical line spacing that is to be used within a paragraph. + * This may be specified in two different ways, percentage spacing and font point spacing: + *

+ * If linespacing >= 0, then linespacing is a percentage of normal line height + * If linespacing < 0, the absolute value of linespacing is the spacing in points + *

+ * Examples: + *

+     *      // spacing will be 120% of the size of the largest text on each line
+     *      paragraph.setLineSpacing(120);
+     *
+     *      // spacing will be 200% of the size of the largest text on each line
+     *      paragraph.setLineSpacing(200);
+     *
+     *      // spacing will be 48 points
+     *      paragraph.setLineSpacing(-48.0);
+     * 
+ * + * @param linespacing the vertical line spacing + */ + public void setLineSpacing(double linespacing){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + CTTextSpacing spc = CTTextSpacing.Factory.newInstance(); + if(linespacing >= 0) spc.addNewSpcPct().setVal((int)(linespacing*1000)); + else spc.addNewSpcPts().setVal((int)(-linespacing*100)); + pr.setLnSpc(spc); + } + + /** + * Returns the vertical line spacing that is to be used within a paragraph. + * This may be specified in two different ways, percentage spacing and font point spacing: + *

+ * If linespacing >= 0, then linespacing is a percentage of normal line height. + * If linespacing < 0, the absolute value of linespacing is the spacing in points + *

+ * + * @return the vertical line spacing. + */ + public double getLineSpacing(){ + CTTextParagraphProperties pr = _p.getPPr(); + if(pr == null || !pr.isSetLnSpc()) return 100; // TODO fetch from master + + CTTextSpacing spc = pr.getLnSpc(); + if(spc.isSetSpcPct()) return spc.getSpcPct().getVal()*0.001; + else if (spc.isSetSpcPts()) return -spc.getSpcPts().getVal()*0.01; + else return 100; + } + + /** + * Set the amount of vertical white space that will be present before the paragraph. + * This space is specified in either percentage or points: + *

+ * If spaceBefore >= 0, then space is a percentage of normal line height. + * If spaceBefore < 0, the absolute value of linespacing is the spacing in points + *

+ * Examples: + *

+     *      // The paragraph will be formatted to have a spacing before the paragraph text.
+     *      // The spacing will be 200% of the size of the largest text on each line
+     *      paragraph.setSpaceBefore(200);
+     *
+     *      // The spacing will be a size of 48 points
+     *      paragraph.setSpaceBefore(-48.0);
+     * 
+ * + * @param spaceBefore the vertical white space before the paragraph. + */ + public void setSpaceBefore(double spaceBefore){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + CTTextSpacing spc = CTTextSpacing.Factory.newInstance(); + if(spaceBefore >= 0) spc.addNewSpcPct().setVal((int)(spaceBefore*1000)); + else spc.addNewSpcPts().setVal((int)(-spaceBefore*100)); + pr.setSpcBef(spc); + } + + /** + * The amount of vertical white space before the paragraph + * This may be specified in two different ways, percentage spacing and font point spacing: + *

+ * If spaceBefore >= 0, then space is a percentage of normal line height. + * If spaceBefore < 0, the absolute value of linespacing is the spacing in points + *

+ * + * @return the vertical white space before the paragraph + */ + public double getSpaceBefore(){ + CTTextParagraphProperties pr = _p.getPPr(); + if(pr == null || !pr.isSetSpcBef()) return 0; // TODO fetch from master + + CTTextSpacing spc = pr.getSpcBef(); + if(spc.isSetSpcPct()) return spc.getSpcPct().getVal()*0.001; + else if (spc.isSetSpcPts()) return -spc.getSpcPts().getVal()*0.01; + else return 0; + } + + /** + * Set the amount of vertical white space that will be present after the paragraph. + * This space is specified in either percentage or points: + *

+ * If spaceAfter >= 0, then space is a percentage of normal line height. + * If spaceAfter < 0, the absolute value of linespacing is the spacing in points + *

+ * Examples: + *

+     *      // The paragraph will be formatted to have a spacing after the paragraph text.
+     *      // The spacing will be 200% of the size of the largest text on each line
+     *      paragraph.setSpaceAfter(200);
+     *
+     *      // The spacing will be a size of 48 points
+     *      paragraph.setSpaceAfter(-48.0);
+     * 
+ * + * @param spaceAfter the vertical white space after the paragraph. + */ + public void setSpaceAfter(double spaceAfter){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + CTTextSpacing spc = CTTextSpacing.Factory.newInstance(); + if(spaceAfter >= 0) spc.addNewSpcPct().setVal((int)(spaceAfter*1000)); + else spc.addNewSpcPts().setVal((int)(-spaceAfter*100)); + pr.setSpcAft(spc); + } + + /** + * The amount of vertical white space after the paragraph + * This may be specified in two different ways, percentage spacing and font point spacing: + *

+ * If spaceBefore >= 0, then space is a percentage of normal line height. + * If spaceBefore < 0, the absolute value of linespacing is the spacing in points + *

+ * + * @return the vertical white space after the paragraph + */ + public double getSpaceAfter(){ + CTTextParagraphProperties pr = _p.getPPr(); + if(pr == null || !pr.isSetSpcAft()) return 0; // TODO fetch from master + + CTTextSpacing spc = pr.getSpcAft(); + if(spc.isSetSpcPct()) return spc.getSpcPct().getVal()*0.001; + else if (spc.isSetSpcPts()) return -spc.getSpcPts().getVal()*0.01; + else return 0; + } + + /** + * Specifies the particular level text properties that this paragraph will follow. + * The value for this attribute formats the text according to the corresponding level + * paragraph properties defined in the SlideMaster. + * + * @param level the level (0 ... 4) + */ + public void setLevel(int level){ + CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); + + pr.setLvl(level); + } + + /** + * + * @return the text level of this paragraph. Default is 0. + */ + public int getLevel(){ + CTTextParagraphProperties pr = _p.getPPr(); + if(pr == null) return 0; + + return pr.getLvl(); + + } + + @Override + public String toString(){ + return "[" + getClass() + "]" + getText(); + } +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java new file mode 100755 index 000000000..859649197 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java @@ -0,0 +1,202 @@ +/* ==================================================================== + 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.util.Beta; +import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharacterProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextFont; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextStrikeType; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextUnderlineType; + +import java.awt.*; + +/** + * Represents a run of text within the containing text body. The run element is the + * lowest level text separation mechanism within a text body. + * + * @author Yegor Kozlov + */ +@Beta +public class XSLFTextRun { + private final CTRegularTextRun _r; + + XSLFTextRun(CTRegularTextRun r){ + _r = r; + } + + public String getText(){ + return _r.getT(); + } + + public void setText(String text){ + _r.setT(text); + } + + public CTRegularTextRun getXmlObject(){ + return _r; + } + + public void setFontColor(Color color){ + CTTextCharacterProperties rPr = getRpR(); + CTSolidColorFillProperties fill = rPr.isSetSolidFill() ? rPr.getSolidFill() : rPr.addNewSolidFill(); + CTSRgbColor clr = fill.isSetSrgbClr() ? fill.getSrgbClr() : fill.addNewSrgbClr(); + clr.setVal(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()}); + } + + /** + * + * @param fontSize font size in points. + * The value of -1 unsets the Sz attribyte from the underlying xml bean + */ + public void setFontSize(double fontSize){ + CTTextCharacterProperties rPr = getRpR(); + if(fontSize == -1.0) { + if(rPr.isSetSz()) rPr.unsetSz(); + } else { + rPr.setSz((int)(100*fontSize)); + } + } + + /** + * @return font size in points or -1 if font size is not set. + */ + public double getFontSize(){ + if(!_r.isSetRPr()) return -1; + + return _r.getRPr().getSz()*0.01; + } + + /** + * Specifies the typeface, or name of the font that is to be used for this text run. + * + * @param typeface the font to apply to this text run. + * The value of null unsets the Typeface attrubute from the underlying xml. + */ + public void setFontFamily(String typeface){ + setFontFamily(typeface, (byte)-1, (byte)-1, false); + } + + public void setFontFamily(String typeface, byte charset, byte pictAndFamily, boolean isSymbol){ + CTTextCharacterProperties rPr = getRpR(); + + if(typeface == null){ + if(rPr.isSetLatin()) rPr.unsetLatin(); + if(rPr.isSetCs()) rPr.unsetCs(); + if(rPr.isSetSym()) rPr.unsetSym(); + } else { + if(isSymbol){ + CTTextFont font = rPr.isSetSym() ? rPr.getSym() : rPr.addNewSym(); + font.setTypeface(typeface); + } else { + CTTextFont latin = rPr.isSetLatin() ? rPr.getLatin() : rPr.addNewLatin(); + latin.setTypeface(typeface); + if(charset != -1) latin.setCharset(charset); + if(pictAndFamily != -1) latin.setPitchFamily(pictAndFamily); + } + } + } + + /** + * @return font family or null if niot set + */ + public String getFontFamily(){ + if(!_r.isSetRPr() || !_r.getRPr().isSetLatin()) return null; + + return _r.getRPr().getLatin().getTypeface(); + } + + /** + * Specifies whether a run of text will be formatted as strikethrough text. + * + * @param strike whether a run of text will be formatted as strikethrough text. + */ + public void setStrikethrough(boolean strike){ + getRpR().setStrike(strike ? STTextStrikeType.SNG_STRIKE : STTextStrikeType.NO_STRIKE); + } + + /** + * @return whether a run of text will be formatted as strikethrough text. Default is false. + */ + public boolean isStrikethrough(){ + if(!_r.isSetRPr()) return false; + + return _r.getRPr().getStrike() == STTextStrikeType.SNG_STRIKE; + } + + /** + * Specifies whether this run of text will be formatted as bold text + * + * @param bold whether this run of text will be formatted as bold text + */ + public void setBold(boolean bold){ + getRpR().setB(bold); + } + + /** + * @return whether this run of text is formatted as bold text + */ + public boolean isBold(){ + if(!_r.isSetRPr()) return false; + + return _r.getRPr().getB(); + } + + /** + * @param italic whether this run of text is formatted as italic text + */ + public void setItalic(boolean italic){ + getRpR().setI(italic); + } + + /** + * @return whether this run of text is formatted as italic text + */ + public boolean isItalic(){ + if(!_r.isSetRPr()) return false; + + return _r.getRPr().getI(); + } + + /** + * @param underline whether this run of text is formatted as underlined text + */ + public void setUnderline(boolean underline){ + getRpR().setU(underline ? STTextUnderlineType.SNG : STTextUnderlineType.NONE); + } + + /** + * @return whether this run of text is formatted as underlined text + */ + public boolean isUnderline(){ + if(!_r.isSetRPr() || !_r.getRPr().isSetU()) return false; + + return _r.getRPr().getU() != STTextUnderlineType.NONE; + } + + protected CTTextCharacterProperties getRpR(){ + return _r.isSetRPr() ? _r.getRPr() : _r.addNewRPr(); + } + + @Override + public String toString(){ + return "[" + getClass() + "]" + getText(); + } + +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/XSLFTestDataSamples.java b/src/ooxml/testcases/org/apache/poi/xslf/XSLFTestDataSamples.java index 0b326c0fd..b6966d06e 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/XSLFTestDataSamples.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/XSLFTestDataSamples.java @@ -41,7 +41,7 @@ public class XSLFTestDataSamples { public static XMLSlideShow writeOutAndReadBack(XMLSlideShow doc) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); - doc._getXSLFSlideShow().write(baos); + doc.write(baos); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); return new XMLSlideShow(OPCPackage.open(bais)); } catch (Exception e) { diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java new file mode 100755 index 000000000..69be4b7a6 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java @@ -0,0 +1,267 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import org.apache.poi.util.Units; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextUnderlineType; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextStrikeType; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFAutoShape extends TestCase { + public void testTextBodyProperies() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + + XSLFAutoShape shape = slide.createAutoShape(); + shape.addNewTextParagraph().addNewTextRun().setText("POI"); + + // margins + assertEquals(-1., shape.getMarginBottom()); + assertEquals(-1., shape.getMarginTop()); + assertEquals(-1., shape.getMarginLeft()); + assertEquals(-1., shape.getMarginRight()); + + shape.setMarginBottom(1.0); + assertEquals(1.0, shape.getMarginBottom()); + shape.setMarginTop(2.0); + assertEquals(2.0, shape.getMarginTop()); + shape.setMarginLeft(3.0); + assertEquals(3.0, shape.getMarginLeft()); + shape.setMarginRight(4.0); + assertEquals(4.0, shape.getMarginRight()); + + shape.setMarginBottom(0.0); + assertEquals(0.0, shape.getMarginBottom()); + shape.setMarginTop(0.0); + assertEquals(0.0, shape.getMarginTop()); + shape.setMarginLeft(0.0); + assertEquals(0.0, shape.getMarginLeft()); + shape.setMarginRight(0.0); + assertEquals(0.0, shape.getMarginRight()); + + shape.setMarginBottom(-1); + assertEquals(-1., shape.getMarginBottom()); + shape.setMarginTop(-1); + assertEquals(-1.0, shape.getMarginTop()); + shape.setMarginLeft(-1); + assertEquals(-1.0, shape.getMarginLeft()); + shape.setMarginRight(-1); + assertEquals(-1.0, shape.getMarginRight()); + + // shape + assertFalse(shape.getWordWrap()); + shape.setWordWrap(true); + assertTrue(shape.getWordWrap()); + shape.setWordWrap(false); + assertFalse(shape.getWordWrap()); + + // shape + assertEquals(TextAutofit.NORMAL, shape.getTextAutofit()); + shape.setTextAutofit(TextAutofit.NONE); + assertEquals(TextAutofit.NONE, shape.getTextAutofit()); + shape.setTextAutofit(TextAutofit.SHAPE); + assertEquals(TextAutofit.SHAPE, shape.getTextAutofit()); + shape.setTextAutofit(TextAutofit.NORMAL); + assertEquals(TextAutofit.NORMAL, shape.getTextAutofit()); + + assertEquals(VerticalAlignment.TOP, shape.getVerticalAlignment()); + shape.setVerticalAlignment(VerticalAlignment.BOTTOM); + assertEquals(VerticalAlignment.BOTTOM, shape.getVerticalAlignment()); + shape.setVerticalAlignment(VerticalAlignment.MIDDLE); + assertEquals(VerticalAlignment.MIDDLE, shape.getVerticalAlignment()); + shape.setVerticalAlignment(null); + assertEquals(VerticalAlignment.TOP, shape.getVerticalAlignment()); + + assertEquals(TextDirection.HORIZONTAL, shape.getTextDirection()); + shape.setTextDirection(TextDirection.VERTICAL); + assertEquals(TextDirection.VERTICAL, shape.getTextDirection()); + shape.setTextDirection(null); + assertEquals(TextDirection.HORIZONTAL, shape.getTextDirection()); + } + + public void testTextParagraph() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + assertEquals(0, slide.getShapes().length); + + XSLFAutoShape shape = slide.createAutoShape(); + assertEquals(0, shape.getTextParagraphs().size()); + XSLFTextParagraph p = shape.addNewTextParagraph(); + assertEquals(1, shape.getTextParagraphs().size()); + + assertEquals(0., p.getIndent()); + assertEquals(0., p.getLeftMargin()); + assertEquals(100., p.getLineSpacing()); + assertEquals(0., p.getSpaceAfter()); + assertEquals(0., p.getSpaceBefore()); + assertEquals(0, p.getLevel()); + + p.setIndent(2.0); + assertEquals(2.0, p.getIndent()); + assertTrue(p.getXmlObject().getPPr().isSetIndent()); + p.setIndent(-1); + assertEquals(0.0, p.getIndent()); + assertFalse(p.getXmlObject().getPPr().isSetIndent()); + p.setIndent(10.0); + assertEquals(10., p.getIndent()); + assertTrue(p.getXmlObject().getPPr().isSetIndent()); + + + assertFalse(p.getXmlObject().getPPr().isSetLvl()); + p.setLevel(1); + assertEquals(1, p.getLevel()); + assertTrue(p.getXmlObject().getPPr().isSetLvl()); + p.setLevel(2); + assertEquals(2, p.getLevel()); + + p.setLeftMargin(2.0); + assertEquals(2.0, p.getLeftMargin()); + assertTrue(p.getXmlObject().getPPr().isSetMarL()); + p.setLeftMargin(10.0); + assertEquals(10., p.getLeftMargin()); + assertEquals(Units.toEMU(10), p.getXmlObject().getPPr().getMarL()); + + + assertFalse(p.getXmlObject().getPPr().isSetSpcAft()); + p.setSpaceAfter(200); + assertEquals(200000, p.getXmlObject().getPPr().getSpcAft().getSpcPct().getVal()); + assertFalse(p.getXmlObject().getPPr().getSpcAft().isSetSpcPts()); + p.setSpaceAfter(100); + assertEquals(100000, p.getXmlObject().getPPr().getSpcAft().getSpcPct().getVal()); + assertFalse(p.getXmlObject().getPPr().getSpcAft().isSetSpcPts()); + p.setSpaceAfter(-20); + assertEquals(2000, p.getXmlObject().getPPr().getSpcAft().getSpcPts().getVal()); + assertFalse(p.getXmlObject().getPPr().getSpcAft().isSetSpcPct()); + p.setSpaceAfter(-10); + assertEquals(1000, p.getXmlObject().getPPr().getSpcAft().getSpcPts().getVal()); + assertFalse(p.getXmlObject().getPPr().getSpcAft().isSetSpcPct()); + + assertFalse(p.getXmlObject().getPPr().isSetSpcBef()); + p.setSpaceBefore(200); + assertEquals(200000, p.getXmlObject().getPPr().getSpcBef().getSpcPct().getVal()); + assertFalse(p.getXmlObject().getPPr().getSpcBef().isSetSpcPts()); + p.setSpaceBefore(100); + assertEquals(100000, p.getXmlObject().getPPr().getSpcBef().getSpcPct().getVal()); + assertFalse(p.getXmlObject().getPPr().getSpcBef().isSetSpcPts()); + p.setSpaceBefore(-20); + assertEquals(2000, p.getXmlObject().getPPr().getSpcBef().getSpcPts().getVal()); + assertFalse(p.getXmlObject().getPPr().getSpcBef().isSetSpcPct()); + p.setSpaceBefore(-10); + assertEquals(1000, p.getXmlObject().getPPr().getSpcBef().getSpcPts().getVal()); + assertFalse(p.getXmlObject().getPPr().getSpcBef().isSetSpcPct()); + + assertFalse(p.getXmlObject().getPPr().isSetLnSpc()); + p.setLineSpacing(200); + assertEquals(200000, p.getXmlObject().getPPr().getLnSpc().getSpcPct().getVal()); + assertFalse(p.getXmlObject().getPPr().getLnSpc().isSetSpcPts()); + p.setLineSpacing(100); + assertEquals(100000, p.getXmlObject().getPPr().getLnSpc().getSpcPct().getVal()); + assertFalse(p.getXmlObject().getPPr().getLnSpc().isSetSpcPts()); + p.setLineSpacing(-20); + assertEquals(2000, p.getXmlObject().getPPr().getLnSpc().getSpcPts().getVal()); + assertFalse(p.getXmlObject().getPPr().getLnSpc().isSetSpcPct()); + p.setLineSpacing(-10); + assertEquals(1000, p.getXmlObject().getPPr().getLnSpc().getSpcPts().getVal()); + assertFalse(p.getXmlObject().getPPr().getLnSpc().isSetSpcPct()); + + assertFalse(p.getXmlObject().getPPr().isSetAlgn()); + assertEquals(TextAlign.LEFT, p.getTextAlign()); + p.setTextAlign(TextAlign.LEFT); + assertTrue(p.getXmlObject().getPPr().isSetAlgn()); + assertEquals(TextAlign.LEFT, p.getTextAlign()); + p.setTextAlign(TextAlign.RIGHT); + assertEquals(TextAlign.RIGHT, p.getTextAlign()); + p.setTextAlign(TextAlign.JUSTIFY); + assertEquals(TextAlign.JUSTIFY, p.getTextAlign()); + p.setTextAlign(null); + assertEquals(TextAlign.LEFT, p.getTextAlign()); + assertFalse(p.getXmlObject().getPPr().isSetAlgn()); + } + + public void testTextRun() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + + XSLFAutoShape shape = slide.createAutoShape(); + assertEquals(0, shape.getTextParagraphs().size()); + XSLFTextParagraph p = shape.addNewTextParagraph(); + assertEquals(1, shape.getTextParagraphs().size()); + assertEquals(0, p.getTextRuns().size()); + XSLFTextRun r = p.addNewTextRun(); + assertEquals(1, p.getTextRuns().size()); + assertSame(r, p.getTextRuns().get(0)); + + assertEquals(-1.0, r.getFontSize()); + assertFalse(r.getXmlObject().isSetRPr()); + r.setFontSize(10.0); + assertTrue(r.getXmlObject().isSetRPr()); + assertEquals(1000, r.getXmlObject().getRPr().getSz()); + r.setFontSize(12.5); + assertEquals(1250, r.getXmlObject().getRPr().getSz()); + r.setFontSize(-1); + assertFalse(r.getXmlObject().getRPr().isSetSz()); + + assertFalse(r.getXmlObject().getRPr().isSetLatin()); + assertNull(r.getFontFamily()); + r.setFontFamily(null); + assertNull(r.getFontFamily()); + r.setFontFamily("Arial"); + assertEquals("Arial", r.getFontFamily()); + assertEquals("Arial", r.getXmlObject().getRPr().getLatin().getTypeface()); + r.setFontFamily("Symbol"); + assertEquals("Symbol", r.getFontFamily()); + assertEquals("Symbol", r.getXmlObject().getRPr().getLatin().getTypeface()); + r.setFontFamily(null); + assertNull(r.getFontFamily()); + assertFalse(r.getXmlObject().getRPr().isSetLatin()); + + assertFalse(r.isStrikethrough()); + assertFalse(r.getXmlObject().getRPr().isSetStrike()); + r.setStrikethrough(true); + assertTrue(r.isStrikethrough()); + assertEquals(STTextStrikeType.SNG_STRIKE, r.getXmlObject().getRPr().getStrike()); + + assertFalse(r.isBold()); + assertFalse(r.getXmlObject().getRPr().isSetB()); + r.setBold(true); + assertTrue(r.isBold()); + assertEquals(true, r.getXmlObject().getRPr().getB()); + + assertFalse(r.isItalic()); + assertFalse(r.getXmlObject().getRPr().isSetI()); + r.setItalic(true); + assertTrue(r.isItalic()); + assertEquals(true, r.getXmlObject().getRPr().getI()); + + assertFalse(r.isUnderline()); + assertFalse(r.getXmlObject().getRPr().isSetU()); + r.setUnderline(true); + assertTrue(r.isUnderline()); + assertEquals(STTextUnderlineType.SNG, r.getXmlObject().getRPr().getU()); + + r.setText("Apache"); + assertEquals("Apache", r.getText()); + r.setText("POI"); + assertEquals("POI", r.getText()); + r.setText(null); + assertNull(r.getText()); + } +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFConnectorShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFConnectorShape.java new file mode 100644 index 000000000..8633a326a --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFConnectorShape.java @@ -0,0 +1,117 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import org.apache.poi.util.Units; +import org.apache.poi.xslf.usermodel.LineCap; +import org.apache.poi.xslf.usermodel.LineDash; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineCap; +import org.openxmlformats.schemas.drawingml.x2006.main.STPresetLineDashVal; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineEndType; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineEndWidth; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineEndLength; + +import java.awt.*; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFConnectorShape extends TestCase { + + public void testLineDecorations() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + + XSLFConnectorShape shape = slide.createConnector(); + assertEquals(1, slide.getShapes().length); + + assertFalse(shape.getSpPr().getLn().isSetHeadEnd()); + assertFalse(shape.getSpPr().getLn().isSetTailEnd()); + + // line decorations + assertEquals(LineDecoration.NONE, shape.getLineHeadDecoration()); + assertEquals(LineDecoration.NONE, shape.getLineTailDecoration()); + shape.setLineHeadDecoration(null); + shape.setLineTailDecoration(null); + assertEquals(LineDecoration.NONE, shape.getLineHeadDecoration()); + assertEquals(LineDecoration.NONE, shape.getLineTailDecoration()); + assertFalse(shape.getSpPr().getLn().getHeadEnd().isSetType()); + assertFalse(shape.getSpPr().getLn().getTailEnd().isSetType()); + + shape.setLineHeadDecoration(LineDecoration.ARROW); + shape.setLineTailDecoration(LineDecoration.DIAMOND); + assertEquals(LineDecoration.ARROW, shape.getLineHeadDecoration()); + assertEquals(LineDecoration.DIAMOND, shape.getLineTailDecoration()); + assertEquals(STLineEndType.ARROW, shape.getSpPr().getLn().getHeadEnd().getType()); + assertEquals(STLineEndType.DIAMOND, shape.getSpPr().getLn().getTailEnd().getType()); + + shape.setLineHeadDecoration(LineDecoration.DIAMOND); + shape.setLineTailDecoration(LineDecoration.ARROW); + assertEquals(LineDecoration.DIAMOND, shape.getLineHeadDecoration()); + assertEquals(LineDecoration.ARROW, shape.getLineTailDecoration()); + assertEquals(STLineEndType.DIAMOND, shape.getSpPr().getLn().getHeadEnd().getType()); + assertEquals(STLineEndType.ARROW, shape.getSpPr().getLn().getTailEnd().getType()); + + // line end width + assertEquals(null, shape.getLineHeadWidth()); + assertEquals(null, shape.getLineTailWidth()); + shape.setLineHeadWidth(null); + shape.setLineHeadWidth(null); + assertEquals(null, shape.getLineHeadWidth()); + assertEquals(null, shape.getLineTailWidth()); + assertFalse(shape.getSpPr().getLn().getHeadEnd().isSetW()); + assertFalse(shape.getSpPr().getLn().getTailEnd().isSetW()); + shape.setLineHeadWidth(LineEndWidth.LARGE); + shape.setLineTailWidth(LineEndWidth.MEDIUM); + assertEquals(LineEndWidth.LARGE, shape.getLineHeadWidth()); + assertEquals(LineEndWidth.MEDIUM, shape.getLineTailWidth()); + assertEquals(STLineEndWidth.LG, shape.getSpPr().getLn().getHeadEnd().getW()); + assertEquals(STLineEndWidth.MED, shape.getSpPr().getLn().getTailEnd().getW()); + shape.setLineHeadWidth(LineEndWidth.MEDIUM); + shape.setLineTailWidth(LineEndWidth.LARGE); + assertEquals(LineEndWidth.MEDIUM, shape.getLineHeadWidth()); + assertEquals(LineEndWidth.LARGE, shape.getLineTailWidth()); + assertEquals(STLineEndWidth.MED, shape.getSpPr().getLn().getHeadEnd().getW()); + assertEquals(STLineEndWidth.LG, shape.getSpPr().getLn().getTailEnd().getW()); + + // line end length + assertEquals(null, shape.getLineHeadLength()); + assertEquals(null, shape.getLineTailLength()); + shape.setLineHeadLength(null); + shape.setLineTailLength(null); + assertEquals(null, shape.getLineHeadLength()); + assertEquals(null, shape.getLineTailLength()); + assertFalse(shape.getSpPr().getLn().getHeadEnd().isSetLen()); + assertFalse(shape.getSpPr().getLn().getTailEnd().isSetLen()); + shape.setLineHeadLength(LineEndLength.LARGE); + shape.setLineTailLength(LineEndLength.MEDIUM); + assertEquals(LineEndLength.LARGE, shape.getLineHeadLength()); + assertEquals(LineEndLength.MEDIUM, shape.getLineTailLength()); + assertEquals(STLineEndLength.LG, shape.getSpPr().getLn().getHeadEnd().getLen()); + assertEquals(STLineEndLength.MED, shape.getSpPr().getLn().getTailEnd().getLen()); + shape.setLineHeadLength(LineEndLength.MEDIUM); + shape.setLineTailLength(LineEndLength.LARGE); + assertEquals(LineEndLength.MEDIUM, shape.getLineHeadLength()); + assertEquals(LineEndLength.LARGE, shape.getLineTailLength()); + assertEquals(STLineEndLength.MED, shape.getSpPr().getLn().getHeadEnd().getLen()); + assertEquals(STLineEndLength.LG, shape.getSpPr().getLn().getTailEnd().getLen()); + + } + +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFFreeformShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFFreeformShape.java new file mode 100755 index 000000000..f52cc83dd --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFFreeformShape.java @@ -0,0 +1,50 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFFreeformShape extends TestCase { + + public void testSetPath() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + XSLFFreeformShape shape1 = slide.createFreeform(); + // comples path consisting of a rectangle and an ellipse inside it + GeneralPath path1 = new GeneralPath(new Rectangle(150, 150, 300, 300)); + path1.append(new Ellipse2D.Double(200, 200, 100, 50), false); + shape1.setPath(path1); + + GeneralPath path2 = shape1.getPath(); + + // YK: how to compare the original path1 and the value returned by XSLFFreeformShape.getPath() ? + // one way is to create another XSLFFreeformShape from path2 and compare the resulting xml + assertEquals(path1.getBounds2D(), path2.getBounds2D()); + + XSLFFreeformShape shape2 = slide.createFreeform(); + shape2.setPath(path2); + + assertEquals(shape1.getSpPr().getCustGeom().toString(), shape2.getSpPr().getCustGeom().toString()); + } +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFGroupShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFGroupShape.java new file mode 100755 index 000000000..13e8eb84d --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFGroupShape.java @@ -0,0 +1,99 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import java.awt.*; +import java.awt.geom.Rectangle2D; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFGroupShape extends TestCase { + + public void testCreateShapes() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + + ppt.setPageSize(new Dimension(792, 612)); + + XSLFGroupShape group = slide.createGroup(); + assertEquals(1, slide.getShapes().length); + + Rectangle2D interior = new Rectangle2D.Double(-10, -10, 20, 20); + group.setInteriorAnchor(interior); + assertEquals(interior, group.getInteriorAnchor()); + + Rectangle2D anchor = new Rectangle2D.Double(0, 0, 792, 612); + group.setAnchor(anchor); + assertEquals(anchor, group.getAnchor()); + + assertEquals(0, group.getShapes().length); + + XSLFTextBox shape1 = group.createTextBox(); + assertEquals(1, group.getShapes().length); + assertSame(shape1, group.getShapes()[0]); + assertEquals(3, shape1.getShapeId()); + + XSLFAutoShape shape2 = group.createAutoShape(); + assertEquals(2, group.getShapes().length); + assertSame(shape1, group.getShapes()[0]); + assertSame(shape2, group.getShapes()[1]); + assertEquals(4, shape2.getShapeId()); + + XSLFConnectorShape shape3 = group.createConnector(); + assertEquals(3, group.getShapes().length); + assertSame(shape3, group.getShapes()[2]); + assertEquals(5, shape3.getShapeId()); + + XSLFGroupShape shape4 = group.createGroup(); + assertEquals(4, group.getShapes().length); + assertSame(shape4, group.getShapes()[3]); + assertEquals(6, shape4.getShapeId()); + + group.removeShape(shape2); + assertEquals(3, group.getShapes().length); + assertSame(shape1, group.getShapes()[0]); + assertSame(shape3, group.getShapes()[1]); + assertSame(shape4, group.getShapes()[2]); + + group.removeShape(shape3); + assertEquals(2, group.getShapes().length); + assertSame(shape1, group.getShapes()[0]); + assertSame(shape4, group.getShapes()[1]); + + group.removeShape(shape1); + group.removeShape(shape4); + assertEquals(0, group.getShapes().length); + } + + public void testRemoveShapes() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + + XSLFGroupShape group1 = slide.createGroup(); + group1.createTextBox(); + XSLFGroupShape group2 = slide.createGroup(); + group2.createTextBox(); + XSLFGroupShape group3 = slide.createGroup(); + slide.removeShape(group1); + slide.removeShape(group2); + slide.removeShape(group3); + + } +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFPictureShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFPictureShape.java new file mode 100644 index 000000000..9bd41025f --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFPictureShape.java @@ -0,0 +1,65 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.util.*; +import java.util.List; + +import org.apache.poi.xslf.XSLFTestDataSamples; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFPictureShape extends TestCase { + + public void testCreate() { + XMLSlideShow ppt = new XMLSlideShow(); + assertEquals(0, ppt.getAllPictures().size()); + byte[] data1 = new byte[100]; + int idx1 = ppt.addPicture(data1, XSLFPictureData.PICTURE_TYPE_JPEG); + assertEquals(0, idx1); + assertEquals(1, ppt.getAllPictures().size()); + + XSLFSlide slide = ppt.createSlide(); + XSLFPictureShape shape1 = slide.createPicture(idx1); + assertNotNull(shape1.getPictureData()); + assertTrue(Arrays.equals(data1, shape1.getPictureData().getData())); + + byte[] data2 = new byte[200]; + int idx2 = ppt.addPicture(data2, XSLFPictureData.PICTURE_TYPE_PNG); + XSLFPictureShape shape2 = slide.createPicture(idx2); + assertNotNull(shape2.getPictureData()); + assertEquals(1, idx2); + assertEquals(2, ppt.getAllPictures().size()); + assertTrue(Arrays.equals(data2, shape2.getPictureData().getData())); + + ppt = XSLFTestDataSamples.writeOutAndReadBack(ppt); + List pics = ppt.getAllPictures(); + assertEquals(2, pics.size()); + assertTrue(Arrays.equals(data1, pics.get(0).getData())); + assertTrue(Arrays.equals(data2, pics.get(1).getData())); + + XSLFShape[] shapes = ppt.getSlides()[0].getShapes(); + assertTrue(Arrays.equals(data1, ((XSLFPictureShape)shapes[0]).getPictureData().getData())); + assertTrue(Arrays.equals(data2, ((XSLFPictureShape)shapes[1]).getPictureData().getData())); + } +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFShape.java new file mode 100755 index 000000000..ca3d82f97 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFShape.java @@ -0,0 +1,101 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import org.apache.poi.xslf.XSLFTestDataSamples; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextUnderlineType; + +import java.util.List; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFShape extends TestCase { + + public void testReadTextShapes() { + XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("shapes.pptx"); + XSLFSlide[] slides = ppt.getSlides(); + assertEquals(3, slides.length); + + XSLFSlide slide1 = slides[0]; + XSLFShape[] shapes1 = slide1.getShapes(); + assertEquals(7, shapes1.length); + assertEquals("TextBox 3", shapes1[0].getShapeName()); + XSLFAutoShape sh0 = (XSLFAutoShape) shapes1[0]; + assertEquals("Learning PPTX", sh0.getText()); + List paragraphs0 = sh0.getTextParagraphs(); + assertEquals(1, paragraphs0.size()); + XSLFTextParagraph p0 = paragraphs0.get(0); + assertEquals("Learning PPTX", p0.getText()); + assertEquals(1, p0.getTextRuns().size()); + XSLFTextRun r0 = p0.getTextRuns().get(0); + assertEquals("Learning PPTX", r0.getText()); + + XSLFSlide slide2 = slides[1]; + XSLFShape[] shapes2 = slide2.getShapes(); + assertTrue(shapes2[0] instanceof XSLFAutoShape); + assertEquals("PPTX Title", ((XSLFAutoShape) shapes2[0]).getText()); + XSLFAutoShape sh1 = (XSLFAutoShape) shapes2[0]; + List paragraphs1 = sh1.getTextParagraphs(); + assertEquals(1, paragraphs1.size()); + XSLFTextParagraph p1 = paragraphs1.get(0); + assertEquals("PPTX Title", p1.getText()); + List r2 = paragraphs1.get(0).getTextRuns(); + assertEquals(2, r2.size()); + assertEquals("PPTX ", r2.get(0).getText()); + assertEquals("Title", r2.get(1).getText()); + // Title is underlined + assertEquals(STTextUnderlineType.SNG, r2.get(1).getXmlObject().getRPr().getU()); + + + assertTrue(shapes2[1] instanceof XSLFAutoShape); + assertEquals("Subtitle\nAnd second line", ((XSLFAutoShape) shapes2[1]).getText()); + XSLFAutoShape sh2 = (XSLFAutoShape) shapes2[1]; + List paragraphs2 = sh2.getTextParagraphs(); + assertEquals(2, paragraphs2.size()); + assertEquals("Subtitle", paragraphs2.get(0).getText()); + assertEquals("And second line", paragraphs2.get(1).getText()); + + assertEquals(1, paragraphs2.get(0).getTextRuns().size()); + assertEquals(1, paragraphs2.get(1).getTextRuns().size()); + + assertEquals("Subtitle", paragraphs2.get(0).getTextRuns().get(0).getText()); + assertTrue(paragraphs2.get(0).getTextRuns().get(0).getXmlObject().getRPr().getB()); + assertEquals("And second line", paragraphs2.get(1).getTextRuns().get(0).getText()); + } + + public void testCreateShapes() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + assertEquals(0, slide.getShapes().length); + + XSLFTextBox textBox = slide.createTextBox(); + + assertEquals(1, slide.getShapes().length); + assertSame(textBox, slide.getShapes()[0]); + + assertEquals("", textBox.getText()); + assertEquals(0, textBox.getTextParagraphs().size()); + textBox.addNewTextParagraph().addNewTextRun().setText("Apache"); + textBox.addNewTextParagraph().addNewTextRun().setText("POI"); + assertEquals("Apache\nPOI", textBox.getText()); + assertEquals(2, textBox.getTextParagraphs().size()); + } + +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSheet.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSheet.java new file mode 100644 index 000000000..88af770fe --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSheet.java @@ -0,0 +1,66 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import org.apache.poi.xslf.XSLFTestDataSamples; + +/** + * test common properties for sheets (slides, masters, layouts, etc.) + * + * @author Yegor Kozlov + */ +public class TestXSLFSheet extends TestCase { + public void testCreateShapes(){ + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + assertEquals(0, slide.getShapes().length); + + XSLFSimpleShape shape1 = slide.createAutoShape(); + assertEquals(1, slide.getShapes().length); + assertSame(shape1, slide.getShapes()[0]); + + XSLFTextBox shape2 = slide.createTextBox(); + assertEquals(2, slide.getShapes().length); + assertSame(shape1, slide.getShapes()[0]); + assertSame(shape2, slide.getShapes()[1]); + + XSLFConnectorShape shape3 = slide.createConnector(); + assertEquals(3, slide.getShapes().length); + assertSame(shape1, slide.getShapes()[0]); + assertSame(shape2, slide.getShapes()[1]); + assertSame(shape3, slide.getShapes()[2]); + + XSLFGroupShape shape4 = slide.createGroup(); + assertEquals(4, slide.getShapes().length); + assertSame(shape1, slide.getShapes()[0]); + assertSame(shape2, slide.getShapes()[1]); + assertSame(shape3, slide.getShapes()[2]); + assertSame(shape4, slide.getShapes()[3]); + + ppt = XSLFTestDataSamples.writeOutAndReadBack(ppt); + slide = ppt.getSlides()[0]; + XSLFShape[] shapes = slide.getShapes(); + assertEquals(4, shapes.length); + + assertTrue(shapes[0] instanceof XSLFAutoShape); + assertTrue(shapes[1] instanceof XSLFTextBox); + assertTrue(shapes[2] instanceof XSLFConnectorShape); + assertTrue(shapes[3] instanceof XSLFGroupShape); + } +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java new file mode 100644 index 000000000..d012f99ab --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java @@ -0,0 +1,132 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import org.apache.poi.util.Units; +import org.apache.poi.xslf.usermodel.LineCap; +import org.apache.poi.xslf.usermodel.LineDash; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineCap; +import org.openxmlformats.schemas.drawingml.x2006.main.STPresetLineDashVal; + +import java.awt.*; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFSimpleShape extends TestCase { + public void testLineStyles() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + + XSLFSimpleShape shape = slide.createAutoShape(); + assertEquals(1, slide.getShapes().length); + // line properties are not set by default + assertFalse(shape.getSpPr().isSetLn()); + + assertEquals(0., shape.getLineWidth()); + assertEquals(null, shape.getLineColor()); + assertEquals(null, shape.getLineDash()); + assertEquals(null, shape.getLineCap()); + + shape.setLineWidth(0); + shape.setLineColor(null); + shape.setLineDash(null); + shape.setLineCap(null); + + // still no line properties + assertFalse(shape.getSpPr().isSetLn()); + + // line width + shape.setLineWidth(1.0); + assertEquals(1.0, shape.getLineWidth()); + assertEquals(Units.EMU_PER_POINT, shape.getSpPr().getLn().getW()); + shape.setLineWidth(5.5); + assertEquals(5.5, shape.getLineWidth()); + assertEquals(Units.toEMU(5.5), shape.getSpPr().getLn().getW()); + shape.setLineWidth(0.0); + // setting line width to zero unsets the W attribute + assertFalse(shape.getSpPr().getLn().isSetW()); + + // line cap + shape.setLineCap(LineCap.FLAT); + assertEquals(LineCap.FLAT, shape.getLineCap()); + assertEquals(STLineCap.FLAT, shape.getSpPr().getLn().getCap()); + shape.setLineCap(LineCap.SQUARE); + assertEquals(LineCap.SQUARE, shape.getLineCap()); + assertEquals(STLineCap.SQ, shape.getSpPr().getLn().getCap()); + shape.setLineCap(LineCap.ROUND); + assertEquals(LineCap.ROUND, shape.getLineCap()); + assertEquals(STLineCap.RND, shape.getSpPr().getLn().getCap()); + shape.setLineCap(null); + // setting cap to null unsets the Cap attribute + assertFalse(shape.getSpPr().getLn().isSetCap()); + + // line dash + shape.setLineDash(LineDash.SOLID); + assertEquals(LineDash.SOLID, shape.getLineDash()); + assertEquals(STPresetLineDashVal.SOLID, shape.getSpPr().getLn().getPrstDash().getVal()); + shape.setLineDash(LineDash.DASH_DOT); + assertEquals(LineDash.DASH_DOT, shape.getLineDash()); + assertEquals(STPresetLineDashVal.DASH_DOT, shape.getSpPr().getLn().getPrstDash().getVal()); + shape.setLineDash(LineDash.LG_DASH_DOT); + assertEquals(LineDash.LG_DASH_DOT, shape.getLineDash()); + assertEquals(STPresetLineDashVal.LG_DASH_DOT, shape.getSpPr().getLn().getPrstDash().getVal()); + shape.setLineDash(null); + // setting dash width to null unsets the Dash element + assertFalse(shape.getSpPr().getLn().isSetPrstDash()); + + // line color + assertFalse(shape.getSpPr().getLn().isSetSolidFill()); + shape.setLineColor(Color.RED); + assertEquals(Color.RED, shape.getLineColor()); + assertTrue(shape.getSpPr().getLn().isSetSolidFill()); + shape.setLineColor(Color.BLUE); + assertEquals(Color.BLUE, shape.getLineColor()); + assertTrue(shape.getSpPr().getLn().isSetSolidFill()); + shape.setLineColor(null); + assertEquals(null, shape.getLineColor()); + // setting dash width to null unsets the SolidFill element + assertFalse(shape.getSpPr().getLn().isSetSolidFill()); + } + + public void testFill() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + + XSLFAutoShape shape = slide.createAutoShape(); + // line properties are not set by default + assertFalse(shape.getSpPr().isSetSolidFill()); + + assertNull(shape.getFillColor()); + shape.setFillColor(null); + assertNull(shape.getFillColor()); + assertFalse(shape.getSpPr().isSetSolidFill()); + + shape.setFillColor(Color.RED); + assertEquals(Color.RED, shape.getFillColor()); + shape.setFillColor(Color.DARK_GRAY); + assertEquals(Color.DARK_GRAY, shape.getFillColor()); + assertTrue(shape.getSpPr().isSetSolidFill()); + + shape.setFillColor(null); + assertNull(shape.getFillColor()); + assertFalse(shape.getSpPr().isSetSolidFill()); + } + +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java new file mode 100755 index 000000000..9c7d131d8 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java @@ -0,0 +1,102 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import org.apache.poi.xslf.XSLFTestDataSamples; +import org.apache.poi.POIXMLDocumentPart; + +import java.util.List; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFSlide extends TestCase { + public void testReadShapes(){ + XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("shapes.pptx"); + XSLFSlide[] slides = ppt.getSlides(); + assertEquals(3, slides.length); + + XSLFSlide slide1 = slides[0]; + XSLFShape[] shapes1 = slide1.getShapes(); + assertEquals(7, shapes1.length); + assertEquals("TextBox 3", shapes1[0].getShapeName()); + assertTrue(shapes1[0] instanceof XSLFTextBox); + XSLFAutoShape sh0 = (XSLFAutoShape)shapes1[0]; + assertEquals("Learning PPTX", sh0.getText()); + + + assertEquals("Straight Connector 5", shapes1[1].getShapeName()); + assertTrue(shapes1[1] instanceof XSLFConnectorShape); + + assertEquals("Freeform 6", shapes1[2].getShapeName()); + assertTrue(shapes1[2] instanceof XSLFFreeformShape); + XSLFAutoShape sh2 = (XSLFAutoShape)shapes1[2]; + assertEquals("Cloud", sh2.getText()); + + assertEquals("Picture 1", shapes1[3].getShapeName()); + assertTrue(shapes1[3] instanceof XSLFPictureShape); + + assertEquals("Table 2", shapes1[4].getShapeName()); + assertTrue(shapes1[4] instanceof XSLFGraphicFrame); + + assertEquals("Straight Arrow Connector 7", shapes1[5].getShapeName()); + assertTrue(shapes1[5] instanceof XSLFConnectorShape); + + assertEquals("Elbow Connector 9", shapes1[6].getShapeName()); + assertTrue(shapes1[6] instanceof XSLFConnectorShape); + + // titles on slide2 + XSLFSlide slide2 = slides[1]; + XSLFShape[] shapes2 = slide2.getShapes(); + assertEquals(2, shapes2.length); + assertTrue(shapes2[0] instanceof XSLFAutoShape); + assertEquals("PPTX Title", ((XSLFAutoShape)shapes2[0]).getText()); + assertTrue(shapes2[1] instanceof XSLFAutoShape); + assertEquals("Subtitle\nAnd second line", ((XSLFAutoShape)shapes2[1]).getText()); + + // group shape on slide3 + XSLFSlide slide3 = slides[2]; + XSLFShape[] shapes3 = slide3.getShapes(); + assertEquals(1, shapes3.length); + assertTrue(shapes3[0] instanceof XSLFGroupShape); + XSLFShape[] groupShapes = ((XSLFGroupShape)shapes3[0]).getShapes(); + assertEquals(3, groupShapes.length); + assertTrue(groupShapes[0] instanceof XSLFAutoShape); + assertEquals("Rectangle 1", groupShapes[0].getShapeName()); + + assertTrue(groupShapes[1] instanceof XSLFAutoShape); + assertEquals("Oval 2", groupShapes[1].getShapeName()); + + assertTrue(groupShapes[2] instanceof XSLFAutoShape); + assertEquals("Right Arrow 3", groupShapes[2].getShapeName()); + } + + public void testCreateSlide(){ + XMLSlideShow ppt = new XMLSlideShow(); + assertEquals(0, ppt.getSlides().length); + + XSLFSlide slide = ppt.createSlide(); + assertTrue(slide.getFollowMasterBackground()); + slide.setFollowMasterBackground(false); + assertFalse(slide.getFollowMasterBackground()); + slide.setFollowMasterBackground(true); + assertTrue(slide.getFollowMasterBackground()); + } + +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlideShow.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlideShow.java new file mode 100755 index 000000000..c07493a3f --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlideShow.java @@ -0,0 +1,108 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import java.awt.Dimension; +import java.util.List; + +import org.apache.poi.POIXMLDocumentPart; +import org.apache.poi.xslf.XSLFTestDataSamples; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFSlideShow extends TestCase { + public void testCreateSlide(){ + XMLSlideShow ppt = new XMLSlideShow(); + assertEquals(0, ppt.getSlides().length); + + XSLFSlide slide1 = ppt.createSlide(); + assertEquals(1, ppt.getSlides().length); + assertSame(slide1, ppt.getSlides()[0]); + + List rels = slide1.getRelations(); + assertEquals(1, rels.size()); + assertEquals(slide1.getMasterSheet().getLayout("blank"), rels.get(0)); + + XSLFSlide slide2 = ppt.createSlide(); + assertEquals(2, ppt.getSlides().length); + assertSame(slide2, ppt.getSlides()[1]); + + ppt.setSlideOrder(slide2, 0); + assertSame(slide2, ppt.getSlides()[0]); + assertSame(slide1, ppt.getSlides()[1]); + + ppt = XSLFTestDataSamples.writeOutAndReadBack(ppt); + assertEquals(2, ppt.getSlides().length); + rels = ppt.getSlides()[0].getRelations(); + } + + public void testRemoveSlide(){ + XMLSlideShow ppt = new XMLSlideShow(); + assertEquals(0, ppt.getSlides().length); + + XSLFSlide slide1 = ppt.createSlide(); + XSLFSlide slide2 = ppt.createSlide(); + + assertEquals(2, ppt.getSlides().length); + assertSame(slide1, ppt.getSlides()[0]); + assertSame(slide2, ppt.getSlides()[1]); + + XSLFSlide removedSlide = ppt.removeSlide(0); + assertSame(slide1, removedSlide); + + assertEquals(1, ppt.getSlides().length); + assertSame(slide2, ppt.getSlides()[0]); + + ppt = XSLFTestDataSamples.writeOutAndReadBack(ppt); + assertEquals(1, ppt.getSlides().length); + } + + public void testDimension(){ + XMLSlideShow ppt = new XMLSlideShow(); + Dimension sz = ppt.getPageSize(); + assertEquals(720, sz.width); + assertEquals(540, sz.height); + ppt.setPageSize(new Dimension(792, 612)); + sz = ppt.getPageSize(); + assertEquals(792, sz.width); + assertEquals(612, sz.height); + } + + public void testSlideMasters(){ + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlideMaster[] masters = ppt.getSlideMasters(); + assertEquals(1, masters.length); + + XSLFSlide slide = ppt.createSlide(); + assertSame(masters[0], slide.getMasterSheet()); + } + + public void testSlideLayout(){ + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlideMaster[] masters = ppt.getSlideMasters(); + assertEquals(1, masters.length); + + XSLFSlide slide = ppt.createSlide(); + XSLFSlideLayout layout = slide.getSlideLayout(); + assertNotNull(layout); + + assertSame(masters[0], layout.getSlideMaster()); + } +} diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextBox.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextBox.java new file mode 100644 index 000000000..cc54111a1 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextBox.java @@ -0,0 +1,37 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFTextBox extends TestCase { + + public void testPlaceholder() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + + XSLFTextBox shape = slide.createTextBox(); + assertNull(shape.getPlaceholder()); + shape.setPlaceholder(Placeholder.TITLE); + assertEquals(Placeholder.TITLE, shape.getPlaceholder()); + shape.setPlaceholder(null); + assertNull(shape.getPlaceholder()); + } +} \ No newline at end of file diff --git a/src/resources/scratchpad/org/apache/poi/xslf/usermodel/empty.pptx b/src/resources/scratchpad/org/apache/poi/xslf/usermodel/empty.pptx new file mode 100755 index 0000000000000000000000000000000000000000..eea1e064e93d56146d6b196ecc9c4f8fcba8f1b1 GIT binary patch literal 29356 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

aUN)@@TxMi3xI_3)`k`Cd_$TKr-kZTq0R;NW(pc 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 T9=M!}N(}tufsVjqlyLVqs;n>U literal 0 HcmV?d00001