/* ==================================================================== 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.hslf.model; import static org.apache.poi.sl.TestCommonSL.sameColor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.awt.Color; import java.awt.Dimension; import java.awt.geom.Rectangle2D; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.apache.poi.POIDataSamples; import org.apache.poi.ddf.AbstractEscherOptRecord; import org.apache.poi.ddf.EscherDgRecord; import org.apache.poi.ddf.EscherDggRecord; import org.apache.poi.ddf.EscherProperties; import org.apache.poi.ddf.EscherSimpleProperty; import org.apache.poi.hslf.usermodel.HSLFAutoShape; import org.apache.poi.hslf.usermodel.HSLFGroupShape; import org.apache.poi.hslf.usermodel.HSLFLine; import org.apache.poi.hslf.usermodel.HSLFPictureData; import org.apache.poi.hslf.usermodel.HSLFPictureShape; import org.apache.poi.hslf.usermodel.HSLFShape; import org.apache.poi.hslf.usermodel.HSLFSimpleShape; import org.apache.poi.hslf.usermodel.HSLFSlide; import org.apache.poi.hslf.usermodel.HSLFSlideShow; import org.apache.poi.hslf.usermodel.HSLFTextBox; import org.apache.poi.hslf.usermodel.HSLFTextParagraph; import org.apache.poi.hslf.usermodel.HSLFTextRun; import org.apache.poi.hslf.usermodel.HSLFTextShape; import org.apache.poi.sl.usermodel.PictureData.PictureType; import org.apache.poi.sl.usermodel.ShapeType; import org.apache.poi.sl.usermodel.StrokeStyle.LineDash; import org.junit.Before; import org.junit.Test; /** * Test drawing shapes via Graphics2D */ public final class TestShapes { private static POIDataSamples _slTests = POIDataSamples.getSlideShowInstance(); private HSLFSlideShow ppt; private HSLFSlideShow pptB; @Before public void setUp() throws Exception { InputStream is1 = null, is2 = null; try { is1 = _slTests.openResourceAsStream("empty.ppt"); ppt = new HSLFSlideShow(is1); is2 = _slTests.openResourceAsStream("empty_textbox.ppt"); pptB = new HSLFSlideShow(is2); } finally { is1.close(); is2.close(); } } @Test public void graphics() throws IOException { HSLFSlide slide = ppt.createSlide(); HSLFLine line = new HSLFLine(); java.awt.Rectangle lineAnchor = new java.awt.Rectangle(100, 200, 50, 60); line.setAnchor(lineAnchor); line.setLineWidth(3); line.setLineDash(LineDash.DASH); line.setLineColor(Color.red); slide.addShape(line); HSLFAutoShape ellipse = new HSLFAutoShape(ShapeType.ELLIPSE); Rectangle2D ellipseAnchor = new Rectangle2D.Double(320, 154, 55, 111); ellipse.setAnchor(ellipseAnchor); ellipse.setLineWidth(2); ellipse.setLineDash(LineDash.SOLID); ellipse.setLineColor(Color.green); ellipse.setFillColor(Color.lightGray); slide.addShape(ellipse); ByteArrayOutputStream out = new ByteArrayOutputStream(); ppt.write(out); out.close(); //read ppt from byte array HSLFSlideShow ppt2 = new HSLFSlideShow(new ByteArrayInputStream(out.toByteArray())); assertEquals(1, ppt2.getSlides().size()); slide = ppt2.getSlides().get(0); List<HSLFShape> shape = slide.getShapes(); assertEquals(2, shape.size()); assertTrue(shape.get(0) instanceof HSLFLine); //group shape assertEquals(lineAnchor, shape.get(0).getAnchor()); //group shape assertTrue(shape.get(1) instanceof HSLFAutoShape); //group shape assertEquals(ellipseAnchor, shape.get(1).getAnchor()); //group shape ppt2.close(); } /** * Verify that we can read TextBox shapes * @throws Exception */ @Test public void textBoxRead() throws IOException { ppt = new HSLFSlideShow(_slTests.openResourceAsStream("with_textbox.ppt")); HSLFSlide sl = ppt.getSlides().get(0); for (HSLFShape sh : sl.getShapes()) { assertTrue(sh instanceof HSLFTextBox); HSLFTextBox txtbox = (HSLFTextBox)sh; String text = txtbox.getText(); assertNotNull(text); assertEquals(txtbox.getTextParagraphs().get(0).getTextRuns().size(), 1); HSLFTextRun rt = txtbox.getTextParagraphs().get(0).getTextRuns().get(0); if (text.equals("Hello, World!!!")){ assertEquals(32, rt.getFontSize(), 0); assertTrue(rt.isBold()); assertTrue(rt.isItalic()); } else if (text.equals("I am just a poor boy")){ assertEquals(44, rt.getFontSize(), 0); assertTrue(rt.isBold()); } else if (text.equals("This is Times New Roman")){ assertEquals(16, rt.getFontSize(), 0); assertTrue(rt.isBold()); assertTrue(rt.isItalic()); assertTrue(rt.isUnderlined()); } else if (text.equals("Plain Text")){ assertEquals(18, rt.getFontSize(), 0); } } } @SuppressWarnings("unused") @Test public void testParagraphs() throws IOException { HSLFSlideShow ss = new HSLFSlideShow(); HSLFSlide slide = ss.createSlide(); HSLFTextBox shape = new HSLFTextBox(); HSLFTextRun p1r1 = shape.setText("para 1 run 1. "); HSLFTextRun p1r2 = shape.appendText("para 1 run 2.", false); HSLFTextRun p2r1 = shape.appendText("para 2 run 1. ", true); HSLFTextRun p2r2 = shape.appendText("para 2 run 2. ", false); p1r1.setFontColor(Color.black); p1r2.setFontColor(Color.red); p2r1.setFontColor(Color.yellow); p2r2.setStrikethrough(true); // run 3 has same text properties as run 2 and will be merged when saving HSLFTextRun p2r3 = shape.appendText("para 2 run 3.", false); shape.setAnchor(new Rectangle2D.Double(100,100,100,10)); slide.addShape(shape); shape.resizeToFitText(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ss.write(bos); ss = new HSLFSlideShow(new ByteArrayInputStream(bos.toByteArray())); slide = ss.getSlides().get(0); HSLFTextBox tb = (HSLFTextBox)slide.getShapes().get(0); List<HSLFTextParagraph> para = tb.getTextParagraphs(); HSLFTextRun tr = para.get(0).getTextRuns().get(0); assertEquals("para 1 run 1. ", tr.getRawText()); assertTrue(sameColor(Color.black, tr.getFontColor())); tr = para.get(0).getTextRuns().get(1); assertEquals("para 1 run 2.\r", tr.getRawText()); assertTrue(sameColor(Color.red, tr.getFontColor())); tr = para.get(1).getTextRuns().get(0); assertEquals("para 2 run 1. ", tr.getRawText()); assertTrue(sameColor(Color.yellow, tr.getFontColor())); tr = para.get(1).getTextRuns().get(1); assertEquals("para 2 run 2. para 2 run 3.", tr.getRawText()); assertTrue(sameColor(Color.black, tr.getFontColor())); assertTrue(tr.isStrikethrough()); } /** * Verify that we can add TextBox shapes to a slide * and set some of the style attributes */ @Test public void textBoxWriteBytes() throws IOException { ppt = new HSLFSlideShow(); HSLFSlide sl = ppt.createSlide(); HSLFTextRun rt; String val = "Hello, World!"; // Create a new textbox, and give it lots of properties HSLFTextBox txtbox = new HSLFTextBox(); rt = txtbox.getTextParagraphs().get(0).getTextRuns().get(0); txtbox.setText(val); rt.setFontFamily("Arial"); rt.setFontSize(42d); rt.setBold(true); rt.setItalic(true); rt.setUnderlined(false); rt.setFontColor(Color.red); sl.addShape(txtbox); // Check it before save rt = txtbox.getTextParagraphs().get(0).getTextRuns().get(0); assertEquals(val, rt.getRawText()); assertEquals(42, rt.getFontSize(), 0); assertTrue(rt.isBold()); assertTrue(rt.isItalic()); assertFalse(rt.isUnderlined()); assertEquals("Arial", rt.getFontFamily()); assertTrue(sameColor(Color.red, rt.getFontColor())); // Serialize and read again ByteArrayOutputStream out = new ByteArrayOutputStream(); ppt.write(out); out.close(); HSLFSlideShow ppt2 = new HSLFSlideShow(new ByteArrayInputStream(out.toByteArray())); sl = ppt2.getSlides().get(0); txtbox = (HSLFTextBox)sl.getShapes().get(0); rt = txtbox.getTextParagraphs().get(0).getTextRuns().get(0); // Check after save assertEquals(val, rt.getRawText()); assertEquals(42, rt.getFontSize(), 0); assertTrue(rt.isBold()); assertTrue(rt.isItalic()); assertFalse(rt.isUnderlined()); assertEquals("Arial", rt.getFontFamily()); assertTrue(sameColor(Color.red, rt.getFontColor())); ppt2.close(); } /** * Test with an empty text box */ @Test public void emptyTextBox() { assertEquals(2, pptB.getSlides().size()); HSLFSlide s1 = pptB.getSlides().get(0); HSLFSlide s2 = pptB.getSlides().get(1); // Check we can get the shapes count assertEquals(2, s1.getShapes().size()); assertEquals(2, s2.getShapes().size()); } /** * If you iterate over text shapes in a slide and collect them in a set * it must be the same as returned by Slide.getTextRuns(). */ @Test public void textBoxSet() throws IOException { textBoxSet("with_textbox.ppt"); textBoxSet("basic_test_ppt_file.ppt"); textBoxSet("next_test_ppt_file.ppt"); textBoxSet("Single_Coloured_Page.ppt"); textBoxSet("Single_Coloured_Page_With_Fonts_and_Alignments.ppt"); textBoxSet("incorrect_slide_order.ppt"); } private void textBoxSet(String filename) throws IOException { HSLFSlideShow ss = new HSLFSlideShow(_slTests.openResourceAsStream(filename)); for (HSLFSlide sld : ss.getSlides()) { ArrayList<String> lst1 = new ArrayList<String>(); for (List<HSLFTextParagraph> txt : sld.getTextParagraphs()) { for (HSLFTextParagraph p : txt) { for (HSLFTextRun r : p) { lst1.add(r.getRawText()); } } } ArrayList<String> lst2 = new ArrayList<String>(); for (HSLFShape sh : sld.getShapes()) { if (sh instanceof HSLFTextShape){ HSLFTextShape tbox = (HSLFTextShape)sh; for (HSLFTextParagraph p : tbox.getTextParagraphs()) { for (HSLFTextRun r : p) { lst2.add(r.getRawText()); } } } } assertTrue(lst1.containsAll(lst2)); assertTrue(lst2.containsAll(lst1)); } ss.close(); } /** * Test adding shapes to <code>ShapeGroup</code> */ @Test public void shapeGroup() throws IOException { HSLFSlideShow ss = new HSLFSlideShow(); HSLFSlide slide = ss.createSlide(); Dimension pgsize = ss.getPageSize(); HSLFGroupShape group = new HSLFGroupShape(); group.setAnchor(new Rectangle2D.Double(0, 0, pgsize.getWidth(), pgsize.getHeight())); slide.addShape(group); HSLFPictureData data = ss.addPicture(_slTests.readFile("clock.jpg"), PictureType.JPEG); HSLFPictureShape pict = new HSLFPictureShape(data, group); pict.setAnchor(new Rectangle2D.Double(0, 0, 200, 200)); group.addShape(pict); HSLFLine line = new HSLFLine(group); line.setAnchor(new Rectangle2D.Double(300, 300, 500, 0)); group.addShape(line); //serialize and read again. ByteArrayOutputStream out = new ByteArrayOutputStream(); ss.write(out); out.close(); ss.close(); ByteArrayInputStream is = new ByteArrayInputStream(out.toByteArray()); ss = new HSLFSlideShow(is); is.close(); slide = ss.getSlides().get(0); List<HSLFShape> shape = slide.getShapes(); assertEquals(1, shape.size()); assertTrue(shape.get(0) instanceof HSLFGroupShape); group = (HSLFGroupShape)shape.get(0); List<HSLFShape> grshape = group.getShapes(); assertEquals(2, grshape.size()); assertTrue(grshape.get(0) instanceof HSLFPictureShape); assertTrue(grshape.get(1) instanceof HSLFLine); pict = (HSLFPictureShape)grshape.get(0); assertEquals(new Rectangle2D.Double(0, 0, 200, 200), pict.getAnchor()); line = (HSLFLine)grshape.get(1); assertEquals(new Rectangle2D.Double(300, 300, 500, 0), line.getAnchor()); ss.close(); } /** * Test functionality of Sheet.removeShape(Shape shape) */ @Test public void removeShapes() throws IOException { String file = "with_textbox.ppt"; HSLFSlideShow ss = new HSLFSlideShow(_slTests.openResourceAsStream(file)); HSLFSlide sl = ss.getSlides().get(0); List<HSLFShape> sh = sl.getShapes(); assertEquals("expected four shaped in " + file, 4, sh.size()); //remove all for (int i = 0; i < sh.size(); i++) { boolean ok = sl.removeShape(sh.get(i)); assertTrue("Failed to delete shape #" + i, ok); } //now Slide.getShapes() should return an empty array assertEquals("expected 0 shaped in " + file, 0, sl.getShapes().size()); //serialize and read again. The file should be readable and contain no shapes ByteArrayOutputStream out = new ByteArrayOutputStream(); ss.write(out); out.close(); ss.close(); ss = new HSLFSlideShow(new ByteArrayInputStream(out.toByteArray())); sl = ss.getSlides().get(0); assertEquals("expected 0 shaped in " + file, 0, sl.getShapes().size()); ss.close(); } @Test public void lineWidth() { HSLFSimpleShape sh = new HSLFAutoShape(ShapeType.RT_TRIANGLE); AbstractEscherOptRecord opt = sh.getEscherOptRecord(); EscherSimpleProperty prop = HSLFSimpleShape.getEscherProperty(opt, EscherProperties.LINESTYLE__LINEWIDTH); assertNull(prop); assertEquals(HSLFSimpleShape.DEFAULT_LINE_WIDTH, sh.getLineWidth(), 0); sh.setLineWidth(1.0); prop = HSLFSimpleShape.getEscherProperty(opt, EscherProperties.LINESTYLE__LINEWIDTH); assertNotNull(prop); assertEquals(1.0, sh.getLineWidth(), 0); } @Test public void shapeId() throws IOException { HSLFSlideShow ss = new HSLFSlideShow(); HSLFSlide slide = ss.createSlide(); HSLFShape shape = null; //EscherDgg is a document-level record which keeps track of the drawing groups EscherDggRecord dgg = ss.getDocumentRecord().getPPDrawingGroup().getEscherDggRecord(); EscherDgRecord dg = slide.getSheetContainer().getPPDrawing().getEscherDgRecord(); int dggShapesUsed = dgg.getNumShapesSaved(); //total number of shapes in the ppt int dggMaxId = dgg.getShapeIdMax(); //max number of shapeId int dgMaxId = dg.getLastMSOSPID(); //max shapeId in the slide int dgShapesUsed = dg.getNumShapes(); // number of shapes in the slide //insert 3 shapes and make sure the Ids are properly incremented for (int i = 0; i < 3; i++) { shape = new HSLFLine(); assertEquals(0, shape.getShapeId()); slide.addShape(shape); assertTrue(shape.getShapeId() > 0); //check that EscherDgRecord is updated assertEquals(shape.getShapeId(), dg.getLastMSOSPID()); assertEquals(dgMaxId + 1, dg.getLastMSOSPID()); assertEquals(dgShapesUsed + 1, dg.getNumShapes()); //check that EscherDggRecord is updated assertEquals(shape.getShapeId() + 1, dgg.getShapeIdMax()); assertEquals(dggMaxId + 1, dgg.getShapeIdMax()); assertEquals(dggShapesUsed + 1, dgg.getNumShapesSaved()); dggShapesUsed = dgg.getNumShapesSaved(); dggMaxId = dgg.getShapeIdMax(); dgMaxId = dg.getLastMSOSPID(); dgShapesUsed = dg.getNumShapes(); } //For each drawing group PPT allocates clusters with size=1024 //if the number of shapes is greater that 1024 a new cluster is allocated //make sure it is so int numClusters = dgg.getNumIdClusters(); for (int i = 0; i < 1025; i++) { shape = new HSLFLine(); slide.addShape(shape); } assertEquals(numClusters + 1, dgg.getNumIdClusters()); ss.close(); } @Test public void lineColor() throws IOException { HSLFSlideShow ss = new HSLFSlideShow(_slTests.openResourceAsStream("51731.ppt")); List<HSLFShape> shape = ss.getSlides().get(0).getShapes(); assertEquals(4, shape.size()); HSLFTextShape sh1 = (HSLFTextShape)shape.get(0); assertEquals("Hello Apache POI", sh1.getText()); assertNull(sh1.getLineColor()); HSLFTextShape sh2 = (HSLFTextShape)shape.get(1); assertEquals("Why are you showing this border?", sh2.getText()); assertNull(sh2.getLineColor()); HSLFTextShape sh3 = (HSLFTextShape)shape.get(2); assertEquals("Text in a black border", sh3.getText()); assertEquals(Color.black, sh3.getLineColor()); assertEquals(0.75, sh3.getLineWidth(), 0); HSLFTextShape sh4 = (HSLFTextShape)shape.get(3); assertEquals("Border width is 5 pt", sh4.getText()); assertEquals(Color.black, sh4.getLineColor()); assertEquals(5.0, sh4.getLineWidth(), 0); ss.close(); } }