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;
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
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();
- }
+ // Look for Picture Streams:
+ readPictures();
+ }
- /**
- * Shuts things down. Closes underlying streams etc
- *
- * @throws IOException
- */
- public void close() throws IOException
- {
- if(istream != null) {
- istream.close();
+ /**
+ * 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;
- }
/**
}
- /**
- * 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()]);
}
- }
/**
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);
}
- /* ******************* fetching methods follow ********************* */
+ /* ******************* adding methods follow ********************* */
+ /**
+ * 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;
+ }
- /**
- * 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;
- }
+ /* ******************* fetching methods follow ********************* */
- /**
- * 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; }
+ /**
+ * Returns an array of all the records found in the slideshow
+ */
+ public Record[] getRecords() { return _records; }
- /**
- * Fetch the Summary Information of the document
- */
- public SummaryInformation getSummaryInformation() { return sInf; }
+ /**
+ * 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 Current User Atom of the document
- */
- public CurrentUserAtom getCurrentUserAtom() { return currentUser; }
+ /**
+ * 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; }
/**
- * Read pictures contained in this presentation
+ * 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 <code>null</code> if the
+ * @return array with the read pictures or <code>null</code> 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;
}
}
\r
public Ellipse(Shape parent){\r
super(null, parent);\r
- _escherContainer = create(parent instanceof ShapeGroup);\r
+ _escherContainer = createSpContainer(parent instanceof ShapeGroup);\r
}\r
\r
public Ellipse(){\r
this(null);\r
}\r
\r
- protected EscherContainerRecord create(boolean isChild){\r
- EscherContainerRecord spcont = super.create(isChild);\r
- spcont.setOptions((short)15);\r
+ protected EscherContainerRecord createSpContainer(boolean isChild){\r
+ EscherContainerRecord spcont = super.createSpContainer(isChild);\r
\r
EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID);\r
short type = (ShapeTypes.Ellipse << 4) + 2;\r
\r
public Line(Shape parent){\r
super(null, parent);\r
- _escherContainer = create(parent instanceof ShapeGroup);\r
+ _escherContainer = createSpContainer(parent instanceof ShapeGroup);\r
}\r
\r
public Line(){\r
this(null);\r
}\r
\r
- protected EscherContainerRecord create(boolean isChild){\r
- EscherContainerRecord spcont = super.create(isChild);\r
- spcont.setOptions((short)15);\r
+ protected EscherContainerRecord createSpContainer(boolean isChild){\r
+ EscherContainerRecord spcont = super.createSpContainer(isChild);\r
\r
EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID);\r
short type = (ShapeTypes.Line << 4) + 2;\r
--- /dev/null
+package org.apache.poi.hslf.model;\r
+\r
+import org.apache.poi.ddf.*;\r
+import org.apache.poi.hslf.usermodel.PictureData;\r
+import org.apache.poi.hslf.usermodel.SlideShow;\r
+\r
+import javax.imageio.ImageIO;\r
+import java.awt.image.BufferedImage;\r
+import java.io.ByteArrayInputStream;\r
+import java.io.IOException;\r
+\r
+\r
+/**\r
+ * Represents a picture in a PowerPoint document.\r
+ * <p>\r
+ * The information about an image in PowerPoint document is stored in\r
+ * two places:\r
+ * <li> EscherBSE container in the Document keeps information about image\r
+ * type, image index to refer by slides etc.\r
+ * <li> "Pictures" OLE stream holds the actual data of the image.\r
+ * </p>\r
+ * <p>\r
+ * Data in the "Pictures" OLE stream is organized as follows:<br>\r
+ * For each image there is an entry: 25 byte header + image data.\r
+ * Image data is the exact content of the JPEG file, i.e. PowerPoint\r
+ * puts the whole jpeg file there without any modifications.<br>\r
+ * Header format:\r
+ * <li> 2 byte: image type. For JPEGs it is 0x46A0, for PNG it is 0x6E00.\r
+ * <li> 2 byte: unknown.\r
+ * <li> 4 byte : image size + 17. Looks like shift from the end of\r
+ * header but why to add it to the image size?\r
+ * <li> next 16 bytes. Unique identifier of this image which is used by\r
+ * EscherBSE record.\r
+ * </p>\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class Picture extends SimpleShape {\r
+\r
+ /**\r
+ * Windows Metafile\r
+ * ( NOT YET SUPPORTED )\r
+ */\r
+ public static final int WMF = 3;\r
+\r
+ /**\r
+ * Macintosh PICT\r
+ * ( NOT YET SUPPORTED )\r
+ */\r
+ public static final int PICT = 4;\r
+\r
+ /**\r
+ * JPEG\r
+ */\r
+ public static final int JPEG = 5;\r
+\r
+ /**\r
+ * PNG\r
+ */\r
+ public static final int PNG = 6;\r
+\r
+ /**\r
+ * Windows DIB (BMP)\r
+ */\r
+ public static final int DIB = 7;\r
+\r
+ /**\r
+ * Create a new <code>Picture</code>\r
+ *\r
+ * @param idx the index of the picture\r
+ */\r
+ public Picture(int idx){\r
+ super(null, null);\r
+ _escherContainer = createSpContainer(idx);\r
+ }\r
+\r
+ /**\r
+ * Create a <code>Picture</code> object\r
+ *\r
+ * @param escherRecord the <code>EscherSpContainer</code> record which holds information about\r
+ * this picture in the <code>Slide</code>\r
+ * @param parent the parent shape of this picture\r
+ */\r
+ protected Picture(EscherContainerRecord escherRecord, Shape parent){\r
+ super(escherRecord, parent);\r
+ }\r
+\r
+ /**\r
+ * Returns index associated with this picture.\r
+ * Index starts with 1 and points to a EscherBSE record which\r
+ * holds information about this picture.\r
+ *\r
+ * @return the index to this picture (1 based).\r
+ */\r
+ public int getPictureIndex(){\r
+ EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);\r
+ EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.BLIP__BLIPTODISPLAY + 0x4000);\r
+ return prop.getPropertyValue();\r
+ }\r
+\r
+ /**\r
+ * Create a new Picture and populate the inital structure of the <code>EscherSp</code> record which holds information about this picture.\r
+\r
+ * @param idx the index of the picture which referes to <code>EscherBSE</code> container.\r
+ * @return the create Picture object\r
+ */\r
+ protected EscherContainerRecord createSpContainer(int idx) {\r
+ EscherContainerRecord spContainer = super.createSpContainer(false);\r
+ spContainer.setOptions((short)15);\r
+\r
+ EscherSpRecord spRecord = spContainer.getChildById(EscherSpRecord.RECORD_ID);\r
+ spRecord.setOptions((short)((ShapeTypes.PictureFrame << 4) | 0x2));\r
+\r
+ //set default properties for a picture\r
+ EscherOptRecord opt = (EscherOptRecord)getEscherChild(spContainer, EscherOptRecord.RECORD_ID);\r
+ setEscherProperty(opt, EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 8388736);\r
+\r
+ //another weird feature of powerpoint: for picture id we must add 0x4000.\r
+ setEscherProperty(opt, (short)(EscherProperties.BLIP__BLIPTODISPLAY + 0x4000), idx);\r
+\r
+ return spContainer;\r
+ }\r
+\r
+ /**\r
+ * Set default size of the picture\r
+ *\r
+ * @param ppt presentation which holds the picture\r
+ */\r
+ public void setDefaultSize(SlideShow ppt) throws IOException {\r
+ int idx = getPictureIndex();\r
+\r
+ PictureData pict = ppt.getPictures()[idx-1];\r
+ BufferedImage img = ImageIO.read(new ByteArrayInputStream(pict.getData()));\r
+\r
+ setAnchor(new java.awt.Rectangle(0, 0, img.getWidth()*6, img.getHeight()*6));\r
+ }\r
+}\r
\r
public Rectangle(Shape parent){\r
super(null, parent);\r
- _escherContainer = create(parent instanceof ShapeGroup);\r
+ _escherContainer = createSpContainer(parent instanceof ShapeGroup);\r
}\r
\r
public Rectangle(){\r
this(null);\r
}\r
\r
- protected EscherContainerRecord create(boolean isChild){\r
- EscherContainerRecord spcont = super.create(isChild);\r
+ protected EscherContainerRecord createSpContainer(boolean isChild){\r
+ EscherContainerRecord spcont = super.createSpContainer(isChild);\r
spcont.setOptions((short)15);\r
\r
EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID);\r
*\r
* @author Yegor Kozlov\r
*/\r
-public class Shape {\r
+public abstract class Shape {\r
\r
public static final int EMU_PER_POINT = 12700;\r
\r
- /**\r
- * The parent of the shape\r
- */\r
- protected Shape _parent;\r
-\r
/**\r
* Either EscherSpContainer or EscheSpgrContainer record\r
* which holds information about this shape.\r
*/\r
protected EscherContainerRecord _escherContainer;\r
\r
- protected Shape(EscherContainerRecord escherRecord, Shape parent){\r
+ /**\r
+ * Parent of this shape.\r
+ * <code>null</code> for the topmost shapes.\r
+ */\r
+ protected Shape _parent;\r
+\r
+ /**\r
+ * Create a Shape object. This constructor is used when an existing Shape is read from from a PowerPoint document.\r
+ *\r
+ * @param escherRecord <code>EscherSpContainer</code> container which holds information about this shape\r
+ * @param parent the parent of this Shape\r
+ */\r
+ protected Shape(EscherContainerRecord escherRecord, Shape parent){\r
_escherContainer = escherRecord;\r
_parent = parent;\r
- }\r
+ }\r
+\r
+ /**\r
+ * Creates the lowerlevel escher records for this shape.\r
+ */\r
+ protected abstract EscherContainerRecord createSpContainer(boolean isChild);\r
\r
/**\r
* @return the parent of this shape\r
setAnchor(anchor);\r
}\r
\r
- protected static EscherRecord getEscherChild(EscherContainerRecord owner, int recordId){\r
+ /**\r
+ * Helper method to return escher child by record ID\r
+ *\r
+ * @return escher record or <code>null</code> if not found.\r
+ */\r
+ public static EscherRecord getEscherChild(EscherContainerRecord owner, int recordId){\r
for ( Iterator iterator = owner.getChildRecords().iterator(); iterator.hasNext(); )\r
{\r
EscherRecord escherRecord = (EscherRecord) iterator.next();\r
if (escherRecord.getRecordId() == recordId)\r
- return (EscherRecord) escherRecord;\r
+ return escherRecord;\r
}\r
return null;\r
}\r
\r
- protected static EscherProperty getEscherProperty(EscherOptRecord opt, int propId){\r
+ /**\r
+ * Returns escher property by id.\r
+ *\r
+ * @return escher property or <code>null</code> if not found.\r
+ */\r
+ public static EscherProperty getEscherProperty(EscherOptRecord opt, int propId){\r
for ( Iterator iterator = opt.getEscherProperties().iterator(); iterator.hasNext(); )\r
{\r
EscherProperty prop = (EscherProperty) iterator.next();\r
return null;\r
}\r
\r
- protected static void setEscherProperty(EscherOptRecord opt, short propId, int value){\r
+ /**\r
+ * Set an escher property in the opt record.\r
+ *\r
+ * @param opt The opt record to set the properties to.\r
+ * @param propId The id of the property. One of the constants defined in EscherOptRecord.\r
+ * @param value value of the property\r
+ */\r
+ public static void setEscherProperty(EscherOptRecord opt, short propId, int value){\r
java.util.List props = opt.getEscherProperties();\r
for ( Iterator iterator = props.iterator(); iterator.hasNext(); ) {\r
EscherProperty prop = (EscherProperty) iterator.next();\r
}\r
\r
/**\r
- *\r
- * @return escher container which holds information about this shape\r
+ * @return The shape container and it's children that can represent this\r
+ * shape.\r
*/\r
- public EscherContainerRecord getShapeRecord(){\r
+ public EscherContainerRecord getSpContainer(){\r
return _escherContainer;\r
}\r
}\r
switch (type){\r
case ShapeTypes.TextBox:\r
case ShapeTypes.Rectangle:\r
- shape = new Shape(spContainer, parent);\r
+ shape = new Rectangle(spContainer, parent);\r
break;\r
case ShapeTypes.PictureFrame:\r
- shape = new Shape(spContainer, parent);\r
+ shape = new Picture(spContainer, parent);\r
break;\r
case ShapeTypes.Line:\r
shape = new Line(spContainer, parent);\r
shape = new ShapeGroup(spContainer, parent);\r
break;\r
default:\r
- shape = new Shape(spContainer, parent);\r
+ shape = new SimpleShape(spContainer, parent);\r
break;\r
}\r
return shape;\r
*/\r
public class ShapeGroup extends Shape{\r
\r
- public ShapeGroup(Shape parent){\r
- super(null, parent);\r
- _escherContainer = create();\r
- }\r
-\r
public ShapeGroup(){\r
- this(null);\r
+ this(null, null);\r
+ _escherContainer = createSpContainer(false);\r
}\r
\r
protected ShapeGroup(EscherContainerRecord escherRecord, Shape parent){\r
/**\r
* Create a new ShapeGroup and create an instance of <code>EscherSpgrContainer</code> which represents a group of shapes\r
*/\r
- protected EscherContainerRecord create() {\r
+ protected EscherContainerRecord createSpContainer(boolean isChild) {\r
EscherContainerRecord spgr = new EscherContainerRecord();\r
spgr.setRecordId(EscherContainerRecord.SPGR_CONTAINER);\r
spgr.setOptions((short)15);\r
* @param shape - the Shape to add\r
*/\r
public void addShape(Shape shape){\r
- _escherContainer.addChildRecord(shape.getShapeRecord());\r
+ _escherContainer.addChildRecord(shape.getSpContainer());\r
}\r
\r
/**\r
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);
* @param isChild <code>true</code> if the Line is inside a group, <code>false</code> otherwise\r
* @return the record container which holds this shape\r
*/\r
- protected EscherContainerRecord create(boolean isChild) {\r
+ protected EscherContainerRecord createSpContainer(boolean isChild) {\r
EscherContainerRecord spContainer = new EscherContainerRecord();\r
spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER );\r
- //spContainer.setOptions((short)15);\r
+ spContainer.setOptions((short)15);\r
\r
EscherSpRecord sp = new EscherSpRecord();\r
int flags = EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE;\r
// Links to our more interesting children
private DocumentAtom documentAtom;
private Environment environment;
+ private PPDrawingGroup ppDrawing;
private SlideListWithText[] slwts;
/**
* 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
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];
--- /dev/null
+package org.apache.poi.hslf.record;\r
+\r
+import org.apache.poi.ddf.*;\r
+import org.apache.poi.util.LittleEndian;\r
+\r
+import java.io.OutputStream;\r
+import java.io.IOException;\r
+import java.io.ByteArrayOutputStream;\r
+import java.util.List;\r
+import java.util.Iterator;\r
+\r
+/**\r
+ * Container records which always exists inside Document.\r
+ * It always acts as a holder for escher DGG container\r
+ * which may contain which Escher BStore container information \r
+ * about pictures containes in the presentation (if any).\r
+ * \r
+ * @author Yegor Kozlov\r
+ */\r
+public class PPDrawingGroup extends RecordAtom {\r
+\r
+ private byte[] _header;\r
+ private EscherContainerRecord dggContainer;\r
+\r
+ protected PPDrawingGroup(byte[] source, int start, int len) {\r
+ // Get the header\r
+ _header = new byte[8];\r
+ System.arraycopy(source,start,_header,0,8);\r
+\r
+ // Get the contents for now\r
+ byte[] contents = new byte[len];\r
+ System.arraycopy(source,start,contents,0,len);\r
+\r
+ DefaultEscherRecordFactory erf = new DefaultEscherRecordFactory();\r
+ EscherRecord child = erf.createRecord(contents, 0);\r
+ child.fillFields( contents, 0, erf );\r
+ dggContainer = (EscherContainerRecord)child.getChild(0);\r
+ }\r
+\r
+ /**\r
+ * We are type 1035\r
+ */\r
+ public long getRecordType() {\r
+ return RecordTypes.PPDrawingGroup.typeID;\r
+ }\r
+\r
+ /**\r
+ * We're pretending to be an atom, so return null\r
+ */\r
+ public Record[] getChildRecords() {\r
+ return null;\r
+ }\r
+\r
+ public void writeOut(OutputStream out) throws IOException {\r
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();\r
+ List child = dggContainer.getChildRecords();\r
+ for (int i = 0; i < child.size(); i++) {\r
+ EscherRecord r = (EscherRecord)child.get(i);\r
+ if (r.getRecordId() == EscherContainerRecord.BSTORE_CONTAINER){\r
+ EscherContainerRecord bstore = (EscherContainerRecord)r;\r
+\r
+ ByteArrayOutputStream b2 = new ByteArrayOutputStream();\r
+ List blip = bstore.getChildRecords();\r
+ for (Iterator it=blip.iterator(); it.hasNext();) {\r
+ EscherBSERecord bse = (EscherBSERecord)it.next();\r
+ byte[] b = new byte[36+8];\r
+ bse.serialize(0, b);\r
+ b2.write(b);\r
+ }\r
+ byte[] bstorehead = new byte[8];\r
+ LittleEndian.putShort(bstorehead, 0, bstore.getOptions());\r
+ LittleEndian.putShort(bstorehead, 2, bstore.getRecordId());\r
+ LittleEndian.putInt(bstorehead, 4, b2.size());\r
+ bout.write(bstorehead);\r
+ bout.write(b2.toByteArray());\r
+\r
+ } else {\r
+ bout.write(r.serialize());\r
+ }\r
+ }\r
+ int size = bout.size();\r
+\r
+ // Update the size (header bytes 5-8)\r
+ LittleEndian.putInt(_header,4,size+8);\r
+\r
+ // Write out our header\r
+ out.write(_header);\r
+\r
+ byte[] dgghead = new byte[8];\r
+ LittleEndian.putShort(dgghead, 0, dggContainer.getOptions());\r
+ LittleEndian.putShort(dgghead, 2, dggContainer.getRecordId());\r
+ LittleEndian.putInt(dgghead, 4, size);\r
+ out.write(dgghead);\r
+\r
+ // Finally, write out the children\r
+ out.write(bout.toByteArray());\r
+\r
+ }\r
+\r
+ public EscherContainerRecord getDggContainer(){\r
+ return dggContainer;\r
+ }\r
+}\r
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);
+++ /dev/null
-/* ====================================================================\r
- Copyright 2002-2004 Apache Software Foundation\r
-\r
- Licensed under the Apache License, Version 2.0 (the "License");\r
- you may not use this file except in compliance with the License.\r
- You may obtain a copy of the License at\r
-\r
- http://www.apache.org/licenses/LICENSE-2.0\r
-\r
- Unless required by applicable law or agreed to in writing, software\r
- distributed under the License is distributed on an "AS IS" BASIS,\r
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- See the License for the specific language governing permissions and\r
- limitations under the License.\r
-==================================================================== */\r
-package org.apache.poi.hslf.usermodel;\r
-\r
-import org.apache.poi.util.LittleEndian;\r
-\r
-/**\r
- * Represents a picture in a PowerPoint document.\r
- * <p>\r
- * The information about an image in PowerPoint document is stored in \r
- * two places:\r
- * <li> EscherBSE container in the Document keeps information about image \r
- * type, image index to refer by slides etc.\r
- * <li> "Pictures" OLE stream holds the actual data of the image.\r
- * </p>\r
- * <p>\r
- * Data in the "Pictures" OLE stream is organized as follows:<br>\r
- * For each image there is an entry: 25 byte header + image data.\r
- * Image data is the exact content of the JPEG file, i.e. PowerPoint\r
- * puts the whole jpeg file there without any modifications.<br>\r
- * Header format:\r
- * <li> 2 byte: image type. For JPEGs it is 0x46A0, for PNG it is 0x6E00.\r
- * <li> 2 byte: unknown.\r
- * <li> 4 byte : image size + 17. Looks like shift from the end of \r
- * header but why to add it to the image size?\r
- * <li> next 16 bytes. Unique identifier of this image which is used by \r
- * EscherBSE record.\r
- * </p>\r
- *\r
- * @author Yegor Kozlov\r
- */\r
-public class Picture {\r
-\r
- /**\r
- * Windows Metafile\r
- */\r
- public static final int WMF = 0x2160;\r
-\r
- /**\r
- * Macintosh PICT\r
- */\r
- public static final int PICT = 0x5420;\r
-\r
- /**\r
- * JPEG\r
- */\r
- public static final int JPEG = 0x46A0;\r
-\r
- /**\r
- * PNG\r
- */\r
- public static final int PNG = 0x6E00;\r
-\r
- /**\r
- * Windows DIB (BMP)\r
- */\r
- public static final int DIB = 0x7A80;\r
-\r
- /**\r
- * The size of the header\r
- */\r
- public static final int HEADER_SIZE = 25;\r
-\r
- /**\r
- * Binary data of the picture\r
- */\r
- protected byte[] pictdata;\r
-\r
- /**\r
- * Header which holds information about this picture\r
- */\r
- protected byte[] header;\r
-\r
- /**\r
- * Read a picture from "Pictures" OLE stream\r
- *\r
- * @param pictstream the bytes to read\r
- * @param offset the index of the first byte to read\r
- */\r
- public Picture(byte[] pictstream, int offset){\r
- header = new byte[Picture.HEADER_SIZE];\r
- System.arraycopy(pictstream, offset, header, 0, header.length);\r
-\r
- int size = LittleEndian.getInt(header, 4) - 17;\r
- pictdata = new byte[size];\r
- System.arraycopy(pictstream, offset + Picture.HEADER_SIZE, pictdata, 0, pictdata.length);\r
- }\r
-\r
- /**\r
- * @return the binary data of this picture\r
- */\r
- public byte[] getData(){\r
- return pictdata;\r
- }\r
-\r
- /**\r
- * Return image size in bytes\r
- *\r
- * @return the size of the picture in bytes\r
- */\r
- public int getSize(){\r
- return pictdata.length;\r
- }\r
-\r
- /**\r
- * Returns the unique identifier (UID) of this picture.\r
- * The UID is a checksum of the picture data. Its length is 16 bytes\r
- * and it must be unique across the presentation.\r
- *\r
- * @return the unique identifier of this picture\r
- */\r
- public byte[] getUID(){\r
- byte[] uid = new byte[16];\r
- System.arraycopy(header, 8, uid, 0, uid.length);\r
- return uid;\r
- }\r
-\r
- /**\r
- * Returns the type of this picture. Must be one of the static constans defined in this class.\r
- *\r
- * @return type of this picture.\r
- */\r
- public int getType(){\r
- int type = LittleEndian.getShort(header, 0);\r
- return type;\r
- }\r
-}\r
--- /dev/null
+/* ====================================================================\r
+ Copyright 2002-2004 Apache Software Foundation\r
+\r
+ Licensed under the Apache License, Version 2.0 (the "License");\r
+ you may not use this file except in compliance with the License.\r
+ You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.hslf.usermodel;\r
+\r
+import org.apache.poi.util.LittleEndian;\r
+import org.apache.poi.hslf.model.Picture;\r
+\r
+import java.io.OutputStream;\r
+import java.io.IOException;\r
+import java.security.MessageDigest;\r
+import java.security.NoSuchAlgorithmException;\r
+\r
+/**\r
+ * A class that represents the image data contained in the Presentation.\r
+ *\r
+ * @author Yegor Kozlov\r
+ */\r
+public class PictureData {\r
+\r
+ /**\r
+ * The size of the header\r
+ */\r
+ public static final int HEADER_SIZE = 25;\r
+\r
+ /**\r
+ * Binary data of the picture\r
+ */\r
+ protected byte[] pictdata;\r
+\r
+ /**\r
+ * Header which holds information about this picture\r
+ */\r
+ protected byte[] header;\r
+\r
+ public PictureData(){\r
+ header = new byte[PictureData.HEADER_SIZE];\r
+ }\r
+\r
+ /**\r
+ * Read a picture from "Pictures" OLE stream\r
+ *\r
+ * @param pictstream the bytes to read\r
+ * @param offset the index of the first byte to read\r
+ */\r
+ public PictureData(byte[] pictstream, int offset){\r
+ header = new byte[PictureData.HEADER_SIZE];\r
+ System.arraycopy(pictstream, offset, header, 0, header.length);\r
+\r
+ int size = LittleEndian.getInt(header, 4) - 17;\r
+ pictdata = new byte[size];\r
+ System.arraycopy(pictstream, offset + PictureData.HEADER_SIZE, pictdata, 0, pictdata.length);\r
+ }\r
+\r
+ /**\r
+ * @return the binary data of this picture\r
+ */\r
+ public byte[] getData(){\r
+ return pictdata;\r
+ }\r
+\r
+ /**\r
+ * Set picture data\r
+ */\r
+ public void setData(byte[] data) {\r
+ pictdata = data;\r
+ LittleEndian.putInt(header, 4, data.length + 17);\r
+ }\r
+\r
+ /**\r
+ * Return image size in bytes\r
+ *\r
+ * @return the size of the picture in bytes\r
+ */\r
+ public int getSize(){\r
+ return pictdata.length;\r
+ }\r
+\r
+ /**\r
+ * Returns the unique identifier (UID) of this picture.\r
+ * The UID is a checksum of the picture data. Its length is 16 bytes\r
+ * and it must be unique across the presentation.\r
+ *\r
+ * @return the unique identifier of this picture\r
+ */\r
+ public byte[] getUID(){\r
+ byte[] uid = new byte[16];\r
+ System.arraycopy(header, 8, uid, 0, uid.length);\r
+ return uid;\r
+ }\r
+\r
+ /**\r
+ * Set the unique identifier (UID) of this picture.\r
+ *\r
+ * @param uid checksum of the picture data\r
+ */\r
+ public void setUID(byte[] uid){\r
+ System.arraycopy(uid, 0, header, 8, uid.length);\r
+ }\r
+\r
+ /**\r
+ * Set the type of this picture.\r
+ *\r
+ * @return type of this picture.\r
+ * Must be one of the static constans defined in the <code>Picture<code> class.\r
+ */\r
+ public void setType(int format){\r
+ switch (format){\r
+ case Picture.JPEG: LittleEndian.putInt(header, 0, -266516832); break;\r
+ case Picture.PNG: LittleEndian.putInt(header, 0, -266441216); break;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns the header of the Picture\r
+ *\r
+ * @return the header of the Picture\r
+ */\r
+ public byte[] getHeader(){\r
+ return header;\r
+ }\r
+\r
+ /**\r
+ * Compute 16-byte checksum of this picture\r
+ */\r
+ public static byte[] getChecksum(byte[] data) {\r
+ MessageDigest sha;\r
+ try {\r
+ sha = MessageDigest.getInstance("MD5");\r
+ } catch (NoSuchAlgorithmException e){\r
+ throw new RuntimeException(e.getMessage());\r
+ }\r
+ sha.update(data);\r
+ return sha.digest();\r
+ }\r
+\r
+ /**\r
+ * Write this picture into <code>OutputStream</code>\r
+ */\r
+ public void write(OutputStream out) throws IOException {\r
+ out.write(header);\r
+ out.write(pictdata);\r
+ }\r
+\r
+}\r
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;
* - handle Slide creation cleaner
*
* @author Nick Burch
+ * @author Yegor kozlov
*/
public class SlideShow
// MetaSheets (eg masters) not yet supported
// private MetaSheets[] _msheets;
+
+ /* ===============================================================
+ * Setup Code
+ * ===============================================================
+ */
+
/**
* Constructs a Powerpoint document from the underlying
// 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++) {
buildSlidesAndNotes();
}
+ /**
+ * Constructs a new, empty, Powerpoint document.
+ */
+ public SlideShow() throws IOException {
+ this(new HSLFSlideShow());
+ }
/**
* Find the records that are parent-aware, and tell them
}
}
+ /**
+ * 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 <code>Slide</code>.
*
}
- /**
- * 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);
- }
-
-
- // Accesser methods follow
-
- /**
- * 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 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; }
+ /**
+ * 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 <code>Picture</code> 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 <code>Picture</code> 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);
+ }
}