Patches from Yegor (Bug #39097), along with some sorting out of indenting, method positioning etc

git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@388920 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2006-03-26 16:20:08 +00:00
parent f213a74d5a
commit 7067a15892
17 changed files with 836 additions and 388 deletions

View File

@ -23,7 +23,6 @@ import java.util.*;
import java.io.*; import java.io.*;
import org.apache.poi.poifs.filesystem.POIFSFileSystem; 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.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream; 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.SummaryInformation;
import org.apache.poi.hpsf.DocumentSummaryInformation; import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.hslf.record.*; 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 * This class contains the main functionality for the Powerpoint file
@ -47,78 +44,91 @@ import org.apache.poi.hslf.usermodel.Picture;
public class HSLFSlideShow public class HSLFSlideShow
{ {
private InputStream istream; private InputStream istream;
private POIFSFileSystem filesystem; private POIFSFileSystem filesystem;
// Holds metadata on our document // Holds metadata on our document
private SummaryInformation sInf; private SummaryInformation sInf;
private DocumentSummaryInformation dsInf; private DocumentSummaryInformation dsInf;
private CurrentUserAtom currentUser; private CurrentUserAtom currentUser;
// Low level contents of the file // Low level contents of the file
private byte[] _docstream; private byte[] _docstream;
// Low level contents // Low level contents
private Record[] _records; private Record[] _records;
/** // Raw Pictures contained in the pictures stream
* Constructs a Powerpoint document from fileName. Parses the document private PictureData[] _pictures;
* and places all the important stuff into data structures.
* /**
* @param fileName The name of the file to read. * Constructs a Powerpoint document from fileName. Parses the document
* @throws IOException if there is a problem while parsing the document. * and places all the important stuff into data structures.
*/ *
public HSLFSlideShow(String fileName) throws IOException * @param fileName The name of the file to read.
{ * @throws IOException if there is a problem while parsing the document.
this(new FileInputStream(fileName)); */
} public HSLFSlideShow(String fileName) throws IOException
{
this(new FileInputStream(fileName));
}
/** /**
* Constructs a Powerpoint document from an input stream. Parses the * Constructs a Powerpoint document from an input stream. Parses the
* document and places all the important stuff into data structures. * document and places all the important stuff into data structures.
* *
* @param inputStream the source of the data * @param inputStream the source of the data
* @throws IOException if there is a problem while parsing the document. * @throws IOException if there is a problem while parsing the document.
*/ */
public HSLFSlideShow(InputStream inputStream) throws IOException public HSLFSlideShow(InputStream inputStream) throws IOException
{ {
//do Ole stuff //do Ole stuff
this(new POIFSFileSystem(inputStream)); this(new POIFSFileSystem(inputStream));
istream = inputStream; istream = inputStream;
} }
/** /**
* Constructs a Powerpoint document from a POIFS Filesystem. Parses the * Constructs a Powerpoint document from a POIFS Filesystem. Parses the
* document and places all the important stuff into data structures. * document and places all the important stuff into data structures.
* *
* @param filesystem the POIFS FileSystem to read from * @param filesystem the POIFS FileSystem to read from
* @throws IOException if there is a problem while parsing the document. * @throws IOException if there is a problem while parsing the document.
*/ */
public HSLFSlideShow(POIFSFileSystem filesystem) throws IOException public HSLFSlideShow(POIFSFileSystem filesystem) throws IOException
{ {
this.filesystem = filesystem; this.filesystem = filesystem;
// Go find a PowerPoint document in the stream // Go find a PowerPoint document in the stream
// Save anything useful we come across // Save anything useful we come across
readFIB(); readFIB();
// Look for Property Streams: // Look for Property Streams:
readProperties(); readProperties();
}
// Look for Picture Streams:
/** readPictures();
* Shuts things down. Closes underlying streams etc }
*
* @throws IOException /**
*/ * Constructs a new, empty, Powerpoint document.
public void close() throws IOException */
{ public HSLFSlideShow() throws IOException
if(istream != null) { {
istream.close(); 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 * Find the properties from the filesystem, and load them
*/ */
public void readProperties() { public void readProperties() {
// DocumentSummaryInformation // DocumentSummaryInformation
dsInf = (DocumentSummaryInformation)getPropertySet("\005DocumentSummaryInformation"); dsInf = (DocumentSummaryInformation)getPropertySet("\005DocumentSummaryInformation");
// SummaryInformation // SummaryInformation
sInf = (SummaryInformation)getPropertySet("\005SummaryInformation"); sInf = (SummaryInformation)getPropertySet("\005SummaryInformation");
// Current User // Current User
try { try {
currentUser = new CurrentUserAtom(filesystem); currentUser = new CurrentUserAtom(filesystem);
} catch(IOException ie) { } catch(IOException ie) {
System.err.println("Error finding Current User Atom:\n" + ie); System.err.println("Error finding Current User Atom:\n" + ie);
currentUser = new CurrentUserAtom(); 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.setCurrentEditOffset(newLastUserEditAtomPos.intValue());
currentUser.writeToFS(outFS); 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 // Send the POIFSFileSystem object out to the underlying stream
outFS.writeFilesystem(out); outFS.writeFilesystem(out);
@ -311,87 +360,86 @@ public class HSLFSlideShow
} }
/* ******************* fetching methods follow ********************* */ /* ******************* adding 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; }
/** /**
* 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 <code>null</code> if the * @return array with the read pictures or <code>null</code> if the
* presentation doesn't contain pictures. * presentation doesn't contain pictures.
*/ */
public Picture[] getPictures() throws IOException { public PictureData[] getPictures() {
byte[] pictstream; return _pictures;
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()]);
} }
} }

Binary file not shown.

View File

@ -33,16 +33,15 @@ public class Ellipse extends SimpleShape {
public Ellipse(Shape parent){ public Ellipse(Shape parent){
super(null, parent); super(null, parent);
_escherContainer = create(parent instanceof ShapeGroup); _escherContainer = createSpContainer(parent instanceof ShapeGroup);
} }
public Ellipse(){ public Ellipse(){
this(null); this(null);
} }
protected EscherContainerRecord create(boolean isChild){ protected EscherContainerRecord createSpContainer(boolean isChild){
EscherContainerRecord spcont = super.create(isChild); EscherContainerRecord spcont = super.createSpContainer(isChild);
spcont.setOptions((short)15);
EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID); EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID);
short type = (ShapeTypes.Ellipse << 4) + 2; short type = (ShapeTypes.Ellipse << 4) + 2;

View File

@ -96,16 +96,15 @@ public class Line extends SimpleShape {
public Line(Shape parent){ public Line(Shape parent){
super(null, parent); super(null, parent);
_escherContainer = create(parent instanceof ShapeGroup); _escherContainer = createSpContainer(parent instanceof ShapeGroup);
} }
public Line(){ public Line(){
this(null); this(null);
} }
protected EscherContainerRecord create(boolean isChild){ protected EscherContainerRecord createSpContainer(boolean isChild){
EscherContainerRecord spcont = super.create(isChild); EscherContainerRecord spcont = super.createSpContainer(isChild);
spcont.setOptions((short)15);
EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID); EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID);
short type = (ShapeTypes.Line << 4) + 2; short type = (ShapeTypes.Line << 4) + 2;

View File

@ -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.
* <p>
* The information about an image in PowerPoint document is stored in
* two places:
* <li> EscherBSE container in the Document keeps information about image
* type, image index to refer by slides etc.
* <li> "Pictures" OLE stream holds the actual data of the image.
* </p>
* <p>
* Data in the "Pictures" OLE stream is organized as follows:<br>
* 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.<br>
* Header format:
* <li> 2 byte: image type. For JPEGs it is 0x46A0, for PNG it is 0x6E00.
* <li> 2 byte: unknown.
* <li> 4 byte : image size + 17. Looks like shift from the end of
* header but why to add it to the image size?
* <li> next 16 bytes. Unique identifier of this image which is used by
* EscherBSE record.
* </p>
*
* @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 <code>Picture</code>
*
* @param idx the index of the picture
*/
public Picture(int idx){
super(null, null);
_escherContainer = createSpContainer(idx);
}
/**
* Create a <code>Picture</code> object
*
* @param escherRecord the <code>EscherSpContainer</code> record which holds information about
* this picture in the <code>Slide</code>
* @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 <code>EscherSp</code> record which holds information about this picture.
* @param idx the index of the picture which referes to <code>EscherBSE</code> 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));
}
}

View File

@ -33,15 +33,15 @@ public class Rectangle extends SimpleShape {
public Rectangle(Shape parent){ public Rectangle(Shape parent){
super(null, parent); super(null, parent);
_escherContainer = create(parent instanceof ShapeGroup); _escherContainer = createSpContainer(parent instanceof ShapeGroup);
} }
public Rectangle(){ public Rectangle(){
this(null); this(null);
} }
protected EscherContainerRecord create(boolean isChild){ protected EscherContainerRecord createSpContainer(boolean isChild){
EscherContainerRecord spcont = super.create(isChild); EscherContainerRecord spcont = super.createSpContainer(isChild);
spcont.setOptions((short)15); spcont.setOptions((short)15);
EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID); EscherSpRecord spRecord = spcont.getChildById(EscherSpRecord.RECORD_ID);

View File

@ -26,25 +26,37 @@ import java.util.Iterator;
* *
* @author Yegor Kozlov * @author Yegor Kozlov
*/ */
public class Shape { public abstract class Shape {
public static final int EMU_PER_POINT = 12700; public static final int EMU_PER_POINT = 12700;
/**
* The parent of the shape
*/
protected Shape _parent;
/** /**
* Either EscherSpContainer or EscheSpgrContainer record * Either EscherSpContainer or EscheSpgrContainer record
* which holds information about this shape. * which holds information about this shape.
*/ */
protected EscherContainerRecord _escherContainer; protected EscherContainerRecord _escherContainer;
protected Shape(EscherContainerRecord escherRecord, Shape parent){ /**
* Parent of this shape.
* <code>null</code> 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 <code>EscherSpContainer</code> container which holds information about this shape
* @param parent the parent of this Shape
*/
protected Shape(EscherContainerRecord escherRecord, Shape parent){
_escherContainer = escherRecord; _escherContainer = escherRecord;
_parent = parent; _parent = parent;
} }
/**
* Creates the lowerlevel escher records for this shape.
*/
protected abstract EscherContainerRecord createSpContainer(boolean isChild);
/** /**
* @return the parent of this shape * @return the parent of this shape
@ -128,17 +140,27 @@ public class Shape {
setAnchor(anchor); setAnchor(anchor);
} }
protected static EscherRecord getEscherChild(EscherContainerRecord owner, int recordId){ /**
* Helper method to return escher child by record ID
*
* @return escher record or <code>null</code> if not found.
*/
public static EscherRecord getEscherChild(EscherContainerRecord owner, int recordId){
for ( Iterator iterator = owner.getChildRecords().iterator(); iterator.hasNext(); ) for ( Iterator iterator = owner.getChildRecords().iterator(); iterator.hasNext(); )
{ {
EscherRecord escherRecord = (EscherRecord) iterator.next(); EscherRecord escherRecord = (EscherRecord) iterator.next();
if (escherRecord.getRecordId() == recordId) if (escherRecord.getRecordId() == recordId)
return (EscherRecord) escherRecord; return escherRecord;
} }
return null; return null;
} }
protected static EscherProperty getEscherProperty(EscherOptRecord opt, int propId){ /**
* Returns escher property by id.
*
* @return escher property or <code>null</code> if not found.
*/
public static EscherProperty getEscherProperty(EscherOptRecord opt, int propId){
for ( Iterator iterator = opt.getEscherProperties().iterator(); iterator.hasNext(); ) for ( Iterator iterator = opt.getEscherProperties().iterator(); iterator.hasNext(); )
{ {
EscherProperty prop = (EscherProperty) iterator.next(); EscherProperty prop = (EscherProperty) iterator.next();
@ -148,7 +170,14 @@ public class Shape {
return null; 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(); java.util.List props = opt.getEscherProperties();
for ( Iterator iterator = props.iterator(); iterator.hasNext(); ) { for ( Iterator iterator = props.iterator(); iterator.hasNext(); ) {
EscherProperty prop = (EscherProperty) iterator.next(); EscherProperty prop = (EscherProperty) iterator.next();
@ -163,10 +192,10 @@ public class Shape {
} }
/** /**
* * @return The shape container and it's children that can represent this
* @return escher container which holds information about this shape * shape.
*/ */
public EscherContainerRecord getShapeRecord(){ public EscherContainerRecord getSpContainer(){
return _escherContainer; return _escherContainer;
} }
} }

View File

@ -37,10 +37,10 @@ public class ShapeFactory {
switch (type){ switch (type){
case ShapeTypes.TextBox: case ShapeTypes.TextBox:
case ShapeTypes.Rectangle: case ShapeTypes.Rectangle:
shape = new Shape(spContainer, parent); shape = new Rectangle(spContainer, parent);
break; break;
case ShapeTypes.PictureFrame: case ShapeTypes.PictureFrame:
shape = new Shape(spContainer, parent); shape = new Picture(spContainer, parent);
break; break;
case ShapeTypes.Line: case ShapeTypes.Line:
shape = new Line(spContainer, parent); shape = new Line(spContainer, parent);
@ -52,7 +52,7 @@ public class ShapeFactory {
shape = new ShapeGroup(spContainer, parent); shape = new ShapeGroup(spContainer, parent);
break; break;
default: default:
shape = new Shape(spContainer, parent); shape = new SimpleShape(spContainer, parent);
break; break;
} }
return shape; return shape;

View File

@ -27,13 +27,9 @@ import java.util.List;
*/ */
public class ShapeGroup extends Shape{ public class ShapeGroup extends Shape{
public ShapeGroup(Shape parent){
super(null, parent);
_escherContainer = create();
}
public ShapeGroup(){ public ShapeGroup(){
this(null); this(null, null);
_escherContainer = createSpContainer(false);
} }
protected ShapeGroup(EscherContainerRecord escherRecord, Shape parent){ protected ShapeGroup(EscherContainerRecord escherRecord, Shape parent){
@ -91,7 +87,7 @@ public class ShapeGroup extends Shape{
/** /**
* Create a new ShapeGroup and create an instance of <code>EscherSpgrContainer</code> which represents a group of shapes * Create a new ShapeGroup and create an instance of <code>EscherSpgrContainer</code> which represents a group of shapes
*/ */
protected EscherContainerRecord create() { protected EscherContainerRecord createSpContainer(boolean isChild) {
EscherContainerRecord spgr = new EscherContainerRecord(); EscherContainerRecord spgr = new EscherContainerRecord();
spgr.setRecordId(EscherContainerRecord.SPGR_CONTAINER); spgr.setRecordId(EscherContainerRecord.SPGR_CONTAINER);
spgr.setOptions((short)15); spgr.setOptions((short)15);
@ -124,7 +120,7 @@ public class ShapeGroup extends Shape{
* @param shape - the Shape to add * @param shape - the Shape to add
*/ */
public void addShape(Shape shape){ public void addShape(Shape shape){
_escherContainer.addChildRecord(shape.getShapeRecord()); _escherContainer.addChildRecord(shape.getSpContainer());
} }
/** /**

View File

@ -158,7 +158,7 @@ public abstract class Sheet
EscherContainerRecord dgContainer = (EscherContainerRecord)ppdrawing.getEscherRecords()[0]; EscherContainerRecord dgContainer = (EscherContainerRecord)ppdrawing.getEscherRecords()[0];
EscherContainerRecord spgr = (EscherContainerRecord)Shape.getEscherChild(dgContainer, EscherContainerRecord.SPGR_CONTAINER); 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); EscherDgRecord dg = (EscherDgRecord)Shape.getEscherChild(dgContainer, EscherDgRecord.RECORD_ID);
dg.setNumShapes(dg.getNumShapes()+1); dg.setNumShapes(dg.getNumShapes()+1);

View File

@ -39,10 +39,10 @@ public class SimpleShape extends Shape {
* @param isChild <code>true</code> if the Line is inside a group, <code>false</code> otherwise * @param isChild <code>true</code> if the Line is inside a group, <code>false</code> otherwise
* @return the record container which holds this shape * @return the record container which holds this shape
*/ */
protected EscherContainerRecord create(boolean isChild) { protected EscherContainerRecord createSpContainer(boolean isChild) {
EscherContainerRecord spContainer = new EscherContainerRecord(); EscherContainerRecord spContainer = new EscherContainerRecord();
spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER );
//spContainer.setOptions((short)15); spContainer.setOptions((short)15);
EscherSpRecord sp = new EscherSpRecord(); EscherSpRecord sp = new EscherSpRecord();
int flags = EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE; int flags = EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE;

View File

@ -36,6 +36,7 @@ public class Document extends PositionDependentRecordContainer
// Links to our more interesting children // Links to our more interesting children
private DocumentAtom documentAtom; private DocumentAtom documentAtom;
private Environment environment; private Environment environment;
private PPDrawingGroup ppDrawing;
private SlideListWithText[] slwts; private SlideListWithText[] slwts;
/** /**
@ -47,6 +48,11 @@ public class Document extends PositionDependentRecordContainer
* settings for the document in it * settings for the document in it
*/ */
public Environment getEnvironment() { return environment; } 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 * Returns all the SlideListWithTexts that are defined for
* this Document. They hold the text, and some of the text * 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) { if(_children[i] instanceof Environment) {
environment = (Environment)_children[i]; environment = (Environment)_children[i];
} }
if(_children[i] instanceof PPDrawingGroup) {
ppDrawing = (PPDrawingGroup)_children[i];
}
} }
// Now grab them all // Now grab them all
slwts = new SlideListWithText[slwtcount]; slwts = new SlideListWithText[slwtcount];

View File

@ -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;
}
}

View File

@ -60,7 +60,7 @@ public class RecordTypes {
public static final Type SorterViewInfo = new Type(1032,null); public static final Type SorterViewInfo = new Type(1032,null);
public static final Type ExObjList = new Type(1033,null); public static final Type ExObjList = new Type(1033,null);
public static final Type ExObjListAtom = new Type(1034,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 PPDrawing = new Type(1036,PPDrawing.class);
public static final Type NamedShows = new Type(1040,null); public static final Type NamedShows = new Type(1040,null);
public static final Type NamedShow = new Type(1041,null); public static final Type NamedShow = new Type(1041,null);

View File

@ -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.
* <p>
* The information about an image in PowerPoint document is stored in
* two places:
* <li> EscherBSE container in the Document keeps information about image
* type, image index to refer by slides etc.
* <li> "Pictures" OLE stream holds the actual data of the image.
* </p>
* <p>
* Data in the "Pictures" OLE stream is organized as follows:<br>
* 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.<br>
* Header format:
* <li> 2 byte: image type. For JPEGs it is 0x46A0, for PNG it is 0x6E00.
* <li> 2 byte: unknown.
* <li> 4 byte : image size + 17. Looks like shift from the end of
* header but why to add it to the image size?
* <li> next 16 bytes. Unique identifier of this image which is used by
* EscherBSE record.
* </p>
*
* @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;
}
}

View File

@ -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 <code>Picture<code> 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 <code>OutputStream</code>
*/
public void write(OutputStream out) throws IOException {
out.write(header);
out.write(pictdata);
}
}

View File

@ -23,6 +23,10 @@ import java.util.*;
import java.awt.Dimension; import java.awt.Dimension;
import java.io.*; 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.*;
import org.apache.poi.hslf.model.*; import org.apache.poi.hslf.model.*;
import org.apache.poi.hslf.record.Document; import org.apache.poi.hslf.record.Document;
@ -50,6 +54,7 @@ import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
* - handle Slide creation cleaner * - handle Slide creation cleaner
* *
* @author Nick Burch * @author Nick Burch
* @author Yegor kozlov
*/ */
public class SlideShow public class SlideShow
@ -74,6 +79,12 @@ public class SlideShow
// MetaSheets (eg masters) not yet supported // MetaSheets (eg masters) not yet supported
// private MetaSheets[] _msheets; // private MetaSheets[] _msheets;
/* ===============================================================
* Setup Code
* ===============================================================
*/
/** /**
* Constructs a Powerpoint document from the underlying * Constructs a Powerpoint document from the underlying
@ -86,7 +97,6 @@ public class SlideShow
// Get useful things from our base slideshow // Get useful things from our base slideshow
_hslfSlideShow = hslfSlideShow; _hslfSlideShow = hslfSlideShow;
_records = _hslfSlideShow.getRecords(); _records = _hslfSlideShow.getRecords();
byte[] _docstream = _hslfSlideShow.getUnderlyingBytes();
// Handle Parent-aware Reocrds // Handle Parent-aware Reocrds
for(int i=0; i<_records.length; i++) { for(int i=0; i<_records.length; i++) {
@ -100,6 +110,12 @@ public class SlideShow
buildSlidesAndNotes(); buildSlidesAndNotes();
} }
/**
* Constructs a new, empty, Powerpoint document.
*/
public SlideShow() throws IOException {
this(new HSLFSlideShow());
}
/** /**
* Find the records that are parent-aware, and tell them * 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 <code>Slide</code>. * Create a blank <code>Slide</code>.
* *
@ -476,63 +563,87 @@ public class SlideShow
} }
/** /**
* Writes out the slideshow file the is represented by an instance of * Adds a picture to this presentation and returns the associated index.
* this class *
* @param out The OutputStream to write to. * @param data picture data
* @throws IOException If there is an unexpected IOException from the passed * @param format the format of the picture. One of constans defined in the <code>Picture</code> class.
* in OutputStream * @return the index to this picture (1 based).
*/ */
public void write(OutputStream out) throws IOException { public int addPicture(byte[] data, int format) {
_hslfSlideShow.write(out); 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);
/** List child = dggContainer.getChildRecords();
* Returns an array of the most recent version of all the interesting for ( int i = 0; i < child.size(); i++ ) {
* records EscherRecord rec = (EscherRecord)child.get(i);
*/ if (rec.getRecordId() == EscherOptRecord.RECORD_ID){
public Record[] getMostRecentCoreRecords() { return _mostRecentCoreRecords; } 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();
* Returns an array of all the normal Slides found in the slideshow bse.setRecordId(EscherBSERecord.RECORD_ID);
*/ bse.setOptions( (short) ( 0x0002 | ( format << 4 ) ) );
public Slide[] getSlides() { return _slides; } bse.setSize(data.length + PictureData.HEADER_SIZE);
bse.setUid(uid);
bse.setBlipTypeMacOS((byte)format);
bse.setBlipTypeWin32((byte)format);
/** bse.setRef(1);
* Returns an array of all the normal Notes found in the slideshow bse.setOffset(offset);
*/
public Notes[] getNotes() { return _notes; }
/** bstore.addChildRecord(bse);
* Returns an array of all the meta Sheets (master sheets etc) int count = bstore.getChildRecords().size();
* found in the slideshow bstore.setOptions((short)( (count << 4) | 0xF ));
*/
//public MetaSheet[] getMetaSheets() { return _msheets; }
/** PictureData pict = new PictureData();
* Returns all the pictures attached to the SlideShow pict.setUID(uid);
*/ pict.setData(data);
public Picture[] getPictures() throws IOException { pict.setType(format);
return _hslfSlideShow.getPictures();
} _hslfSlideShow.addPicture(pict);
/** return count;
* Return the current page size }
*/
public Dimension getPageSize(){ /**
DocumentAtom docatom = _documentRecord.getDocumentAtom(); * Adds a picture to this presentation and returns the associated index.
return new Dimension((int)docatom.getSlideSizeX(), (int)docatom.getSlideSizeY()); *
} * @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).
* Helper method for usermodel: Get the font collection */
*/ public int addPicture(File pict, int format) {
protected FontCollection getFontCollection() { return _fonts; } int length = (int)pict.length();
/** byte[] data = new byte[length];
* Helper method for usermodel: Get the document record try {
*/ FileInputStream is = new FileInputStream(pict);
protected Document getDocumentRecord() { return _documentRecord; } is.read(data);
is.close();
} catch (IOException e){
throw new RuntimeException(e);
}
return addPicture(data, format);
}
} }