diff --git a/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java index ed2de1af2..f6bd2cf98 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/HSLFSlideShow.java @@ -23,7 +23,6 @@ import java.util.*; import java.io.*; import org.apache.poi.poifs.filesystem.POIFSFileSystem; -import org.apache.poi.poifs.filesystem.POIFSDocument; import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.DocumentInputStream; @@ -33,10 +32,8 @@ import org.apache.poi.hpsf.MutablePropertySet; import org.apache.poi.hpsf.SummaryInformation; import org.apache.poi.hpsf.DocumentSummaryInformation; -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 @@ -47,78 +44,91 @@ import org.apache.poi.hslf.usermodel.Picture; public class HSLFSlideShow { - private InputStream istream; - private POIFSFileSystem filesystem; + private InputStream istream; + private POIFSFileSystem filesystem; - // Holds metadata on our document - private SummaryInformation sInf; - private DocumentSummaryInformation dsInf; - private CurrentUserAtom currentUser; + // Holds metadata on our document + private SummaryInformation sInf; + private DocumentSummaryInformation dsInf; + private CurrentUserAtom currentUser; - // Low level contents of the file - private byte[] _docstream; + // Low level contents of the file + private byte[] _docstream; - // Low level contents - private Record[] _records; + // Low level contents + private Record[] _records; - /** - * Constructs a Powerpoint document from fileName. Parses the document - * and places all the important stuff into data structures. - * - * @param fileName The name of the file to read. - * @throws IOException if there is a problem while parsing the document. - */ - public HSLFSlideShow(String fileName) throws IOException - { - this(new FileInputStream(fileName)); - } + // Raw Pictures contained in the pictures stream + private PictureData[] _pictures; + + /** + * Constructs a Powerpoint document from fileName. Parses the document + * and places all the important stuff into data structures. + * + * @param fileName The name of the file to read. + * @throws IOException if there is a problem while parsing the document. + */ + public HSLFSlideShow(String fileName) throws IOException + { + this(new FileInputStream(fileName)); + } - /** - * Constructs a Powerpoint document from an input stream. Parses the - * document and places all the important stuff into data structures. - * - * @param inputStream the source of the data - * @throws IOException if there is a problem while parsing the document. - */ - public HSLFSlideShow(InputStream inputStream) throws IOException - { - //do Ole stuff + /** + * Constructs a Powerpoint document from an input stream. Parses the + * document and places all the important stuff into data structures. + * + * @param inputStream the source of the data + * @throws IOException if there is a problem while parsing the document. + */ + public HSLFSlideShow(InputStream inputStream) throws IOException + { + //do Ole stuff this(new POIFSFileSystem(inputStream)); - istream = inputStream; - } + istream = inputStream; + } - /** - * Constructs a Powerpoint document from a POIFS Filesystem. Parses the - * document and places all the important stuff into data structures. - * - * @param filesystem the POIFS FileSystem to read from - * @throws IOException if there is a problem while parsing the document. - */ - public HSLFSlideShow(POIFSFileSystem filesystem) throws IOException - { + /** + * Constructs a Powerpoint document from a POIFS Filesystem. Parses the + * document and places all the important stuff into data structures. + * + * @param filesystem the POIFS FileSystem to read from + * @throws IOException if there is a problem while parsing the document. + */ + public HSLFSlideShow(POIFSFileSystem filesystem) throws IOException + { this.filesystem = filesystem; - // Go find a PowerPoint document in the stream - // Save anything useful we come across - readFIB(); + // Go find a PowerPoint document in the stream + // Save anything useful we come across + readFIB(); // Look for Property Streams: readProperties(); - } - - /** - * Shuts things down. Closes underlying streams etc - * - * @throws IOException - */ - public void close() throws IOException - { - if(istream != null) { - istream.close(); + // Look for Picture Streams: + readPictures(); + } + + /** + * Constructs a new, empty, Powerpoint document. + */ + public HSLFSlideShow() throws IOException + { + this(HSLFSlideShow.class.getResourceAsStream("/org/apache/poi/hslf/data/empty.ppt")); + } + + /** + * Shuts things down. Closes underlying streams etc + * + * @throws IOException + */ + public void close() throws IOException + { + if(istream != null) { + istream.close(); + } + filesystem = null; } - filesystem = null; - } /** @@ -175,24 +185,52 @@ public class HSLFSlideShow } - /** - * Find the properties from the filesystem, and load them - */ - public void readProperties() { - // DocumentSummaryInformation - dsInf = (DocumentSummaryInformation)getPropertySet("\005DocumentSummaryInformation"); + /** + * Find the properties from the filesystem, and load them + */ + public void readProperties() { + // DocumentSummaryInformation + dsInf = (DocumentSummaryInformation)getPropertySet("\005DocumentSummaryInformation"); - // SummaryInformation - sInf = (SummaryInformation)getPropertySet("\005SummaryInformation"); + // SummaryInformation + sInf = (SummaryInformation)getPropertySet("\005SummaryInformation"); - // Current User - try { - currentUser = new CurrentUserAtom(filesystem); - } catch(IOException ie) { - System.err.println("Error finding Current User Atom:\n" + ie); - currentUser = new CurrentUserAtom(); + // Current User + try { + currentUser = new CurrentUserAtom(filesystem); + } catch(IOException ie) { + System.err.println("Error finding Current User Atom:\n" + ie); + currentUser = new CurrentUserAtom(); + } + } + + /** + * Find and read in pictures contained in this presentation + */ + private void readPictures() throws IOException { + byte[] pictstream; + + try { + DocumentEntry entry = (DocumentEntry)filesystem.getRoot().getEntry("Pictures"); + pictstream = new byte[entry.getSize()]; + DocumentInputStream is = filesystem.createDocumentInputStream("Pictures"); + is.read(pictstream); + } catch (FileNotFoundException e){ + // Silently catch exceptions if the presentation doesn't + // contain pictures - will use a null set instead + return; + } + + ArrayList p = new ArrayList(); + int pos = 0; + while (pos < pictstream.length) { + PictureData pict = new PictureData(pictstream, pos); + p.add(pict); + pos += PictureData.HEADER_SIZE + pict.getSize(); + } + + _pictures = (PictureData[])p.toArray(new PictureData[p.size()]); } - } /** @@ -287,6 +325,17 @@ public class HSLFSlideShow currentUser.setCurrentEditOffset(newLastUserEditAtomPos.intValue()); currentUser.writeToFS(outFS); + + // Write any pictures, into another stream + 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); @@ -311,87 +360,86 @@ public class HSLFSlideShow } - /* ******************* fetching methods follow ********************* */ - - - /** - * Returns an array of all the records found in the slideshow - */ - public Record[] getRecords() { return _records; } - - /** - * Adds a new root level record, at the end, but before the last - * PersistPtrIncrementalBlock. - */ - public synchronized int appendRootLevelRecord(Record newRecord) { - int addedAt = -1; - Record[] r = new Record[_records.length+1]; - boolean added = false; - for(int i=(_records.length-1); i>=0; i--) { - if(added) { - // Just copy over - r[i] = _records[i]; - } else { - r[(i+1)] = _records[i]; - if(_records[i] instanceof PersistPtrHolder) { - r[i] = newRecord; - added = true; - addedAt = i; - } - } - } - _records = r; - return addedAt; - } - - /** - * Returns an array of the bytes of the file. Only correct after a - * call to open or write - at all other times might be wrong! - */ - public byte[] getUnderlyingBytes() { return _docstream; } - - /** - * Fetch the Document Summary Information of the document - */ - public DocumentSummaryInformation getDocumentSummaryInformation() { return dsInf; } - - /** - * Fetch the Summary Information of the document - */ - public SummaryInformation getSummaryInformation() { return sInf; } - - /** - * Fetch the Current User Atom of the document - */ - public CurrentUserAtom getCurrentUserAtom() { return currentUser; } + /* ******************* adding methods follow ********************* */ /** - * Read pictures contained in this presentation + * Adds a new root level record, at the end, but before the last + * PersistPtrIncrementalBlock. + */ + public synchronized int appendRootLevelRecord(Record newRecord) { + int addedAt = -1; + Record[] r = new Record[_records.length+1]; + boolean added = false; + for(int i=(_records.length-1); i>=0; i--) { + if(added) { + // Just copy over + r[i] = _records[i]; + } else { + r[(i+1)] = _records[i]; + if(_records[i] instanceof PersistPtrHolder) { + r[i] = newRecord; + added = true; + addedAt = i; + } + } + } + _records = r; + return addedAt; + } + + /** + * Add a new picture to this presentation. + */ + public void addPicture(PictureData img) { + // Copy over the existing pictures, into an array one bigger + PictureData[] lst; + if(_pictures == null) { + lst = new PictureData[1]; + } else { + lst = new PictureData[(_pictures.length+1)]; + System.arraycopy(_pictures,0,lst,0,_pictures.length); + } + // Add in the new image + lst[lst.length - 1] = img; + _pictures = lst; + } + + /* ******************* fetching methods follow ********************* */ + + + /** + * Returns an array of all the records found in the slideshow + */ + public Record[] getRecords() { return _records; } + + /** + * Returns an array of the bytes of the file. Only correct after a + * call to open or write - at all other times might be wrong! + */ + public byte[] getUnderlyingBytes() { return _docstream; } + + /** + * Fetch the Document Summary Information of the document + */ + public DocumentSummaryInformation getDocumentSummaryInformation() { return dsInf; } + + /** + * Fetch the Summary Information of the document + */ + public SummaryInformation getSummaryInformation() { return sInf; } + + /** + * Fetch the Current User Atom of the document + */ + public CurrentUserAtom getCurrentUserAtom() { return currentUser; } + + /** + * Return array of pictures contained in this presentation * - * @return array with the read pictures ot null if the + * @return array with the read pictures or null if the * presentation doesn't contain pictures. */ - public Picture[] getPictures() throws IOException { - byte[] pictstream; - - try { - DocumentEntry entry = (DocumentEntry)filesystem.getRoot().getEntry("Pictures"); - pictstream = new byte[entry.getSize()]; - DocumentInputStream is = filesystem.createDocumentInputStream("Pictures"); - is.read(pictstream); - } catch (FileNotFoundException e){ - //silently catch exceptions if the presentation doesn't contain pictures - return null; - } - - ArrayList p = new ArrayList(); - int pos = 0; - while (pos < pictstream.length) { - Picture pict = new Picture(pictstream, pos); - p.add(pict); - pos += Picture.HEADER_SIZE + pict.getSize(); - } - - return (Picture[])p.toArray(new Picture[p.size()]); + public PictureData[] getPictures() { + return _pictures; } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/data/empty.ppt b/src/scratchpad/src/org/apache/poi/hslf/data/empty.ppt new file mode 100644 index 000000000..23e1e94ca Binary files /dev/null and b/src/scratchpad/src/org/apache/poi/hslf/data/empty.ppt differ diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Ellipse.java b/src/scratchpad/src/org/apache/poi/hslf/model/Ellipse.java index 9db95012c..0c58bdc12 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Ellipse.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Ellipse.java @@ -33,16 +33,15 @@ public class Ellipse extends SimpleShape { 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; diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Line.java b/src/scratchpad/src/org/apache/poi/hslf/model/Line.java index ea8e32fa6..d6ea230cd 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Line.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Line.java @@ -96,16 +96,15 @@ public class Line extends SimpleShape { 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; diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java new file mode 100644 index 000000000..5e74988ae --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java @@ -0,0 +1,137 @@ +package org.apache.poi.hslf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hslf.usermodel.PictureData; +import org.apache.poi.hslf.usermodel.SlideShow; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; + + +/** + * Represents a picture in a PowerPoint document. + *

+ * The information about an image in PowerPoint document is stored in + * two places: + *

  • EscherBSE container in the Document keeps information about image + * type, image index to refer by slides etc. + *
  • "Pictures" OLE stream holds the actual data of the image. + *

    + *

    + * Data in the "Pictures" OLE stream is organized as follows:
    + * For each image there is an entry: 25 byte header + image data. + * Image data is the exact content of the JPEG file, i.e. PowerPoint + * puts the whole jpeg file there without any modifications.
    + * Header format: + *

  • 2 byte: image type. For JPEGs it is 0x46A0, for PNG it is 0x6E00. + *
  • 2 byte: unknown. + *
  • 4 byte : image size + 17. Looks like shift from the end of + * header but why to add it to the image size? + *
  • next 16 bytes. Unique identifier of this image which is used by + * EscherBSE record. + *

    + * + * @author Yegor Kozlov + */ +public class Picture extends SimpleShape { + + /** + * Windows Metafile + * ( NOT YET SUPPORTED ) + */ + public static final int WMF = 3; + + /** + * Macintosh PICT + * ( NOT YET SUPPORTED ) + */ + public static final int PICT = 4; + + /** + * JPEG + */ + public static final int JPEG = 5; + + /** + * PNG + */ + public static final int PNG = 6; + + /** + * Windows DIB (BMP) + */ + public static final int DIB = 7; + + /** + * Create a new Picture + * + * @param idx the index of the picture + */ + public Picture(int idx){ + super(null, null); + _escherContainer = createSpContainer(idx); + } + + /** + * Create a Picture object + * + * @param escherRecord the EscherSpContainer record which holds information about + * this picture in the Slide + * @param parent the parent shape of this picture + */ + protected Picture(EscherContainerRecord escherRecord, Shape parent){ + super(escherRecord, parent); + } + + /** + * Returns index associated with this picture. + * Index starts with 1 and points to a EscherBSE record which + * holds information about this picture. + * + * @return the index to this picture (1 based). + */ + public int getPictureIndex(){ + EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID); + EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.BLIP__BLIPTODISPLAY + 0x4000); + return prop.getPropertyValue(); + } + + /** + * Create a new Picture and populate the inital structure of the EscherSp record which holds information about this picture. + + * @param idx the index of the picture which referes to EscherBSE container. + * @return the create Picture object + */ + protected EscherContainerRecord createSpContainer(int idx) { + EscherContainerRecord spContainer = super.createSpContainer(false); + spContainer.setOptions((short)15); + + EscherSpRecord spRecord = spContainer.getChildById(EscherSpRecord.RECORD_ID); + spRecord.setOptions((short)((ShapeTypes.PictureFrame << 4) | 0x2)); + + //set default properties for a picture + EscherOptRecord opt = (EscherOptRecord)getEscherChild(spContainer, EscherOptRecord.RECORD_ID); + setEscherProperty(opt, EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 8388736); + + //another weird feature of powerpoint: for picture id we must add 0x4000. + setEscherProperty(opt, (short)(EscherProperties.BLIP__BLIPTODISPLAY + 0x4000), idx); + + return spContainer; + } + + /** + * Set default size of the picture + * + * @param ppt presentation which holds the picture + */ + public void setDefaultSize(SlideShow ppt) throws IOException { + int idx = getPictureIndex(); + + PictureData pict = ppt.getPictures()[idx-1]; + BufferedImage img = ImageIO.read(new ByteArrayInputStream(pict.getData())); + + setAnchor(new java.awt.Rectangle(0, 0, img.getWidth()*6, img.getHeight()*6)); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Rectangle.java b/src/scratchpad/src/org/apache/poi/hslf/model/Rectangle.java index a4be8e271..2c5d69d22 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Rectangle.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Rectangle.java @@ -33,15 +33,15 @@ public class Rectangle extends SimpleShape { 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); diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java index a01b11691..aa1b200e6 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java @@ -26,25 +26,37 @@ import java.util.Iterator; * * @author Yegor Kozlov */ -public class Shape { +public abstract class Shape { public static final int EMU_PER_POINT = 12700; - /** - * The parent of the shape - */ - protected Shape _parent; - /** * Either EscherSpContainer or EscheSpgrContainer record * which holds information about this shape. */ protected EscherContainerRecord _escherContainer; - protected Shape(EscherContainerRecord escherRecord, Shape parent){ + /** + * Parent of this shape. + * null for the topmost shapes. + */ + protected Shape _parent; + + /** + * 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 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 @@ -128,17 +140,27 @@ public class Shape { 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 @@ public class Shape { 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 @@ public class Shape { } /** - * - * @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; } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java index 67f2d708b..c886638c3 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeFactory.java @@ -37,10 +37,10 @@ public class ShapeFactory { 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 @@ public class ShapeFactory { shape = new ShapeGroup(spContainer, parent); break; default: - shape = new Shape(spContainer, parent); + shape = new SimpleShape(spContainer, parent); break; } return shape; diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java index c11e29471..0d2d61e95 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java @@ -27,13 +27,9 @@ import java.util.List; */ 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 @@ public class ShapeGroup extends Shape{ /** * 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 @@ public class ShapeGroup extends Shape{ * @param shape - the Shape to add */ public void addShape(Shape shape){ - _escherContainer.addChildRecord(shape.getShapeRecord()); + _escherContainer.addChildRecord(shape.getSpContainer()); } /** diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java b/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java index f0834578e..e4a688628 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java @@ -158,7 +158,7 @@ public abstract class Sheet 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); diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java index 7f08b83a6..787780dcf 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java @@ -39,10 +39,10 @@ public class SimpleShape extends Shape { * @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; diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/Document.java b/src/scratchpad/src/org/apache/poi/hslf/record/Document.java index c3c0012dd..2593eecd3 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/Document.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/Document.java @@ -36,6 +36,7 @@ public class Document extends PositionDependentRecordContainer // Links to our more interesting children private DocumentAtom documentAtom; private Environment environment; + private PPDrawingGroup ppDrawing; private SlideListWithText[] slwts; /** @@ -47,6 +48,11 @@ public class Document extends PositionDependentRecordContainer * settings for the document in it */ public Environment getEnvironment() { return environment; } + /** + * Returns the PPDrawingGroup, which holds an Escher Structure + * that contains information on pictures in the slides. + */ + 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 +88,9 @@ public class Document extends PositionDependentRecordContainer 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]; diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawingGroup.java b/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawingGroup.java new file mode 100644 index 000000000..f877d2a4a --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawingGroup.java @@ -0,0 +1,103 @@ +package org.apache.poi.hslf.record; + +import org.apache.poi.ddf.*; +import org.apache.poi.util.LittleEndian; + +import java.io.OutputStream; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.util.List; +import java.util.Iterator; + +/** + * Container records which always exists inside Document. + * It always acts as a holder for escher DGG container + * which may contain which Escher BStore container information + * about pictures containes in the presentation (if any). + * + * @author Yegor Kozlov + */ +public class PPDrawingGroup extends RecordAtom { + + private byte[] _header; + private EscherContainerRecord dggContainer; + + protected PPDrawingGroup(byte[] source, int start, int len) { + // Get the header + _header = new byte[8]; + System.arraycopy(source,start,_header,0,8); + + // Get the contents for now + byte[] contents = new byte[len]; + System.arraycopy(source,start,contents,0,len); + + DefaultEscherRecordFactory erf = new DefaultEscherRecordFactory(); + EscherRecord child = erf.createRecord(contents, 0); + child.fillFields( contents, 0, erf ); + dggContainer = (EscherContainerRecord)child.getChild(0); + } + + /** + * We are type 1035 + */ + public long getRecordType() { + return RecordTypes.PPDrawingGroup.typeID; + } + + /** + * We're pretending to be an atom, so return null + */ + public Record[] getChildRecords() { + return null; + } + + public void writeOut(OutputStream out) throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + List child = dggContainer.getChildRecords(); + for (int i = 0; i < child.size(); i++) { + EscherRecord r = (EscherRecord)child.get(i); + if (r.getRecordId() == EscherContainerRecord.BSTORE_CONTAINER){ + EscherContainerRecord bstore = (EscherContainerRecord)r; + + ByteArrayOutputStream b2 = new ByteArrayOutputStream(); + List blip = bstore.getChildRecords(); + for (Iterator it=blip.iterator(); it.hasNext();) { + EscherBSERecord bse = (EscherBSERecord)it.next(); + byte[] b = new byte[36+8]; + bse.serialize(0, b); + b2.write(b); + } + byte[] bstorehead = new byte[8]; + LittleEndian.putShort(bstorehead, 0, bstore.getOptions()); + LittleEndian.putShort(bstorehead, 2, bstore.getRecordId()); + LittleEndian.putInt(bstorehead, 4, b2.size()); + bout.write(bstorehead); + bout.write(b2.toByteArray()); + + } else { + bout.write(r.serialize()); + } + } + int size = bout.size(); + + // Update the size (header bytes 5-8) + LittleEndian.putInt(_header,4,size+8); + + // Write out our header + out.write(_header); + + byte[] dgghead = new byte[8]; + LittleEndian.putShort(dgghead, 0, dggContainer.getOptions()); + LittleEndian.putShort(dgghead, 2, dggContainer.getRecordId()); + LittleEndian.putInt(dgghead, 4, size); + out.write(dgghead); + + // Finally, write out the children + out.write(bout.toByteArray()); + + } + + public EscherContainerRecord getDggContainer(){ + return dggContainer; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java index eaca249e9..41935120c 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java @@ -60,7 +60,7 @@ public class RecordTypes { 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); diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/Picture.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/Picture.java deleted file mode 100644 index 64caff7fa..000000000 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/Picture.java +++ /dev/null @@ -1,140 +0,0 @@ -/* ==================================================================== - Copyright 2002-2004 Apache Software Foundation - - Licensed 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.usermodel; - -import org.apache.poi.util.LittleEndian; - -/** - * Represents a picture in a PowerPoint document. - *

    - * The information about an image in PowerPoint document is stored in - * two places: - *

  • EscherBSE container in the Document keeps information about image - * type, image index to refer by slides etc. - *
  • "Pictures" OLE stream holds the actual data of the image. - *

    - *

    - * Data in the "Pictures" OLE stream is organized as follows:
    - * For each image there is an entry: 25 byte header + image data. - * Image data is the exact content of the JPEG file, i.e. PowerPoint - * puts the whole jpeg file there without any modifications.
    - * Header format: - *

  • 2 byte: image type. For JPEGs it is 0x46A0, for PNG it is 0x6E00. - *
  • 2 byte: unknown. - *
  • 4 byte : image size + 17. Looks like shift from the end of - * header but why to add it to the image size? - *
  • next 16 bytes. Unique identifier of this image which is used by - * EscherBSE record. - *

    - * - * @author Yegor Kozlov - */ -public class Picture { - - /** - * Windows Metafile - */ - public static final int WMF = 0x2160; - - /** - * Macintosh PICT - */ - public static final int PICT = 0x5420; - - /** - * JPEG - */ - public static final int JPEG = 0x46A0; - - /** - * PNG - */ - public static final int PNG = 0x6E00; - - /** - * Windows DIB (BMP) - */ - public static final int DIB = 0x7A80; - - /** - * The size of the header - */ - public static final int HEADER_SIZE = 25; - - /** - * Binary data of the picture - */ - protected byte[] pictdata; - - /** - * Header which holds information about this picture - */ - protected byte[] header; - - /** - * Read a picture from "Pictures" OLE stream - * - * @param pictstream the bytes to read - * @param offset the index of the first byte to read - */ - public Picture(byte[] pictstream, int offset){ - header = new byte[Picture.HEADER_SIZE]; - System.arraycopy(pictstream, offset, header, 0, header.length); - - int size = LittleEndian.getInt(header, 4) - 17; - pictdata = new byte[size]; - System.arraycopy(pictstream, offset + Picture.HEADER_SIZE, pictdata, 0, pictdata.length); - } - - /** - * @return the binary data of this picture - */ - public byte[] getData(){ - return pictdata; - } - - /** - * Return image size in bytes - * - * @return the size of the picture in bytes - */ - public int getSize(){ - return pictdata.length; - } - - /** - * Returns the unique identifier (UID) of this picture. - * The UID is a checksum of the picture data. Its length is 16 bytes - * and it must be unique across the presentation. - * - * @return the unique identifier of this picture - */ - public byte[] getUID(){ - byte[] uid = new byte[16]; - System.arraycopy(header, 8, uid, 0, uid.length); - return uid; - } - - /** - * Returns the type of this picture. Must be one of the static constans defined in this class. - * - * @return type of this picture. - */ - public int getType(){ - int type = LittleEndian.getShort(header, 0); - return type; - } -} diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/PictureData.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/PictureData.java new file mode 100644 index 000000000..0d2eba10c --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/PictureData.java @@ -0,0 +1,157 @@ +/* ==================================================================== + Copyright 2002-2004 Apache Software Foundation + + Licensed 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.usermodel; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.hslf.model.Picture; + +import java.io.OutputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * A class that represents the image data contained in the Presentation. + * + * @author Yegor Kozlov + */ +public class PictureData { + + /** + * The size of the header + */ + public static final int HEADER_SIZE = 25; + + /** + * Binary data of the picture + */ + protected byte[] pictdata; + + /** + * Header which holds information about this picture + */ + protected byte[] header; + + public PictureData(){ + header = new byte[PictureData.HEADER_SIZE]; + } + + /** + * Read a picture from "Pictures" OLE stream + * + * @param pictstream the bytes to read + * @param offset the index of the first byte to read + */ + public PictureData(byte[] pictstream, int offset){ + header = new byte[PictureData.HEADER_SIZE]; + System.arraycopy(pictstream, offset, header, 0, header.length); + + int size = LittleEndian.getInt(header, 4) - 17; + pictdata = new byte[size]; + System.arraycopy(pictstream, offset + PictureData.HEADER_SIZE, pictdata, 0, pictdata.length); + } + + /** + * @return the binary data of this picture + */ + public byte[] getData(){ + return pictdata; + } + + /** + * Set picture data + */ + public void setData(byte[] data) { + pictdata = data; + LittleEndian.putInt(header, 4, data.length + 17); + } + + /** + * Return image size in bytes + * + * @return the size of the picture in bytes + */ + public int getSize(){ + return pictdata.length; + } + + /** + * Returns the unique identifier (UID) of this picture. + * The UID is a checksum of the picture data. Its length is 16 bytes + * and it must be unique across the presentation. + * + * @return the unique identifier of this picture + */ + public byte[] getUID(){ + byte[] uid = new byte[16]; + System.arraycopy(header, 8, uid, 0, uid.length); + return uid; + } + + /** + * Set the unique identifier (UID) of this picture. + * + * @param uid checksum of the picture data + */ + public void setUID(byte[] uid){ + System.arraycopy(uid, 0, header, 8, uid.length); + } + + /** + * Set the type of this picture. + * + * @return type of this picture. + * Must be one of the static constans defined in the Picture class. + */ + public void setType(int format){ + switch (format){ + case Picture.JPEG: LittleEndian.putInt(header, 0, -266516832); break; + case Picture.PNG: LittleEndian.putInt(header, 0, -266441216); break; + } + } + + /** + * Returns the header of the Picture + * + * @return the header of the Picture + */ + public byte[] getHeader(){ + return header; + } + + /** + * Compute 16-byte checksum of this picture + */ + public static byte[] getChecksum(byte[] data) { + MessageDigest sha; + try { + sha = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e){ + throw new RuntimeException(e.getMessage()); + } + sha.update(data); + return sha.digest(); + } + + /** + * Write this picture into OutputStream + */ + public void write(OutputStream out) throws IOException { + out.write(header); + out.write(pictdata); + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java index a114ee91f..ba5e75562 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java @@ -23,6 +23,10 @@ import java.util.*; import java.awt.Dimension; import java.io.*; +import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherOptRecord; +import org.apache.poi.ddf.EscherRecord; import org.apache.poi.hslf.*; import org.apache.poi.hslf.model.*; import org.apache.poi.hslf.record.Document; @@ -50,6 +54,7 @@ import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; * - handle Slide creation cleaner * * @author Nick Burch + * @author Yegor kozlov */ public class SlideShow @@ -74,6 +79,12 @@ public class SlideShow // MetaSheets (eg masters) not yet supported // private MetaSheets[] _msheets; + + /* =============================================================== + * Setup Code + * =============================================================== + */ + /** * Constructs a Powerpoint document from the underlying @@ -86,7 +97,6 @@ public class SlideShow // Get useful things from our base slideshow _hslfSlideShow = hslfSlideShow; _records = _hslfSlideShow.getRecords(); - byte[] _docstream = _hslfSlideShow.getUnderlyingBytes(); // Handle Parent-aware Reocrds for(int i=0; i<_records.length; i++) { @@ -100,6 +110,12 @@ public class SlideShow buildSlidesAndNotes(); } + /** + * Constructs a new, empty, Powerpoint document. + */ + public SlideShow() throws IOException { + this(new HSLFSlideShow()); + } /** * Find the records that are parent-aware, and tell them @@ -373,6 +389,77 @@ public class SlideShow } } + /** + * Writes out the slideshow file the is represented by an instance of + * this class + * @param out The OutputStream to write to. + * @throws IOException If there is an unexpected IOException from the passed + * in OutputStream + */ + public void write(OutputStream out) throws IOException { + _hslfSlideShow.write(out); + } + + + /* =============================================================== + * Accessor Code + * =============================================================== + */ + + + /** + * Returns an array of the most recent version of all the interesting + * records + */ + public Record[] getMostRecentCoreRecords() { return _mostRecentCoreRecords; } + + /** + * Returns an array of all the normal Slides found in the slideshow + */ + public Slide[] getSlides() { return _slides; } + + /** + * Returns an array of all the normal Notes found in the slideshow + */ + public Notes[] getNotes() { return _notes; } + + /** + * Returns an array of all the meta Sheets (master sheets etc) + * found in the slideshow + */ + //public MetaSheet[] getMetaSheets() { return _msheets; } + + /** + * Returns all the pictures attached to the SlideShow + */ + public PictureData[] getPictures() throws IOException { + return _hslfSlideShow.getPictures(); + } + + /** + * Return the current page size + */ + public Dimension getPageSize(){ + DocumentAtom docatom = _documentRecord.getDocumentAtom(); + return new Dimension((int)docatom.getSlideSizeX(), (int)docatom.getSlideSizeY()); + } + + /** + * Helper method for usermodel: Get the font collection + */ + protected FontCollection getFontCollection() { return _fonts; } + /** + * Helper method for usermodel: Get the document record + */ + protected Document getDocumentRecord() { return _documentRecord; } + + + /* =============================================================== + * Addition Code + * =============================================================== + */ + + /** * Create a blank Slide. * @@ -476,63 +563,87 @@ public class SlideShow } - /** - * Writes out the slideshow file the is represented by an instance of - * this class - * @param out The OutputStream to write to. - * @throws IOException If there is an unexpected IOException from the passed - * in OutputStream - */ - public void write(OutputStream out) throws IOException { - _hslfSlideShow.write(out); - } + /** + * 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; - // Accesser methods follow + EscherContainerRecord dggContainer = _documentRecord.getPPDrawingGroup().getDggContainer(); + bstore = (EscherContainerRecord)Shape.getEscherChild(dggContainer, EscherContainerRecord.BSTORE_CONTAINER); + if (bstore == null){ + bstore = new EscherContainerRecord(); + bstore.setRecordId( EscherContainerRecord.BSTORE_CONTAINER); - /** - * Returns an array of the most recent version of all the interesting - * records - */ - public Record[] getMostRecentCoreRecords() { return _mostRecentCoreRecords; } + 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(); + } + } - /** - * Returns an array of all the normal Slides found in the slideshow - */ - public Slide[] getSlides() { return _slides; } + 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); - /** - * Returns an array of all the normal Notes found in the slideshow - */ - public Notes[] getNotes() { return _notes; } + bse.setRef(1); + bse.setOffset(offset); - /** - * Returns an array of all the meta Sheets (master sheets etc) - * found in the slideshow - */ - //public MetaSheet[] getMetaSheets() { return _msheets; } + bstore.addChildRecord(bse); + int count = bstore.getChildRecords().size(); + bstore.setOptions((short)( (count << 4) | 0xF )); - /** - * Returns all the pictures attached to the SlideShow - */ - public Picture[] getPictures() throws IOException { - return _hslfSlideShow.getPictures(); - } - - /** - * Return the current page size - */ - public Dimension getPageSize(){ - DocumentAtom docatom = _documentRecord.getDocumentAtom(); - return new Dimension((int)docatom.getSlideSizeX(), (int)docatom.getSlideSizeY()); - } - - /** - * Helper method for usermodel: Get the font collection - */ - protected FontCollection getFontCollection() { return _fonts; } - /** - * Helper method for usermodel: Get the document record - */ - protected Document getDocumentRecord() { return _documentRecord; } + 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); + } }