Index: src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java (working copy) @@ -25,21 +25,13 @@ import org.apache.poi.hslf.*; import org.apache.poi.hslf.model.*; -import org.apache.poi.hslf.record.Document; -import org.apache.poi.hslf.record.DocumentAtom; -import org.apache.poi.hslf.record.FontCollection; -import org.apache.poi.hslf.record.ParentAwareRecord; -import org.apache.poi.hslf.record.Record; -import org.apache.poi.hslf.record.RecordContainer; -import org.apache.poi.hslf.record.RecordTypes; -import org.apache.poi.hslf.record.SlideAtom; -import org.apache.poi.hslf.record.SlideListWithText; -import org.apache.poi.hslf.record.SlidePersistAtom; -import org.apache.poi.hslf.record.UserEditAtom; +import org.apache.poi.hslf.model.Notes; +import org.apache.poi.hslf.model.Slide; import org.apache.poi.hslf.record.SlideListWithText.*; -import org.apache.poi.hslf.record.PersistPtrHolder; -import org.apache.poi.hslf.record.PositionDependentRecord; +import org.apache.poi.hslf.record.*; import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.ddf.*; /** * This class is a friendly wrapper on top of the more scary HSLFSlideShow. @@ -50,6 +42,7 @@ * - handle Slide creation cleaner * * @author Nick Burch + * @author Yegor kozlov */ public class SlideShow @@ -99,8 +92,14 @@ // Build up the model level Slides and Notes buildSlidesAndNotes(); } - - + + /** + * Constructs a new Powerpoint document. + */ + public SlideShow() throws IOException { + this(new HSLFSlideShow()); + } + /** * Find the records that are parent-aware, and tell them * who their parent is @@ -515,7 +514,7 @@ /** * Returns all the pictures attached to the SlideShow */ - public Picture[] getPictures() throws IOException { + public PictureData[] getPictures() { return _hslfSlideShow.getPictures(); } @@ -535,4 +534,88 @@ * Helper method for usermodel: Get the document record */ protected Document getDocumentRecord() { return _documentRecord; } + + /** + * Adds a picture to this presentation and returns the associated index. + * + * @param data picture data + * @param format the format of the picture. One of constans defined in the Picture class. + * @return the index to this picture (1 based). + */ + public int addPicture(byte[] data, int format) { + byte[] uid = PictureData.getChecksum(data); + + EscherContainerRecord bstore; + int offset = 0; + + EscherContainerRecord dggContainer = _documentRecord.getPPDrawingGroup().getDggContainer(); + bstore = (EscherContainerRecord)Shape.getEscherChild(dggContainer, EscherContainerRecord.BSTORE_CONTAINER); + if (bstore == null){ + bstore = new EscherContainerRecord(); + bstore.setRecordId( EscherContainerRecord.BSTORE_CONTAINER); + + List child = dggContainer.getChildRecords(); + for ( int i = 0; i < child.size(); i++ ) { + EscherRecord rec = (EscherRecord)child.get(i); + if (rec.getRecordId() == EscherOptRecord.RECORD_ID){ + child.add(i, bstore); + i++; + } + } + dggContainer.setChildRecords(child); + } else { + List lst = bstore.getChildRecords(); + for ( int i = 0; i < lst.size(); i++ ) { + EscherBSERecord bse = (EscherBSERecord) lst.get(i); + if (Arrays.equals(bse.getUid(), uid)){ + return i + 1; + } + offset += bse.getSize(); + } + } + + EscherBSERecord bse = new EscherBSERecord(); + bse.setRecordId(EscherBSERecord.RECORD_ID); + bse.setOptions( (short) ( 0x0002 | ( format << 4 ) ) ); + bse.setSize(data.length + PictureData.HEADER_SIZE); + bse.setUid(uid); + bse.setBlipTypeMacOS((byte)format); + bse.setBlipTypeWin32((byte)format); + + bse.setRef(1); + bse.setOffset(offset); + + bstore.addChildRecord(bse); + int count = bstore.getChildRecords().size(); + bstore.setOptions((short)( (count << 4) | 0xF )); + + PictureData pict = new PictureData(); + pict.setUID(uid); + pict.setData(data); + pict.setType(format); + + _hslfSlideShow.addPicture(pict); + + return count; + } + + /** + * Adds a picture to this presentation and returns the associated index. + * + * @param pict the file containing the image to add + * @param format the format of the picture. One of constans defined in the Picture class. + * @return the index to this picture (1 based). + */ + public int addPicture(File pict, int format) { + int length = (int)pict.length(); + byte[] data = new byte[length]; + try { + FileInputStream is = new FileInputStream(pict); + is.read(data); + is.close(); + } catch (IOException e){ + throw new RuntimeException(e); + } + return addPicture(data, format); + } } Index: src/scratchpad/src/org/apache/poi/hslf/model/Shape.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/model/Shape.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/model/Shape.java (working copy) @@ -26,27 +26,39 @@ * * @author Yegor Kozlov */ -public class Shape { +public abstract class Shape { public static final int EMU_PER_POINT = 12700; /** - * The parent of the shape + * Either EscherSpContainer or EscheSpgrContainer record + * which holds information about this shape. */ + protected EscherContainerRecord _escherContainer; + + /** + * Parent of this shape. + * null for the topmost shapes. + */ protected Shape _parent; /** - * Either EscherSpContainer or EscheSpgrContainer record - * which holds information about this shape. + * Create a Shape object. This constructor is used when an existing Shape is read from from a PowerPoint document. + * + * @param escherRecord EscherSpContainer container which holds information about this shape + * @param parent the parent of this Shape */ - protected EscherContainerRecord _escherContainer; - - protected Shape(EscherContainerRecord escherRecord, Shape parent){ + protected Shape(EscherContainerRecord escherRecord, Shape parent){ _escherContainer = escherRecord; _parent = parent; - } + } /** + * Creates the lowerlevel escher records for this shape. + */ + protected abstract EscherContainerRecord createSpContainer(boolean isChild); + + /** * @return the parent of this shape */ public Shape getParent(){ @@ -128,17 +140,27 @@ setAnchor(anchor); } - protected static EscherRecord getEscherChild(EscherContainerRecord owner, int recordId){ + /** + * Helper method to return escher child by record ID + * + * @return escher record or null if not found. + */ + public static EscherRecord getEscherChild(EscherContainerRecord owner, int recordId){ for ( Iterator iterator = owner.getChildRecords().iterator(); iterator.hasNext(); ) { EscherRecord escherRecord = (EscherRecord) iterator.next(); if (escherRecord.getRecordId() == recordId) - return (EscherRecord) escherRecord; + return escherRecord; } return null; } - protected static EscherProperty getEscherProperty(EscherOptRecord opt, int propId){ + /** + * Returns escher property by id. + * + * @return escher property or null if not found. + */ + public static EscherProperty getEscherProperty(EscherOptRecord opt, int propId){ for ( Iterator iterator = opt.getEscherProperties().iterator(); iterator.hasNext(); ) { EscherProperty prop = (EscherProperty) iterator.next(); @@ -148,7 +170,14 @@ return null; } - protected static void setEscherProperty(EscherOptRecord opt, short propId, int value){ + /** + * Set an escher property in the opt record. + * + * @param opt The opt record to set the properties to. + * @param propId The id of the property. One of the constants defined in EscherOptRecord. + * @param value value of the property + */ + public static void setEscherProperty(EscherOptRecord opt, short propId, int value){ java.util.List props = opt.getEscherProperties(); for ( Iterator iterator = props.iterator(); iterator.hasNext(); ) { EscherProperty prop = (EscherProperty) iterator.next(); @@ -163,10 +192,10 @@ } /** - * - * @return escher container which holds information about this shape + * @return The shape container and it's children that can represent this + * shape. */ - public EscherContainerRecord getShapeRecord(){ + public EscherContainerRecord getSpContainer(){ return _escherContainer; } } Index: src/scratchpad/src/org/apache/poi/hslf/model/Rectangle.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/model/Rectangle.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/model/Rectangle.java (working copy) @@ -33,15 +33,15 @@ public Rectangle(Shape parent){ super(null, parent); - _escherContainer = create(parent instanceof ShapeGroup); + _escherContainer = createSpContainer(parent instanceof ShapeGroup); } public Rectangle(){ this(null); } - protected EscherContainerRecord create(boolean isChild){ - EscherContainerRecord spcont = super.create(isChild); + protected EscherContainerRecord createSpContainer(boolean isChild){ + EscherContainerRecord spcont = super.createSpContainer(isChild); spcont.setOptions((short)15); EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID); Index: src/scratchpad/src/org/apache/poi/hslf/model/Line.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/model/Line.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/model/Line.java (working copy) @@ -96,16 +96,15 @@ public Line(Shape parent){ super(null, parent); - _escherContainer = create(parent instanceof ShapeGroup); + _escherContainer = createSpContainer(parent instanceof ShapeGroup); } public Line(){ this(null); } - protected EscherContainerRecord create(boolean isChild){ - EscherContainerRecord spcont = super.create(isChild); - spcont.setOptions((short)15); + protected EscherContainerRecord createSpContainer(boolean isChild){ + EscherContainerRecord spcont = super.createSpContainer(isChild); EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID); short type = (ShapeTypes.Line << 4) + 2; Index: src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java (working copy) @@ -158,7 +158,7 @@ EscherContainerRecord dgContainer = (EscherContainerRecord)ppdrawing.getEscherRecords()[0]; EscherContainerRecord spgr = (EscherContainerRecord)Shape.getEscherChild(dgContainer, EscherContainerRecord.SPGR_CONTAINER); - spgr.addChildRecord(shape.getShapeRecord()); + spgr.addChildRecord(shape.getSpContainer()); EscherDgRecord dg = (EscherDgRecord)Shape.getEscherChild(dgContainer, EscherDgRecord.RECORD_ID); dg.setNumShapes(dg.getNumShapes()+1); Index: src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java (working copy) @@ -37,10 +37,10 @@ switch (type){ case ShapeTypes.TextBox: case ShapeTypes.Rectangle: - shape = new Shape(spContainer, parent); + shape = new Rectangle(spContainer, parent); break; case ShapeTypes.PictureFrame: - shape = new Shape(spContainer, parent); + shape = new Picture(spContainer, parent); break; case ShapeTypes.Line: shape = new Line(spContainer, parent); @@ -52,7 +52,7 @@ shape = new ShapeGroup(spContainer, parent); break; default: - shape = new Shape(spContainer, parent); + shape = new SimpleShape(spContainer, parent); break; } return shape; Index: src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java (working copy) @@ -39,10 +39,10 @@ * @param isChild true if the Line is inside a group, false otherwise * @return the record container which holds this shape */ - protected EscherContainerRecord create(boolean isChild) { + protected EscherContainerRecord createSpContainer(boolean isChild) { EscherContainerRecord spContainer = new EscherContainerRecord(); spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); - //spContainer.setOptions((short)15); + spContainer.setOptions((short)15); EscherSpRecord sp = new EscherSpRecord(); int flags = EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE; Index: src/scratchpad/src/org/apache/poi/hslf/model/Ellipse.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/model/Ellipse.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/model/Ellipse.java (working copy) @@ -33,16 +33,15 @@ public Ellipse(Shape parent){ super(null, parent); - _escherContainer = create(parent instanceof ShapeGroup); + _escherContainer = createSpContainer(parent instanceof ShapeGroup); } public Ellipse(){ this(null); } - protected EscherContainerRecord create(boolean isChild){ - EscherContainerRecord spcont = super.create(isChild); - spcont.setOptions((short)15); + protected EscherContainerRecord createSpContainer(boolean isChild){ + EscherContainerRecord spcont = super.createSpContainer(isChild); EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID); short type = (ShapeTypes.Ellipse << 4) + 2; Index: src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java (working copy) @@ -27,13 +27,9 @@ */ public class ShapeGroup extends Shape{ - public ShapeGroup(Shape parent){ - super(null, parent); - _escherContainer = create(); - } - public ShapeGroup(){ - this(null); + this(null, null); + _escherContainer = createSpContainer(false); } protected ShapeGroup(EscherContainerRecord escherRecord, Shape parent){ @@ -91,7 +87,7 @@ /** * Create a new ShapeGroup and create an instance of EscherSpgrContainer which represents a group of shapes */ - protected EscherContainerRecord create() { + protected EscherContainerRecord createSpContainer(boolean isChild) { EscherContainerRecord spgr = new EscherContainerRecord(); spgr.setRecordId(EscherContainerRecord.SPGR_CONTAINER); spgr.setOptions((short)15); @@ -124,7 +120,7 @@ * @param shape - the Shape to add */ public void addShape(Shape shape){ - _escherContainer.addChildRecord(shape.getShapeRecord()); + _escherContainer.addChildRecord(shape.getSpContainer()); } /** Index: src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java (working copy) @@ -60,7 +60,7 @@ public static final Type SorterViewInfo = new Type(1032,null); public static final Type ExObjList = new Type(1033,null); public static final Type ExObjListAtom = new Type(1034,null); - public static final Type PPDrawingGroup = new Type(1035,null); + public static final Type PPDrawingGroup = new Type(1035,PPDrawingGroup.class); public static final Type PPDrawing = new Type(1036,PPDrawing.class); public static final Type NamedShows = new Type(1040,null); public static final Type NamedShow = new Type(1041,null); Index: src/scratchpad/src/org/apache/poi/hslf/record/Document.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/record/Document.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/record/Document.java (working copy) @@ -36,6 +36,7 @@ // Links to our more interesting children private DocumentAtom documentAtom; private Environment environment; + private PPDrawingGroup ppDrawing; private SlideListWithText[] slwts; /** @@ -47,6 +48,13 @@ * settings for the document in it */ public Environment getEnvironment() { return environment; } + + /** + * + * @return PPDrawingGroup container + */ + public PPDrawingGroup getPPDrawingGroup() { return ppDrawing; } + /** * Returns all the SlideListWithTexts that are defined for * this Document. They hold the text, and some of the text @@ -82,6 +90,9 @@ if(_children[i] instanceof Environment) { environment = (Environment)_children[i]; } + if(_children[i] instanceof PPDrawingGroup) { + ppDrawing = (PPDrawingGroup)_children[i]; + } } // Now grab them all slwts = new SlideListWithText[slwtcount]; Index: src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java =================================================================== --- src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java (revision 388557) +++ src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java (working copy) @@ -36,7 +36,7 @@ import org.apache.poi.util.LittleEndian; import org.apache.poi.hslf.record.*; -import org.apache.poi.hslf.usermodel.Picture; +import org.apache.poi.hslf.usermodel.PictureData; /** * This class contains the main functionality for the Powerpoint file @@ -61,6 +61,9 @@ // Low level contents private Record[] _records; + //pictures + private PictureData[] _pictures; + /** * Constructs a Powerpoint document from fileName. Parses the document * and places all the important stuff into data structures. @@ -104,9 +107,20 @@ // Look for Property Streams: readProperties(); + + //read pictures + readPictures(); } + /** + * Constructs a new Powerpoint document. + */ + public HSLFSlideShow() throws IOException + { + this(HSLFSlideShow.class.getResourceAsStream("/org/apache/poi/hslf/data/empty.ppt")); + } + /** * Shuts things down. Closes underlying streams etc * @@ -288,6 +302,15 @@ currentUser.writeToFS(outFS); + //write pictures + if (_pictures != null) { + ByteArrayOutputStream pict = new ByteArrayOutputStream(); + for (int i = 0; i < _pictures.length; i++ ) { + _pictures[i].write(pict); + } + outFS.createDocument(new ByteArrayInputStream(pict.toByteArray()), "Pictures"); + } + // Send the POIFSFileSystem object out to the underlying stream outFS.writeFilesystem(out); } @@ -371,7 +394,7 @@ * @return array with the read pictures ot null if the * presentation doesn't contain pictures. */ - public Picture[] getPictures() throws IOException { + private void readPictures() throws IOException { byte[] pictstream; try { @@ -381,17 +404,39 @@ is.read(pictstream); } catch (FileNotFoundException e){ //silently catch exceptions if the presentation doesn't contain pictures - return null; + return; } ArrayList p = new ArrayList(); int pos = 0; while (pos < pictstream.length) { - Picture pict = new Picture(pictstream, pos); + PictureData pict = new PictureData(pictstream, pos); p.add(pict); - pos += Picture.HEADER_SIZE + pict.getSize(); + pos += PictureData.HEADER_SIZE + pict.getSize(); } - return (Picture[])p.toArray(new Picture[p.size()]); + _pictures = (PictureData[])p.toArray(new PictureData[p.size()]); } + + /** + * Return array of pictures contained in this presentation + * + * @return array with the read pictures ot null if the + * presentation doesn't contain pictures. + */ + public PictureData[] getPictures() { + return _pictures; + } + + /** + * Add a new picture to this presentation. + */ + public void addPicture(PictureData img) { + PictureData[] lst = new PictureData[_pictures == null ? 1: (_pictures.length + 1)]; + if(_pictures != null ) for (int i = 0; i < _pictures.length; i++){ + lst[i] = _pictures[i]; + } + lst[lst.length - 1] = img; + _pictures = lst; + } } Index: src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java =================================================================== --- src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java (revision 388557) +++ src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java (working copy) @@ -17,11 +17,19 @@ package org.apache.poi.hslf.usermodel; import org.apache.poi.hslf.*; +import org.apache.poi.hslf.usermodel.PictureData; +import org.apache.poi.hslf.usermodel.SlideShow; +import org.apache.poi.hslf.model.Slide; +import org.apache.poi.hslf.model.Shape; +import org.apache.poi.hslf.model.Picture; +import org.apache.poi.util.LittleEndian; import junit.framework.TestCase; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; /** * Test extracting images from a ppt file @@ -29,19 +37,61 @@ * @author Yegor Kozlov */ public class TestPictures extends TestCase{ + public static String dirname = System.getProperty("HSLF.testdata.path"); + public static String filename = dirname + "/ppt_with_png.ppt"; - public void testPictures() throws Exception { - String dirname = System.getProperty("HSLF.testdata.path"); - String filename = dirname + "/ppt_with_png.ppt"; + public void testReadPictures() throws Exception { HSLFSlideShow ppt = new HSLFSlideShow(filename); - Picture[] pict = ppt.getPictures(); + PictureData[] pict = ppt.getPictures(); assertNotNull(pict); for (int i = 0; i < pict.length; i++) { byte[] data = pict[i].getData(); + BufferedImage img = ImageIO.read(new ByteArrayInputStream(data)); assertNotNull(img); } ppt.close(); } + + public void testSerializePictures() throws Exception { + HSLFSlideShow ppt = new HSLFSlideShow(filename); + PictureData[] pict = ppt.getPictures(); + assertNotNull(pict); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ppt.write(out); + out.close(); + + ppt = new HSLFSlideShow(new ByteArrayInputStream(out.toByteArray())); + pict = ppt.getPictures(); + assertNotNull(pict); + } + + public void testAddPictures() throws Exception { + int idx; + Slide slide; + Picture pict; + + SlideShow ppt = new SlideShow(); + + idx = ppt.addPicture(new File(dirname + "/clock.jpg"), Picture.JPEG); + slide = ppt.createSlide(); + pict = new Picture(idx); + pict.setDefaultSize(ppt); + slide.addShape(pict); + + idx = ppt.addPicture(new File(dirname + "/painting.png"), Picture.PNG); + pict = new Picture(idx); + pict.setDefaultSize(ppt); + slide.addShape(pict); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ppt.write(out); + out.close(); + + ppt = new SlideShow(new HSLFSlideShow(new ByteArrayInputStream(out.toByteArray()))); + assertTrue(ppt.getPictures().length == 2 ); + } + } Index: src/scratchpad/testcases/org/apache/poi/hslf/data/empty.ppt =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream