diff --git a/src/java/org/apache/poi/hssf/model/AbstractShape.java b/src/java/org/apache/poi/hssf/model/AbstractShape.java new file mode 100644 index 000000000..522220184 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/AbstractShape.java @@ -0,0 +1,126 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.usermodel.*; + +/** + * An abstract shape is the lowlevel model for a shape. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public abstract class AbstractShape +{ + /** + * Create a new shape object used to create the escher records. + * + * @param hssfShape The simple shape this is based on. + */ + public static AbstractShape createShape( HSSFShape hssfShape, int shapeId ) + { + AbstractShape shape = null; + if (hssfShape instanceof HSSFTextbox) + { + shape = new TextboxShape( (HSSFTextbox)hssfShape, shapeId ); + } + else if (hssfShape instanceof HSSFPolygon) + { + shape = new PolygonShape( (HSSFPolygon) hssfShape, shapeId ); + } + else if (hssfShape instanceof HSSFSimpleShape) + { + HSSFSimpleShape simpleShape = (HSSFSimpleShape) hssfShape; + switch ( simpleShape.getShapeType() ) + { + case HSSFSimpleShape.OBJECT_TYPE_LINE: + shape = new LineShape( simpleShape, shapeId ); + break; + case HSSFSimpleShape.OBJECT_TYPE_OVAL: + case HSSFSimpleShape.OBJECT_TYPE_RECTANGLE: + shape = new SimpleFilledShape( simpleShape, shapeId ); + break; + default: + throw new IllegalArgumentException("Do not know how to handle this type of shape"); + } + } + else + { + throw new IllegalArgumentException("Unknown shape type"); + } + EscherSpRecord sp = shape.getSpContainer().getChildById(EscherSpRecord.RECORD_ID); + if (hssfShape.getParent() != null) + sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_CHILD); + return shape; + } + + protected AbstractShape() + { + } + + /** + * @return The shape container and it's children that can represent this + * shape. + */ + public abstract EscherContainerRecord getSpContainer(); + + /** + * @return The object record that is associated with this shape. + */ + public abstract ObjRecord getObjRecord(); + + /** + * Creates an escher anchor record from a HSSFAnchor. + * + * @param userAnchor The high level anchor to convert. + * @return An escher anchor record. + */ + protected EscherRecord createAnchor( HSSFAnchor userAnchor ) + { + return ConvertAnchor.createAnchor(userAnchor); + } + + /** + * Add standard properties to the opt record. These properties effect + * all records. + * + * @param shape The user model shape. + * @param opt The opt record to add the properties to. + * @return The number of options added. + */ + protected int addStandardOptions( HSSFShape shape, EscherOptRecord opt ) + { + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 0x080000 ) ); +// opt.addEscherProperty( new EscherBoolProperty( EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 0x080008 ) ); + if ( shape.isNoFill() ) + { + // Wonderful... none of the spec's give any clue as to what these constants mean. + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.FILL__NOFILLHITTEST, 0x00110000 ) ); + } + else + { + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.FILL__NOFILLHITTEST, 0x00010000 ) ); + } + opt.addEscherProperty( new EscherRGBProperty( EscherProperties.FILL__FILLCOLOR, shape.getFillColor() ) ); + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.GROUPSHAPE__PRINT, 0x080000 ) ); + opt.addEscherProperty( new EscherRGBProperty( EscherProperties.LINESTYLE__COLOR, shape.getLineStyleColor() ) ); + int options = 5; + if (shape.getLineWidth() != HSSFShape.LINEWIDTH_DEFAULT) + { + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.LINESTYLE__LINEWIDTH, shape.getLineWidth())); + options++; + } + if (shape.getLineStyle() != HSSFShape.LINESTYLE_SOLID) + { + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.LINESTYLE__LINEDASHING, shape.getLineStyle())); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.LINESTYLE__LINEENDCAPSTYLE, 0)); + if (shape.getLineStyle() == HSSFShape.LINESTYLE_NONE) + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080000)); + else + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080008)); + options += 3; + } + opt.sortProperties(); + return options; // # options added + } + +} diff --git a/src/java/org/apache/poi/hssf/model/ConvertAnchor.java b/src/java/org/apache/poi/hssf/model/ConvertAnchor.java new file mode 100644 index 000000000..bb07a121a --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/ConvertAnchor.java @@ -0,0 +1,50 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.ddf.EscherClientAnchorRecord; +import org.apache.poi.ddf.EscherChildAnchorRecord; +import org.apache.poi.hssf.usermodel.HSSFAnchor; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFChildAnchor; + +/** + * $Id$ + */ +public class ConvertAnchor +{ + public static EscherRecord createAnchor( HSSFAnchor userAnchor ) + { + if (userAnchor instanceof HSSFClientAnchor) + { + HSSFClientAnchor a = (HSSFClientAnchor) userAnchor; + + EscherClientAnchorRecord anchor = new EscherClientAnchorRecord(); + anchor.setRecordId( EscherClientAnchorRecord.RECORD_ID ); + anchor.setOptions( (short) 0x0000 ); + anchor.setFlag( (short) 0 ); + anchor.setCol1( (short) Math.min(a.getCol1(), a.getCol2()) ); + anchor.setDx1( (short) Math.min(a.getDx1(), a.getDx2()) ); + anchor.setRow1( (short) Math.min(a.getRow1(), a.getRow2()) ); + anchor.setDy1( (short) Math.min(a.getDy1(), a.getDy2()) ); + + anchor.setCol2( (short) Math.max(a.getCol1(), a.getCol2()) ); + anchor.setDx2( (short) Math.max(a.getDx1(), a.getDx2()) ); + anchor.setRow2( (short) Math.max(a.getRow1(), a.getRow2()) ); + anchor.setDy2( (short) Math.max(a.getDy1(), a.getDy2() ) ); + return anchor; + } + else + { + HSSFChildAnchor a = (HSSFChildAnchor) userAnchor; + EscherChildAnchorRecord anchor = new EscherChildAnchorRecord(); + anchor.setRecordId( EscherChildAnchorRecord.RECORD_ID ); + anchor.setOptions( (short) 0x0000 ); + anchor.setDx1( (short) Math.min(a.getDx1(), a.getDx2()) ); + anchor.setDy1( (short) Math.min(a.getDy1(), a.getDy2()) ); + anchor.setDx2( (short) Math.max(a.getDx2(), a.getDx1()) ); + anchor.setDy2( (short) Math.max(a.getDy2(), a.getDy1()) ); + return anchor; + } + } + +} diff --git a/src/java/org/apache/poi/hssf/model/DrawingManager.java b/src/java/org/apache/poi/hssf/model/DrawingManager.java new file mode 100644 index 000000000..585f6b90b --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/DrawingManager.java @@ -0,0 +1,136 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.EscherDggRecord; +import org.apache.poi.ddf.EscherDgRecord; + +import java.util.Map; +import java.util.HashMap; + +/** + * Provides utilities to manage drawing groups. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class DrawingManager +{ + EscherDggRecord dgg; + Map dgMap = new HashMap(); // key = Short(drawingId), value=EscherDgRecord + + public DrawingManager( EscherDggRecord dgg ) + { + this.dgg = dgg; + } + + public EscherDgRecord createDgRecord() + { + EscherDgRecord dg = new EscherDgRecord(); + dg.setRecordId( EscherDgRecord.RECORD_ID ); + short dgId = findNewDrawingGroupId(); + dg.setOptions( (short) ( dgId << 4 ) ); + dg.setNumShapes( 0 ); + dg.setLastMSOSPID( -1 ); + dgg.addCluster( dgId, 0 ); + dgg.setDrawingsSaved( dgg.getDrawingsSaved() + 1 ); + dgMap.put( new Short( dgId ), dg ); + return dg; + } + + /** + * Allocates new shape id for the new drawing group id. + * + * @return a new shape id. + */ + public int allocateShapeId(short drawingGroupId) + { + // Get the last shape id for this drawing group. + EscherDgRecord dg = (EscherDgRecord) dgMap.get(new Short(drawingGroupId)); + int lastShapeId = dg.getLastMSOSPID(); + + + // Have we run out of shapes for this cluster? + int newShapeId = 0; + if (lastShapeId % 1024 == 1023) + { + // Yes: + // Find the starting shape id of the next free cluster + newShapeId = findFreeSPIDBlock(); + // Create a new cluster in the dgg record. + dgg.addCluster(drawingGroupId, 1); + } + else + { + // No: + // Find the cluster for this drawing group with free space. + for (int i = 0; i < dgg.getFileIdClusters().length; i++) + { + EscherDggRecord.FileIdCluster c = dgg.getFileIdClusters()[i]; + if (c.getDrawingGroupId() == drawingGroupId) + { + if (c.getNumShapeIdsUsed() != 1024) + { + // Increment the number of shapes used for this cluster. + c.incrementShapeId(); + } + } + // If the last shape id = -1 then we know to find a free block; + if (dg.getLastMSOSPID() == -1) + { + newShapeId = findFreeSPIDBlock(); + } + else + { + // The new shape id to be the last shapeid of this cluster + 1 + newShapeId = dg.getLastMSOSPID() + 1; + } + } + } + // Increment the total number of shapes used in the dgg. + dgg.setNumShapesSaved(dgg.getNumShapesSaved() + 1); + // Is the new shape id >= max shape id for dgg? + if (newShapeId >= dgg.getShapeIdMax()) + { + // Yes: + // Set the max shape id = new shape id + 1 + dgg.setShapeIdMax(newShapeId + 1); + } + // Set last shape id for this drawing group. + dg.setLastMSOSPID(newShapeId); + // Increased the number of shapes used for this drawing group. + dg.incrementShapeCount(); + + + return newShapeId; + } + + //////////// Non-public methods ///////////// + short findNewDrawingGroupId() + { + short dgId = 1; + while ( drawingGroupExists( dgId ) ) + dgId++; + return dgId; + } + + boolean drawingGroupExists( short dgId ) + { + for ( int i = 0; i < dgg.getFileIdClusters().length; i++ ) + { + if ( dgg.getFileIdClusters()[i].getDrawingGroupId() == dgId ) + return true; + } + return false; + } + + int findFreeSPIDBlock() + { + int max = dgg.getShapeIdMax(); + int next = ( ( max / 1024 ) + 1 ) * 1024; + return next; + } + + public EscherDggRecord getDgg() + { + return dgg; + } + +} diff --git a/src/java/org/apache/poi/hssf/model/LineShape.java b/src/java/org/apache/poi/hssf/model/LineShape.java new file mode 100644 index 000000000..0b4e4d1e5 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/LineShape.java @@ -0,0 +1,105 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.usermodel.*; + +/** + * Represents a line shape and creates all the line specific low level records. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class LineShape + extends AbstractShape +{ + private EscherContainerRecord spContainer; + private ObjRecord objRecord; + + /** + * Creates the line shape from the highlevel user shape. All low level + * records are created at this point. + * + * @param hssfShape The user model shape. + * @param shapeId The identifier to use for this shape. + */ + LineShape( HSSFSimpleShape hssfShape, int shapeId ) + { + spContainer = createSpContainer(hssfShape, shapeId); + objRecord = createObjRecord(hssfShape, shapeId); + } + + /** + * Creates the lowerlevel escher records for this shape. + */ + private EscherContainerRecord createSpContainer(HSSFSimpleShape hssfShape, int shapeId) + { + HSSFShape shape = hssfShape; + + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherRecord anchor = new EscherClientAnchorRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + sp.setOptions( (short) ( (EscherAggregate.ST_LINE << 4) | 0x2 ) ); + + sp.setShapeId( shapeId ); + sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + opt.setRecordId( EscherOptRecord.RECORD_ID ); + opt.addEscherProperty( new EscherShapePathProperty( EscherProperties.GEOMETRY__SHAPEPATH, EscherShapePathProperty.COMPLEX ) ); + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 1048592 ) ); + addStandardOptions(shape, opt); + HSSFAnchor userAnchor = shape.getAnchor(); + if (userAnchor.isHorizontallyFlipped()) + sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPHORIZ); + if (userAnchor.isVerticallyFlipped()) + sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPVERT); + anchor = createAnchor(userAnchor); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) 0x0000 ); + + spContainer.addChildRecord(sp); + spContainer.addChildRecord(opt); + spContainer.addChildRecord(anchor); + spContainer.addChildRecord(clientData); + + return spContainer; + } + + /** + * Creates the low level OBJ record for this shape. + */ + private ObjRecord createObjRecord(HSSFShape hssfShape, int shapeId) + { + HSSFShape shape = hssfShape; + + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType((short) ((HSSFSimpleShape)shape).getShapeType()); + c.setObjectId((short) ( shapeId )); + c.setLocked(true); + c.setPrintable(true); + c.setAutofill(true); + c.setAutoline(true); + EndSubRecord e = new EndSubRecord(); + + obj.addSubRecord(c); + obj.addSubRecord(e); + + return obj; + } + + public EscherContainerRecord getSpContainer() + { + return spContainer; + } + + public ObjRecord getObjRecord() + { + return objRecord; + } + +} diff --git a/src/java/org/apache/poi/hssf/model/PolygonShape.java b/src/java/org/apache/poi/hssf/model/PolygonShape.java new file mode 100644 index 000000000..fccc92e6a --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/PolygonShape.java @@ -0,0 +1,143 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.EndSubRecord; +import org.apache.poi.hssf.usermodel.HSSFSimpleShape; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFPolygon; +import org.apache.poi.util.LittleEndian; + +public class PolygonShape + extends AbstractShape +{ + public final static short OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING = 30; + + private EscherContainerRecord spContainer; + private ObjRecord objRecord; + + /** + * Creates the low evel records for an polygon. + * + * @param hssfShape The highlevel shape. + * @param shapeId The shape id to use for this shape. + */ + PolygonShape( HSSFPolygon hssfShape, int shapeId ) + { + spContainer = createSpContainer( hssfShape, shapeId ); + objRecord = createObjRecord( hssfShape, shapeId ); + } + + /** + * Generates the shape records for this shape. + * + */ + private EscherContainerRecord createSpContainer( HSSFPolygon hssfShape, int shapeId ) + { + HSSFShape shape = hssfShape; + + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + sp.setOptions( (short) ( ( EscherAggregate.ST_DONUT << 4 ) | 0x2 ) ); + sp.setShapeId( shapeId ); + if (hssfShape.getParent() == null) + sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + else + sp.setFlags( EscherSpRecord.FLAG_CHILD | EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + opt.setRecordId( EscherOptRecord.RECORD_ID ); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TRANSFORM__ROTATION, false, false, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__RIGHT, false, false, hssfShape.getDrawAreaWidth())); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__BOTTOM, false, false, hssfShape.getDrawAreaHeight())); + opt.addEscherProperty(new EscherShapePathProperty(EscherProperties.GEOMETRY__SHAPEPATH, EscherShapePathProperty.COMPLEX)); + EscherArrayProperty verticesProp = new EscherArrayProperty(EscherProperties.GEOMETRY__VERTICES, false, new byte[0] ); + verticesProp.setNumberOfElementsInArray(hssfShape.getXPoints().length+1); + verticesProp.setNumberOfElementsInMemory(hssfShape.getXPoints().length+1); + verticesProp.setSizeOfElements(0xFFF0); + for (int i = 0; i < hssfShape.getXPoints().length; i++) + { + byte[] data = new byte[4]; + LittleEndian.putShort(data, 0, (short)hssfShape.getXPoints()[i]); + LittleEndian.putShort(data, 2, (short)hssfShape.getYPoints()[i]); + verticesProp.setElement(i, data); + } + int point = hssfShape.getXPoints().length; + byte[] data = new byte[4]; + LittleEndian.putShort(data, 0, (short)hssfShape.getXPoints()[0]); + LittleEndian.putShort(data, 2, (short)hssfShape.getYPoints()[0]); + verticesProp.setElement(point, data); + opt.addEscherProperty(verticesProp); + EscherArrayProperty segmentsProp = new EscherArrayProperty(EscherProperties.GEOMETRY__SEGMENTINFO, false, null ); + segmentsProp.setSizeOfElements(0x0002); + segmentsProp.setNumberOfElementsInArray(hssfShape.getXPoints().length * 2 + 4); + segmentsProp.setNumberOfElementsInMemory(hssfShape.getXPoints().length * 2 + 4); + segmentsProp.setElement(0, new byte[] { (byte)0x00, (byte)0x40 } ); + segmentsProp.setElement(1, new byte[] { (byte)0x00, (byte)0xAC } ); + for (int i = 0; i < hssfShape.getXPoints().length; i++) + { + segmentsProp.setElement(2 + i * 2, new byte[] { (byte)0x01, (byte)0x00 } ); + segmentsProp.setElement(3 + i * 2, new byte[] { (byte)0x00, (byte)0xAC } ); + } + segmentsProp.setElement(segmentsProp.getNumberOfElementsInArray() - 2, new byte[] { (byte)0x01, (byte)0x60 } ); + segmentsProp.setElement(segmentsProp.getNumberOfElementsInArray() - 1, new byte[] { (byte)0x00, (byte)0x80 } ); + opt.addEscherProperty(segmentsProp); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__FILLOK, false, false, 0x00010001)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINESTARTARROWHEAD, false, false, 0x0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEENDARROWHEAD, false, false, 0x0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEENDCAPSTYLE, false, false, 0x0)); + + addStandardOptions(shape, opt); + + EscherRecord anchor = createAnchor( shape.getAnchor() ); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) 0x0000 ); + + spContainer.addChildRecord( sp ); + spContainer.addChildRecord( opt ); + spContainer.addChildRecord( anchor ); + spContainer.addChildRecord( clientData ); + + return spContainer; + } + + /** + * Creates the low level OBJ record for this shape. + */ + private ObjRecord createObjRecord( HSSFShape hssfShape, int shapeId ) + { + HSSFShape shape = hssfShape; + + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType( OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING ); + c.setObjectId( (short) ( shapeId ) ); + c.setLocked( true ); + c.setPrintable( true ); + c.setAutofill( true ); + c.setAutoline( true ); + EndSubRecord e = new EndSubRecord(); + + obj.addSubRecord( c ); + obj.addSubRecord( e ); + + return obj; + } + + public EscherContainerRecord getSpContainer() + { + return spContainer; + } + + public ObjRecord getObjRecord() + { + return objRecord; + } + +} diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index b55c7422b..be0569f74 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -55,16 +55,18 @@ package org.apache.poi.hssf.model; -import java.util.List; +import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; +import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; +import org.apache.poi.hssf.record.aggregates.ValueRecordsAggregate; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.util.IntList; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + import java.util.ArrayList; import java.util.Iterator; - -import org.apache.poi.hssf - .record.*; // normally I don't do this, buy we literally mean ALL -import org.apache.poi.hssf.record.formula.Ptg; -import org.apache.poi.util.*; -import org.apache.poi.hssf.record - .aggregates.*; // normally I don't do this, buy we literally mean ALL +import java.util.List; /** * Low level model implementation of a Sheet (one workbook contains many sheets) @@ -795,12 +797,17 @@ public class Sheet implements Model // System.arraycopy(rec, 0, data, offset + pos, rec.length); Record record = (( Record ) records.get(k)); - //uncomment to test record sizes + //// uncomment to test record sizes //// +// System.out.println( record.getClass().getName() ); // byte[] data2 = new byte[record.getRecordSize()]; // record.serialize(0, data2 ); // rec.length; // if (LittleEndian.getUShort(data2, 2) != record.getRecordSize() - 4 -// && record instanceof RowRecordsAggregate == false && record instanceof ValueRecordsAggregate == false) -// throw new RuntimeException("Blah!!!"); +// && record instanceof RowRecordsAggregate == false +// && record instanceof ValueRecordsAggregate == false +// && record instanceof EscherAggregate == false) +// { +// throw new RuntimeException("Blah!!! Size off by " + ( LittleEndian.getUShort(data2, 2) - record.getRecordSize() - 4) + " records."); +// } pos += record.serialize(pos + offset, data ); // rec.length; @@ -2609,7 +2616,7 @@ public class Sheet implements Model * @return whether RowColHeadings are displayed */ public boolean isDisplayRowColHeadings() { - return windowTwo.getDisplayRowColHeadings(); + return windowTwo.getDisplayRowColHeadings(); } /** @@ -2620,9 +2627,63 @@ public class Sheet implements Model protected Margin[] getMargins() { if (margins == null) margins = new Margin[4]; - return margins; + return margins; } - + + public int aggregateDrawingRecords(DrawingManager drawingManager) + { + int loc = findFirstRecordLocBySid(DrawingRecord.sid); + boolean noDrawingRecordsFound = loc == -1; + if (noDrawingRecordsFound) + { + EscherAggregate aggregate = new EscherAggregate( drawingManager ); + loc = findFirstRecordLocBySid(EscherAggregate.sid); + if (loc == -1) + { + loc = findFirstRecordLocBySid( WindowTwoRecord.sid ); + } + else + { + getRecords().remove(loc); + } + getRecords().add( loc, aggregate ); + return loc; + } + else + { + List records = getRecords(); + EscherAggregate r = EscherAggregate.createAggregate( records, loc, drawingManager ); + int startloc = loc; + while ( loc + 1 < records.size() + && records.get( loc ) instanceof DrawingRecord + && records.get( loc + 1 ) instanceof ObjRecord ) + { + loc += 2; + } + int endloc = loc-1; + for(int i = 0; i < (endloc - startloc + 1); i++) + records.remove(startloc); + records.add(startloc, r); + + return startloc; + } + } + + /** + * Perform any work necessary before the sheet is about to be serialized. + * For instance the escher aggregates size needs to be calculated before + * serialization so that the dgg record (which occurs first) can be written. + */ + public void preSerialize() + { + for ( Iterator iterator = getRecords().iterator(); iterator.hasNext(); ) + { + Record r = (Record) iterator.next(); + if (r instanceof EscherAggregate) + r.getRecordSize(); // Trigger flatterning of user model and corresponding update of dgg record. + } + } + /** * Shifts all the page breaks in the range "count" number of rows/columns * @param breaks The page record to be shifted diff --git a/src/java/org/apache/poi/hssf/model/SimpleFilledShape.java b/src/java/org/apache/poi/hssf/model/SimpleFilledShape.java new file mode 100644 index 000000000..dadb02d79 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/SimpleFilledShape.java @@ -0,0 +1,111 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.EndSubRecord; +import org.apache.poi.hssf.usermodel.HSSFSimpleShape; +import org.apache.poi.hssf.usermodel.HSSFShape; + +public class SimpleFilledShape + extends AbstractShape +{ + private EscherContainerRecord spContainer; + private ObjRecord objRecord; + + /** + * Creates the low evel records for an oval. + * + * @param hssfShape The highlevel shape. + * @param shapeId The shape id to use for this shape. + */ + SimpleFilledShape( HSSFSimpleShape hssfShape, int shapeId ) + { + spContainer = createSpContainer( hssfShape, shapeId ); + objRecord = createObjRecord( hssfShape, shapeId ); + } + + /** + * Generates the shape records for this shape. + * + * @param hssfShape + * @param shapeId + * @return + */ + private EscherContainerRecord createSpContainer( HSSFSimpleShape hssfShape, int shapeId ) + { + HSSFShape shape = hssfShape; + + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + short shapeType = objTypeToShapeType( hssfShape.getShapeType() ); + sp.setOptions( (short) ( ( shapeType << 4 ) | 0x2 ) ); + sp.setShapeId( shapeId ); + sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + opt.setRecordId( EscherOptRecord.RECORD_ID ); + addStandardOptions(shape, opt); + EscherRecord anchor = createAnchor( shape.getAnchor() ); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) 0x0000 ); + + spContainer.addChildRecord( sp ); + spContainer.addChildRecord( opt ); + spContainer.addChildRecord( anchor ); + spContainer.addChildRecord( clientData ); + + return spContainer; + } + + private short objTypeToShapeType( int objType ) + { + short shapeType; + if (objType == HSSFSimpleShape.OBJECT_TYPE_OVAL) + shapeType = EscherAggregate.ST_ELLIPSE; + else if (objType == HSSFSimpleShape.OBJECT_TYPE_RECTANGLE) + shapeType = EscherAggregate.ST_RECTANGLE; + else + throw new IllegalArgumentException("Unable to handle an object of this type"); + return shapeType; + } + + /** + * Creates the low level OBJ record for this shape. + */ + private ObjRecord createObjRecord( HSSFShape hssfShape, int shapeId ) + { + HSSFShape shape = hssfShape; + + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType( (short) ( (HSSFSimpleShape) shape ).getShapeType() ); + c.setObjectId( (short) ( shapeId ) ); + c.setLocked( true ); + c.setPrintable( true ); + c.setAutofill( true ); + c.setAutoline( true ); + EndSubRecord e = new EndSubRecord(); + + obj.addSubRecord( c ); + obj.addSubRecord( e ); + + return obj; + } + + public EscherContainerRecord getSpContainer() + { + return spContainer; + } + + public ObjRecord getObjRecord() + { + return objRecord; + } + +} diff --git a/src/java/org/apache/poi/hssf/model/TextboxShape.java b/src/java/org/apache/poi/hssf/model/TextboxShape.java new file mode 100644 index 000000000..02143974d --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/TextboxShape.java @@ -0,0 +1,151 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.usermodel.*; + +/** + * Represents an textbox shape and converts between the highlevel records + * and lowlevel records for an oval. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TextboxShape + extends AbstractShape +{ + private EscherContainerRecord spContainer; + private TextObjectRecord textObjectRecord; + private ObjRecord objRecord; + private EscherTextboxRecord escherTextbox; + + /** + * Creates the low evel records for an textbox. + * + * @param hssfShape The highlevel shape. + * @param shapeId The shape id to use for this shape. + */ + TextboxShape( HSSFTextbox hssfShape, int shapeId ) + { + spContainer = createSpContainer( hssfShape, shapeId ); + objRecord = createObjRecord( hssfShape, shapeId ); + textObjectRecord = createTextObjectRecord( hssfShape, shapeId ); + } + + /** + * Creates the low level OBJ record for this shape. + */ + private ObjRecord createObjRecord( HSSFTextbox hssfShape, int shapeId ) + { + HSSFShape shape = hssfShape; + + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType( (short) ( (HSSFSimpleShape) shape ).getShapeType() ); + c.setObjectId( (short) ( shapeId ) ); + c.setLocked( true ); + c.setPrintable( true ); + c.setAutofill( true ); + c.setAutoline( true ); + EndSubRecord e = new EndSubRecord(); + + obj.addSubRecord( c ); + obj.addSubRecord( e ); + + return obj; + } + + /** + * Generates the escher shape records for this shape. + * + * @param hssfShape + * @param shapeId + * @return + */ + private EscherContainerRecord createSpContainer( HSSFTextbox hssfShape, int shapeId ) + { + HSSFTextbox shape = hssfShape; + + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherRecord anchor = new EscherClientAnchorRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + escherTextbox = new EscherTextboxRecord(); + + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + sp.setOptions( (short) ( ( EscherAggregate.ST_TEXTBOX << 4 ) | 0x2 ) ); + + sp.setShapeId( shapeId ); + sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + opt.setRecordId( EscherOptRecord.RECORD_ID ); + // opt.addEscherProperty( new EscherBoolProperty( EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 262144 ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.TEXT__TEXTID, 0 ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.TEXT__TEXTLEFT, shape.getMarginLeft() ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.TEXT__TEXTRIGHT, shape.getMarginRight() ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.TEXT__TEXTBOTTOM, shape.getMarginBottom() ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.TEXT__TEXTTOP, shape.getMarginTop() ) ); + addStandardOptions( shape, opt ); + HSSFAnchor userAnchor = shape.getAnchor(); + // if (userAnchor.isHorizontallyFlipped()) + // sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPHORIZ); + // if (userAnchor.isVerticallyFlipped()) + // sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPVERT); + anchor = createAnchor( userAnchor ); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) 0x0000 ); + escherTextbox.setRecordId( EscherTextboxRecord.RECORD_ID ); + escherTextbox.setOptions( (short) 0x0000 ); + + spContainer.addChildRecord( sp ); + spContainer.addChildRecord( opt ); + spContainer.addChildRecord( anchor ); + spContainer.addChildRecord( clientData ); + spContainer.addChildRecord( escherTextbox ); + + return spContainer; + } + + /** + * Textboxes also have an extra TXO record associated with them that most + * other shapes dont have. + */ + private TextObjectRecord createTextObjectRecord( HSSFTextbox hssfShape, int shapeId ) + { + HSSFTextbox shape = hssfShape; + + TextObjectRecord obj = new TextObjectRecord(); + obj.setHorizontalTextAlignment( TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED ); + obj.setVerticalTextAlignment( TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_TOP ); + obj.setTextLocked( true ); + obj.setTextOrientation( TextObjectRecord.TEXT_ORIENTATION_NONE ); + int frLength = ( shape.getString().numFormattingRuns() + 1 ) * 8; + obj.setFormattingRunLength( (short) frLength ); + obj.setTextLength( (short) shape.getString().length() ); + obj.setStr( shape.getString() ); + obj.setReserved7( 0 ); + + return obj; + } + + public EscherContainerRecord getSpContainer() + { + return spContainer; + } + + public ObjRecord getObjRecord() + { + return objRecord; + } + + public TextObjectRecord getTextObjectRecord() + { + return textObjectRecord; + } + + public EscherRecord getEscherTextbox() + { + return escherTextbox; + } +} diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index bcdcaa0f9..417c81458 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -58,8 +58,10 @@ package org.apache.poi.hssf.model; import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.hssf.util.SheetReferences; +import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; +import org.apache.poi.ddf.*; import java.util.ArrayList; import java.util.Iterator; @@ -91,7 +93,8 @@ import java.util.Locale; * @version 1.0-pre */ -public class Workbook implements Model { +public class Workbook implements Model +{ private static final int DEBUG = POILogger.DEBUG; // public static Workbook currentBook = null; @@ -133,6 +136,7 @@ public class Workbook implements Model { protected int numfonts = 0; // hold the number of font records private short maxformatid = -1; // holds the max format id private boolean uses1904datewindowing = false; // whether 1904 date windowing is being used + private DrawingManager drawingManager; private static POILogger log = POILogFactory.getLogger(Workbook.class); @@ -2080,7 +2084,57 @@ public class Workbook implements Model { } return palette; } - - + + /** + * Creates a drawing group record. If it already exists then it's left + * alone. + */ + public void createDrawingGroup() + { + int dggLoc = findFirstRecordLocBySid(EscherContainerRecord.DGG_CONTAINER); + if (dggLoc == -1) + { + EscherContainerRecord dggContainer = new EscherContainerRecord(); + EscherDggRecord dgg = new EscherDggRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherSplitMenuColorsRecord splitMenuColors = new EscherSplitMenuColorsRecord(); + + dggContainer.setRecordId((short) 0xF000); + dggContainer.setOptions((short) 0x000F); + dgg.setRecordId(EscherDggRecord.RECORD_ID); + dgg.setOptions((short)0x0000); + dgg.setShapeIdMax(1024); + dgg.setNumShapesSaved(0); + dgg.setDrawingsSaved(0); + dgg.setFileIdClusters(new EscherDggRecord.FileIdCluster[] {} ); + drawingManager = new DrawingManager(dgg); + opt.setRecordId((short) 0xF00B); + opt.setOptions((short) 0x0033); + opt.addEscherProperty( new EscherBoolProperty(EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 524296) ); + opt.addEscherProperty( new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, 134217737) ); + opt.addEscherProperty( new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, 134217792) ); + splitMenuColors.setRecordId((short) 0xF11E); + splitMenuColors.setOptions((short) 0x0040); + splitMenuColors.setColor1(0x0800000D); + splitMenuColors.setColor2(0x0800000C); + splitMenuColors.setColor3(0x08000017); + splitMenuColors.setColor4(0x100000F7); + + dggContainer.addChildRecord(dgg); + dggContainer.addChildRecord(opt); + dggContainer.addChildRecord(splitMenuColors); + + DrawingGroupRecord drawingGroup = new DrawingGroupRecord(); + drawingGroup.addEscherRecord(dggContainer); + int loc = findFirstRecordLocBySid(CountryRecord.sid); + getRecords().add(loc+1, drawingGroup); + } + } + + public DrawingManager getDrawingManager() + { + return drawingManager; + } + }