From 4c43845491810a4c14bdd331d8988a8d594a20c7 Mon Sep 17 00:00:00 2001 From: Evgeniy Berlog Date: Sun, 1 Jul 2012 09:38:08 +0000 Subject: [PATCH] implemented work with existing shape groups and polygons git-svn-id: https://svn.apache.org/repos/asf/poi/branches/gsoc2012@1355866 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/ddf/EscherSpRecord.java | 2 +- .../apache/poi/hssf/model/PolygonShape.java | 2 +- .../poi/hssf/usermodel/HSSFComment.java | 5 - .../poi/hssf/usermodel/HSSFPatriarch.java | 2 + .../poi/hssf/usermodel/HSSFPolygon.java | 197 +++++++++++---- .../apache/poi/hssf/usermodel/HSSFShape.java | 9 - .../poi/hssf/usermodel/HSSFShapeFactory.java | 16 +- .../poi/hssf/usermodel/HSSFShapeGroup.java | 206 +++++++++++----- .../poi/hssf/usermodel/HSSFSimpleShape.java | 8 +- .../poi/hssf/usermodel/HSSFTextbox.java | 6 + .../poi/hssf/model/HSSFTestModelHelper.java | 5 + .../poi/hssf/model/TestDrawingAggregate.java | 17 +- .../poi/hssf/usermodel/HSSFTestHelper.java | 53 ++++ .../poi/hssf/usermodel/TestPolygon.java | 231 ++++++++++++++++++ .../poi/hssf/usermodel/TestShapeGroup.java | 225 +++++++++++++++++ test-data/spreadsheet/drawings.xls | Bin 818688 -> 821760 bytes 16 files changed, 830 insertions(+), 154 deletions(-) create mode 100644 src/testcases/org/apache/poi/hssf/usermodel/TestPolygon.java create mode 100644 src/testcases/org/apache/poi/hssf/usermodel/TestShapeGroup.java diff --git a/src/java/org/apache/poi/ddf/EscherSpRecord.java b/src/java/org/apache/poi/ddf/EscherSpRecord.java index 11a893f46..7d4dd5cfb 100644 --- a/src/java/org/apache/poi/ddf/EscherSpRecord.java +++ b/src/java/org/apache/poi/ddf/EscherSpRecord.java @@ -119,7 +119,7 @@ public class EscherSpRecord public String toXml(String tab) { StringBuilder builder = new StringBuilder(); builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance()))) - .append(tab).append("\t").append("").append(HexDump.toHex(getShapeType())).append("\n") + .append(tab).append("\t").append("0x").append(HexDump.toHex(getShapeType())).append("\n") .append(tab).append("\t").append("").append(field_1_shapeId).append("\n") .append(tab).append("\t").append("").append(decodeFlags(field_2_flags) + " (0x" + HexDump.toHex(field_2_flags) + ")").append("\n"); builder.append(tab).append("\n"); diff --git a/src/java/org/apache/poi/hssf/model/PolygonShape.java b/src/java/org/apache/poi/hssf/model/PolygonShape.java index 02308bf48..22212c88d 100644 --- a/src/java/org/apache/poi/hssf/model/PolygonShape.java +++ b/src/java/org/apache/poi/hssf/model/PolygonShape.java @@ -63,7 +63,7 @@ public class PolygonShape spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); spContainer.setOptions( (short) 0x000F ); sp.setRecordId( EscherSpRecord.RECORD_ID ); - sp.setOptions( (short) ( ( EscherAggregate.ST_DONUT << 4 ) | 0x2 ) ); + sp.setOptions( (short) ( ( EscherAggregate.ST_NOT_PRIMATIVE << 4 ) | 0x2 ) ); sp.setShapeId( shapeId ); if (hssfShape.getParent() == null) sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java index 23a792f36..add7e3272 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java @@ -38,11 +38,6 @@ public class HSSFComment extends HSSFTextbox implements Comment { * It seems like HSSFRow should manage a collection of local HSSFComments */ - private boolean _visible; - private int _row; - private int _col; - private String _author; - private NoteRecord _note; public HSSFComment(EscherContainerRecord spContainer, ObjRecord objRecord, TextObjectRecord textObjectRecord, NoteRecord _note) { diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java index bc3eebc1b..675c7d7f8 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java @@ -73,6 +73,7 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { HSSFShapeGroup group = new HSSFShapeGroup(null, anchor); group.anchor = anchor; addShape(group); + onCreate(group); return group; } @@ -132,6 +133,7 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { HSSFPolygon shape = new HSSFPolygon(null, anchor); shape.anchor = anchor; addShape(shape); + onCreate(shape); return shape; } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java index 33f17d7e4..8d561a0ff 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java @@ -17,87 +17,186 @@ package org.apache.poi.hssf.usermodel; -import org.apache.poi.ddf.EscherContainerRecord; -import org.apache.poi.ddf.EscherOptRecord; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.EndSubRecord; +import org.apache.poi.hssf.record.EscherAggregate; import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.util.LittleEndian; /** * @author Glen Stampoultzis (glens at superlinksoftware.com) */ -public class HSSFPolygon - extends HSSFShape -{ - int[] xPoints; - int[] yPoints; - int drawAreaWidth = 100; - int drawAreaHeight = 100; +public class HSSFPolygon extends HSSFShape { - HSSFPolygon( HSSFShape parent, HSSFAnchor anchor ) - { - super( parent, anchor ); + public final static short OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING = 0x1E; + + public HSSFPolygon(EscherContainerRecord spContainer, ObjRecord objRecord) { + super(spContainer, objRecord); } - @Override + HSSFPolygon(HSSFShape parent, HSSFAnchor anchor) { + super(parent, anchor); + } + + /** + * Generates the shape records for this shape. + */ protected EscherContainerRecord createSpContainer() { EscherContainerRecord spContainer = new EscherContainerRecord(); - spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); - spContainer.setOptions( (short) 0x000F ); - EscherOptRecord optRecord = new EscherOptRecord(); - optRecord.setRecordId(EscherOptRecord.RECORD_ID); - spContainer.addChildRecord(optRecord); + 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_NOT_PRIMATIVE << 4) | 0x2)); + if (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.setEscherProperty(new EscherSimpleProperty(EscherProperties.TRANSFORM__ROTATION, false, false, 0)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__RIGHT, false, false, 100)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__BOTTOM, false, false, 100)); + opt.setEscherProperty(new EscherShapePathProperty(EscherProperties.GEOMETRY__SHAPEPATH, EscherShapePathProperty.COMPLEX)); + + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__FILLOK, false, false, 0x00010001)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINESTARTARROWHEAD, false, false, 0x0)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEENDARROWHEAD, false, false, 0x0)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEENDCAPSTYLE, false, false, 0x0)); + + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEDASHING, LINESTYLE_SOLID)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEWIDTH, LINEWIDTH_DEFAULT)); + opt.setEscherProperty(new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, FILL__FILLCOLOR_DEFAULT)); + opt.setEscherProperty(new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, LINESTYLE__COLOR_DEFAULT)); + opt.setEscherProperty(new EscherBoolProperty(EscherProperties.FILL__NOFILLHITTEST, 0x0)); + + EscherRecord anchor = getAnchor().getEscherAnchor(); + clientData.setRecordId(EscherClientDataRecord.RECORD_ID); + clientData.setOptions((short) 0x0000); + + spContainer.addChildRecord(sp); + spContainer.addChildRecord(opt); + spContainer.addChildRecord(anchor); + spContainer.addChildRecord(clientData); + return spContainer; } - @Override + /** + * Creates the low level OBJ record for this shape. + */ protected ObjRecord createObjRecord() { - return null; + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType(OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING); + 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 int[] getXPoints() - { - return xPoints; + public int[] getXPoints() { + EscherArrayProperty verticesProp = _optRecord.lookup(EscherProperties.GEOMETRY__VERTICES); + if (null == verticesProp){ + return new int[]{}; + } + int []array = new int[verticesProp.getNumberOfElementsInArray()-1]; + for (int i=0; i< verticesProp.getNumberOfElementsInArray()-1; i++){ + byte[] property = verticesProp.getElement(i); + short x = LittleEndian.getShort(property, 0); + array[i] = x; + } + return array; } - public int[] getYPoints() - { - return yPoints; + public int[] getYPoints() { + EscherArrayProperty verticesProp = _optRecord.lookup(EscherProperties.GEOMETRY__VERTICES); + if (null == verticesProp){ + return new int[]{}; + } + int []array = new int[verticesProp.getNumberOfElementsInArray()-1]; + for (int i=0; i< verticesProp.getNumberOfElementsInArray()-1; i++){ + byte[] property = verticesProp.getElement(i); + short x = LittleEndian.getShort(property, 2); + array[i] = x; + } + return array; } - public void setPoints(int[] xPoints, int[] yPoints) - { - this.xPoints = cloneArray(xPoints); - this.yPoints = cloneArray(yPoints); - } + public void setPoints(int[] xPoints, int[] yPoints) { + if (xPoints.length != yPoints.length){ + System.out.println("xPoint.length must be equal to yPoints.length"); + return; + } + if (xPoints.length == 0){ + System.out.println("HSSFPolygon must have at least one point"); + } + EscherArrayProperty verticesProp = new EscherArrayProperty(EscherProperties.GEOMETRY__VERTICES, false, new byte[0] ); + verticesProp.setNumberOfElementsInArray(xPoints.length+1); + verticesProp.setNumberOfElementsInMemory(xPoints.length+1); + verticesProp.setSizeOfElements(0xFFF0); + for (int i = 0; i < xPoints.length; i++) + { + byte[] data = new byte[4]; + LittleEndian.putShort(data, 0, (short)xPoints[i]); + LittleEndian.putShort(data, 2, (short)yPoints[i]); + verticesProp.setElement(i, data); + } + int point = xPoints.length; + byte[] data = new byte[4]; + LittleEndian.putShort(data, 0, (short)xPoints[0]); + LittleEndian.putShort(data, 2, (short)yPoints[0]); + verticesProp.setElement(point, data); + setPropertyValue(verticesProp); - private int[] cloneArray( int[] a ) - { - int[] result = new int[a.length]; - for ( int i = 0; i < a.length; i++ ) - result[i] = a[i]; - - return result; + EscherArrayProperty segmentsProp = new EscherArrayProperty(EscherProperties.GEOMETRY__SEGMENTINFO, false, null ); + segmentsProp.setSizeOfElements(0x0002); + segmentsProp.setNumberOfElementsInArray(xPoints.length * 2 + 4); + segmentsProp.setNumberOfElementsInMemory(xPoints.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 < xPoints.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 } ); + setPropertyValue(segmentsProp); } /** * Defines the width and height of the points in the polygon + * * @param width * @param height */ - public void setPolygonDrawArea( int width, int height ) - { - this.drawAreaWidth = width; - this.drawAreaHeight = height; + public void setPolygonDrawArea(int width, int height) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.GEOMETRY__RIGHT, width)); + setPropertyValue(new EscherSimpleProperty(EscherProperties.GEOMETRY__BOTTOM, height)); } - public int getDrawAreaWidth() - { - return drawAreaWidth; + public int getDrawAreaWidth() { + EscherSimpleProperty property = _optRecord.lookup(EscherProperties.GEOMETRY__RIGHT); + return property == null ? 100: property.getPropertyValue(); } - public int getDrawAreaHeight() - { - return drawAreaHeight; + public int getDrawAreaHeight() { + EscherSimpleProperty property = _optRecord.lookup(EscherProperties.GEOMETRY__BOTTOM); + return property == null ? 100: property.getPropertyValue(); } - + @Override + void afterInsert(HSSFPatriarch patriarch) { + EscherAggregate agg = patriarch._getBoundAggregate(); + agg.associateShapeToObjRecord(_escherContainer.getChildById(EscherClientDataRecord.RECORD_ID), getObjRecord()); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java index 9c2775373..09bf271fc 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java @@ -72,7 +72,6 @@ public abstract class HSSFShape { this.anchor = anchor; this._escherContainer = createSpContainer(); _optRecord = _escherContainer.getChildById(EscherOptRecord.RECORD_ID); - addStandardOptions(_optRecord); _objRecord = createObjRecord(); } @@ -87,14 +86,6 @@ public abstract class HSSFShape { CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) _objRecord.getSubRecords().get(0); cod.setObjectId((short) (shapeId-1024)); } - - private void addStandardOptions(EscherOptRecord optRecord){ - setPropertyValue(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEDASHING, LINESTYLE_SOLID)); - setPropertyValue(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEWIDTH, LINEWIDTH_DEFAULT)); - setPropertyValue(new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, FILL__FILLCOLOR_DEFAULT)); - setPropertyValue(new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, LINESTYLE__COLOR_DEFAULT)); - setPropertyValue(new EscherBoolProperty(EscherProperties.FILL__NOFILLHITTEST, 0x0)); - } int getShapeId(){ return ((EscherSpRecord)_escherContainer.getChildById(EscherSpRecord.RECORD_ID)).getShapeId(); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java index 5358f4868..1ee53171b 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java @@ -17,12 +17,7 @@ package org.apache.poi.hssf.usermodel; -import org.apache.poi.ddf.EscherClientDataRecord; -import org.apache.poi.ddf.EscherContainerRecord; -import org.apache.poi.ddf.EscherRecord; -import org.apache.poi.ddf.EscherSpRecord; -import org.apache.poi.ddf.EscherSpgrRecord; -import org.apache.poi.ddf.EscherTextboxRecord; +import org.apache.poi.ddf.*; import org.apache.poi.hssf.model.TextboxShape; import org.apache.poi.hssf.record.CommonObjectDataSubRecord; import org.apache.poi.hssf.record.EscherAggregate; @@ -124,6 +119,15 @@ public class HSSFShapeFactory { case CommonObjectDataSubRecord.OBJECT_TYPE_RECTANGLE: shape = new HSSFSimpleShape(container, objRecord); break; + case CommonObjectDataSubRecord.OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING: + EscherOptRecord optRecord = container.getChildById(EscherOptRecord.RECORD_ID); + EscherProperty property = optRecord.lookup(EscherProperties.GEOMETRY__VERTICES); + if (null != property){ + shape = new HSSFPolygon(container, objRecord); + } else { + shape = new HSSFSimpleShape(container, objRecord); + } + break; case CommonObjectDataSubRecord.OBJECT_TYPE_TEXT: shape = new HSSFTextbox(container, objRecord, txtRecord); break; diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java index d98471e33..eb023e0ca 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java @@ -19,8 +19,7 @@ package org.apache.poi.hssf.usermodel; import org.apache.poi.ddf.*; import org.apache.poi.hssf.model.TextboxShape; -import org.apache.poi.hssf.record.EscherAggregate; -import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.*; import java.util.ArrayList; import java.util.List; @@ -32,10 +31,7 @@ import java.util.Iterator; * * @author Glen Stampoultzis (glens at apache.org) */ -public class HSSFShapeGroup - extends HSSFShape - implements HSSFShapeContainer -{ +public class HSSFShapeGroup extends HSSFShape implements HSSFShapeContainer { List shapes = new ArrayList(); private EscherSpgrRecord _spgrRecord; @@ -45,24 +41,22 @@ public class HSSFShapeGroup // read internal and external coordinates from spgrContainer EscherContainerRecord spContainer = spgrContainer.getChildContainers().get(0); _spgrRecord = (EscherSpgrRecord) spContainer.getChild(0); - for(EscherRecord ch : spContainer.getChildRecords()){ - switch(ch.getRecordId()) { + for (EscherRecord ch : spContainer.getChildRecords()) { + switch (ch.getRecordId()) { case EscherSpgrRecord.RECORD_ID: break; case EscherClientAnchorRecord.RECORD_ID: - anchor = new HSSFClientAnchor((EscherClientAnchorRecord)ch); + anchor = new HSSFClientAnchor((EscherClientAnchorRecord) ch); break; case EscherChildAnchorRecord.RECORD_ID: - anchor = new HSSFChildAnchor((EscherChildAnchorRecord)ch); + anchor = new HSSFChildAnchor((EscherChildAnchorRecord) ch); break; } } - } - public HSSFShapeGroup( HSSFShape parent, HSSFAnchor anchor ) - { - super( parent, anchor ); + public HSSFShapeGroup(HSSFShape parent, HSSFAnchor anchor) { + super(parent, anchor); _spgrRecord = new EscherSpgrRecord(); _spgrRecord.setRectX1(0); _spgrRecord.setRectX2(1023); @@ -72,104 +66,169 @@ public class HSSFShapeGroup @Override protected EscherContainerRecord createSpContainer() { + EscherContainerRecord spgrContainer = new EscherContainerRecord(); EscherContainerRecord spContainer = new EscherContainerRecord(); - spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); - spContainer.setOptions( (short) 0x000F ); - EscherOptRecord optRecord = new EscherOptRecord(); - optRecord.setRecordId(EscherOptRecord.RECORD_ID); - spContainer.addChildRecord(optRecord); - return spContainer; + EscherSpgrRecord spgr = new EscherSpgrRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherRecord anchor; + EscherClientDataRecord clientData = new EscherClientDataRecord(); + + spgrContainer.setRecordId(EscherContainerRecord.SPGR_CONTAINER); + spgrContainer.setOptions((short) 0x000F); + spContainer.setRecordId(EscherContainerRecord.SP_CONTAINER); + spContainer.setOptions((short) 0x000F); + spgr.setRecordId(EscherSpgrRecord.RECORD_ID); + spgr.setOptions((short) 0x0001); + spgr.setRectX1(0); + spgr.setRectY1(0); + spgr.setRectX2(1023); + spgr.setRectY2(255); + sp.setRecordId(EscherSpRecord.RECORD_ID); + sp.setOptions((short) 0x0002); + if (getAnchor() instanceof HSSFClientAnchor) { + sp.setFlags(EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_HAVEANCHOR); + } else { + sp.setFlags(EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_CHILD); + } + opt.setRecordId(EscherOptRecord.RECORD_ID); + opt.setOptions((short) 0x0023); + opt.addEscherProperty(new EscherBoolProperty(EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 0x00040004)); + opt.addEscherProperty(new EscherBoolProperty(EscherProperties.GROUPSHAPE__PRINT, 0x00080000)); + + anchor = getAnchor().getEscherAnchor(); + clientData.setRecordId(EscherClientDataRecord.RECORD_ID); + clientData.setOptions((short) 0x0000); + + spgrContainer.addChildRecord(spContainer); + spContainer.addChildRecord(spgr); + spContainer.addChildRecord(sp); + spContainer.addChildRecord(opt); + spContainer.addChildRecord(anchor); + spContainer.addChildRecord(clientData); + return spgrContainer; } @Override protected ObjRecord createObjRecord() { - return null; //To change body of implemented methods use File | Settings | File Templates. + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord cmo = new CommonObjectDataSubRecord(); + cmo.setObjectType(CommonObjectDataSubRecord.OBJECT_TYPE_GROUP); + cmo.setLocked(true); + cmo.setPrintable(true); + cmo.setAutofill(true); + cmo.setAutoline(true); + GroupMarkerSubRecord gmo = new GroupMarkerSubRecord(); + EndSubRecord end = new EndSubRecord(); + obj.addSubRecord(cmo); + obj.addSubRecord(gmo); + obj.addSubRecord(end); + return obj; + } + + private void onCreate(HSSFShape shape){ + if(_patriarch != null && _patriarch._getBoundAggregate().getPatriarch() == null){ + EscherContainerRecord spContainer = shape.getEscherContainer(); + int shapeId = _patriarch.newShapeId(); + shape.setShapeId(shapeId); + _escherContainer.addChildRecord(spContainer); + shape.afterInsert(_patriarch); + } } /** * Create another group under this group. - * @param anchor the position of the new group. - * @return the group + * + * @param anchor the position of the new group. + * @return the group */ - public HSSFShapeGroup createGroup(HSSFChildAnchor anchor) - { + public HSSFShapeGroup createGroup(HSSFChildAnchor anchor) { HSSFShapeGroup group = new HSSFShapeGroup(this, anchor); + group.parent = this; group.anchor = anchor; shapes.add(group); + onCreate(group); return group; } - public void addShape(HSSFShape shape){ + public void addShape(HSSFShape shape) { shape._patriarch = this._patriarch; + shape.parent = this; shapes.add(shape); } - public void addTextBox(TextboxShape textboxShape){ + public void addTextBox(TextboxShape textboxShape) { // HSSFTextbox shape = new HSSFTextbox(this, textboxShape.geanchor); // shapes.add(textboxShape); } /** * Create a new simple shape under this group. - * @param anchor the position of the shape. - * @return the shape + * + * @param anchor the position of the shape. + * @return the shape */ - public HSSFSimpleShape createShape(HSSFChildAnchor anchor) - { + public HSSFSimpleShape createShape(HSSFChildAnchor anchor) { HSSFSimpleShape shape = new HSSFSimpleShape(this, anchor); + shape.parent = this; shape.anchor = anchor; shapes.add(shape); + onCreate(shape); return shape; } /** * Create a new textbox under this group. - * @param anchor the position of the shape. - * @return the textbox + * + * @param anchor the position of the shape. + * @return the textbox */ - public HSSFTextbox createTextbox(HSSFChildAnchor anchor) - { + public HSSFTextbox createTextbox(HSSFChildAnchor anchor) { HSSFTextbox shape = new HSSFTextbox(this, anchor); + shape.parent = this; shape.anchor = anchor; shapes.add(shape); + onCreate(shape); return shape; } /** * Creates a polygon * - * @param anchor the client anchor describes how this group is attached - * to the sheet. - * @return the newly created shape. + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created shape. */ - public HSSFPolygon createPolygon(HSSFChildAnchor anchor) - { + public HSSFPolygon createPolygon(HSSFChildAnchor anchor) { HSSFPolygon shape = new HSSFPolygon(this, anchor); + shape.parent = this; shape.anchor = anchor; shapes.add(shape); + onCreate(shape); return shape; } /** * Creates a picture. * - * @param anchor the client anchor describes how this group is attached - * to the sheet. - * @return the newly created shape. + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created shape. */ - public HSSFPicture createPicture(HSSFChildAnchor anchor, int pictureIndex) - { - HSSFPicture shape = new HSSFPicture(this, anchor); - shape.anchor = anchor; - shape.setPictureIndex( pictureIndex ); - shapes.add(shape); - return shape; + public HSSFPicture createPicture(HSSFChildAnchor anchor, int pictureIndex) { + HSSFPicture shape = new HSSFPicture(this, anchor); + shape.parent = this; + shape.anchor = anchor; + shape.setPictureIndex(pictureIndex); + shapes.add(shape); + onCreate(shape); + return shape; } + /** * Return all children contained by this shape. */ - public List getChildren() - { + public List getChildren() { return shapes; } @@ -177,8 +236,7 @@ public class HSSFShapeGroup * Sets the coordinate space of this group. All children are constrained * to these coordinates. */ - public void setCoordinates( int x1, int y1, int x2, int y2 ) - { + public void setCoordinates(int x1, int y1, int x2, int y2) { _spgrRecord.setRectX1(x1); _spgrRecord.setRectX2(x2); _spgrRecord.setRectY1(y1); @@ -188,46 +246,62 @@ public class HSSFShapeGroup /** * The top left x coordinate of this group. */ - public int getX1() - { + public int getX1() { return _spgrRecord.getRectX1(); } /** * The top left y coordinate of this group. */ - public int getY1() - { + public int getY1() { return _spgrRecord.getRectY1(); } /** * The bottom right x coordinate of this group. */ - public int getX2() - { + public int getX2() { return _spgrRecord.getRectX2(); } /** * The bottom right y coordinate of this group. */ - public int getY2() - { + public int getY2() { return _spgrRecord.getRectY2(); } /** * Count of all children and their childrens children. */ - public int countOfAllChildren() - { + public int countOfAllChildren() { int count = shapes.size(); - for ( Iterator iterator = shapes.iterator(); iterator.hasNext(); ) - { + for (Iterator iterator = shapes.iterator(); iterator.hasNext(); ) { HSSFShape shape = (HSSFShape) iterator.next(); count += shape.countOfAllChildren(); } return count; } + + @Override + void afterInsert(HSSFPatriarch patriarch){ + EscherAggregate agg = patriarch._getBoundAggregate(); + EscherContainerRecord containerRecord = _escherContainer.getChildById(EscherContainerRecord.SP_CONTAINER); + agg.associateShapeToObjRecord(containerRecord.getChildById(EscherClientDataRecord.RECORD_ID), getObjRecord()); + } + + @Override + void setShapeId(int shapeId){ + EscherContainerRecord containerRecord = _escherContainer.getChildById(EscherContainerRecord.SP_CONTAINER); + EscherSpRecord spRecord = containerRecord.getChildById(EscherSpRecord.RECORD_ID); + spRecord.setShapeId(shapeId); + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) _objRecord.getSubRecords().get(0); + cod.setObjectId((short) (shapeId-1024)); + } + + @Override + int getShapeId(){ + EscherContainerRecord containerRecord = _escherContainer.getChildById(EscherContainerRecord.SP_CONTAINER); + return ((EscherSpRecord)containerRecord.getChildById(EscherSpRecord.RECORD_ID)).getShapeId(); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java index 2b64faaa5..62b9d6a8e 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java @@ -32,8 +32,7 @@ import java.util.Map; * * @author Glen Stampoultzis (glens at apache.org) */ -public class HSSFSimpleShape - extends HSSFShape +public class HSSFSimpleShape extends HSSFShape { // The commented out ones haven't been tested yet or aren't supported // by HSSFSimpleShape. @@ -93,6 +92,11 @@ public class HSSFSimpleShape clientData.setOptions( (short) 0x0000 ); EscherOptRecord optRecord = new EscherOptRecord(); + optRecord.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEDASHING, LINESTYLE_SOLID)); + optRecord.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEWIDTH, LINEWIDTH_DEFAULT)); + optRecord.setEscherProperty(new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, FILL__FILLCOLOR_DEFAULT)); + optRecord.setEscherProperty(new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, LINESTYLE__COLOR_DEFAULT)); + optRecord.setEscherProperty(new EscherBoolProperty(EscherProperties.FILL__NOFILLHITTEST, 0x0)); optRecord.setRecordId( EscherOptRecord.RECORD_ID ); spContainer.addChildRecord(sp); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java index 42385a87e..1ed70f86a 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java @@ -118,6 +118,12 @@ public class HSSFTextbox extends HSSFSimpleShape { opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTTOP, 0)); opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTBOTTOM, 0)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEDASHING, LINESTYLE_SOLID)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEWIDTH, LINEWIDTH_DEFAULT)); + opt.setEscherProperty(new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, FILL__FILLCOLOR_DEFAULT)); + opt.setEscherProperty(new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, LINESTYLE__COLOR_DEFAULT)); + opt.setEscherProperty(new EscherBoolProperty(EscherProperties.FILL__NOFILLHITTEST, 0x0)); + EscherRecord anchor = getAnchor().getEscherAnchor(); clientData.setRecordId(EscherClientDataRecord.RECORD_ID); diff --git a/src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java b/src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java index 507561e7e..bc22d17a9 100644 --- a/src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java +++ b/src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java @@ -1,6 +1,7 @@ package org.apache.poi.hssf.model; import org.apache.poi.hssf.usermodel.HSSFComment; +import org.apache.poi.hssf.usermodel.HSSFPolygon; import org.apache.poi.hssf.usermodel.HSSFTextbox; /** @@ -15,4 +16,8 @@ public class HSSFTestModelHelper { public static CommentShape createCommentShape(int shapeId, HSSFComment comment){ return new CommentShape(comment, shapeId); } + + public static PolygonShape createPolygonShape(int shapeId, HSSFPolygon polygon){ + return new PolygonShape(polygon, shapeId); + } } diff --git a/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java b/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java index 18572d6ec..1f058c06d 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java +++ b/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java @@ -201,7 +201,7 @@ public class TestDrawingAggregate extends TestCase { HSSFPatriarch drawing = sheet.createDrawingPatriarch(); EscherAggregate agg1 = HSSFTestHelper.getEscherAggregate(drawing); - callConvertPatriarch(agg1); + HSSFTestHelper.callConvertPatriarch(agg1); agg1.setPatriarch(null); agg.setPatriarch(null); @@ -213,20 +213,7 @@ public class TestDrawingAggregate extends TestCase { assertTrue(Arrays.equals(aggS, agg1S)); } - private static void callConvertPatriarch(EscherAggregate agg) { - Method method = null; - try { - method = agg.getClass().getDeclaredMethod("convertPatriarch", HSSFPatriarch.class); - method.setAccessible(true); - method.invoke(agg, agg.getPatriarch()); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } catch (InvocationTargetException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } - } + /** * when reading incomplete data ensure that the serialized bytes diff --git a/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java b/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java index 9b2273c59..214121d94 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java @@ -16,17 +16,40 @@ ==================================================================== */ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherDggRecord; import org.apache.poi.ddf.EscherOptRecord; +import org.apache.poi.hssf.model.DrawingManager2; import org.apache.poi.hssf.model.InternalSheet; import org.apache.poi.hssf.model.InternalWorkbook; import org.apache.poi.hssf.record.EscherAggregate; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; + /** * Helper class for HSSF tests that aren't within the * HSSF UserModel package, but need to do internal * UserModel things. */ public class HSSFTestHelper { + + private static class MockDrawingManager extends DrawingManager2 { +// +// public MockDrawingManager(EscherDggRecord dgg) { +// super(dgg); +// } + + public MockDrawingManager (){ + super(null); + } + + @Override + public int allocateShapeId(short drawingGroupId) { + return 0; //Mock value + } + } /** * Lets non UserModel tests at the low level Workbook */ @@ -52,4 +75,34 @@ public class HSSFTestHelper { public static EscherOptRecord getOptRecord(HSSFShape shape){ return shape._optRecord; } + + public static void convertHSSFGroup(HSSFShapeGroup shape, EscherContainerRecord escherParent, Map shapeToObj){ + Class clazz = EscherAggregate.class; + try { + Method method = clazz.getDeclaredMethod("convertGroup", HSSFShapeGroup.class, EscherContainerRecord.class, Map.class); + method.setAccessible(true); + method.invoke(new EscherAggregate(new MockDrawingManager()), shape, escherParent, shapeToObj); + } catch (NoSuchMethodException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } catch (InvocationTargetException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } catch (IllegalAccessException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + + public static void callConvertPatriarch(EscherAggregate agg) { + Method method = null; + try { + method = agg.getClass().getDeclaredMethod("convertPatriarch", HSSFPatriarch.class); + method.setAccessible(true); + method.invoke(agg, agg.getPatriarch()); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } catch (InvocationTargetException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestPolygon.java b/src/testcases/org/apache/poi/hssf/usermodel/TestPolygon.java new file mode 100644 index 000000000..7c83a0d25 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestPolygon.java @@ -0,0 +1,231 @@ +package org.apache.poi.hssf.usermodel; + +import junit.framework.TestCase; +import org.apache.poi.ddf.EscherArrayProperty; +import org.apache.poi.ddf.EscherOptRecord; +import org.apache.poi.ddf.EscherProperties; +import org.apache.poi.ddf.EscherSpRecord; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.model.HSSFTestModelHelper; +import org.apache.poi.hssf.model.PolygonShape; +import org.apache.poi.hssf.record.ObjRecord; + +import java.io.IOException; +import java.util.Arrays; + +/** + * @author Evgeniy Berlog + * @date 28.06.12 + */ +public class TestPolygon extends TestCase{ + + public void testResultEqualsToAbstractShape() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPolygonDrawArea( 100, 100 ); + polygon.setPoints( new int[]{0, 90, 50}, new int[]{5, 5, 44} ); + PolygonShape polygonShape = HSSFTestModelHelper.createPolygonShape(1024, polygon); + polygon.setShapeId(1024); + + assertEquals(polygon.getEscherContainer().getChildRecords().size(), 4); + assertEquals(polygonShape.getSpContainer().getChildRecords().size(), 4); + + //sp record + byte[] expected = polygonShape.getSpContainer().getChild(0).serialize(); + byte[] actual = polygon.getEscherContainer().getChild(0).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = polygonShape.getSpContainer().getChild(2).serialize(); + actual = polygon.getEscherContainer().getChild(2).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = polygonShape.getSpContainer().getChild(3).serialize(); + actual = polygon.getEscherContainer().getChild(3).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + ObjRecord obj = polygon.getObjRecord(); + ObjRecord objShape = polygonShape.getObjRecord(); + + expected = obj.serialize(); + actual = objShape.serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + } + + public void testPolygonPoints(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPolygonDrawArea( 100, 100 ); + polygon.setPoints( new int[]{0, 90, 50, 90}, new int[]{5, 5, 44, 88} ); + + PolygonShape polygonShape = HSSFTestModelHelper.createPolygonShape(0, polygon); + + EscherArrayProperty verticesProp1 = polygon._optRecord.lookup(EscherProperties.GEOMETRY__VERTICES); + EscherArrayProperty verticesProp2 = ((EscherOptRecord)polygonShape.getSpContainer().getChildById(EscherOptRecord.RECORD_ID)) + .lookup(EscherProperties.GEOMETRY__VERTICES); + + assertEquals(verticesProp1.getNumberOfElementsInArray(), verticesProp2.getNumberOfElementsInArray()); + assertEquals(verticesProp1.toXml(""), verticesProp2.toXml("")); + + polygon.setPoints(new int[]{1,2,3}, new int[] {4,5,6}); + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{1, 2, 3})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{4, 5, 6})); + + polygonShape = HSSFTestModelHelper.createPolygonShape(0, polygon); + verticesProp1 = polygon._optRecord.lookup(EscherProperties.GEOMETRY__VERTICES); + verticesProp2 = ((EscherOptRecord)polygonShape.getSpContainer().getChildById(EscherOptRecord.RECORD_ID)) + .lookup(EscherProperties.GEOMETRY__VERTICES); + + assertEquals(verticesProp1.getNumberOfElementsInArray(), verticesProp2.getNumberOfElementsInArray()); + assertEquals(verticesProp1.toXml(""), verticesProp2.toXml("")); + } + + public void testSetGetProperties(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPolygonDrawArea( 102, 101 ); + polygon.setPoints( new int[]{1,2,3}, new int[]{4,5,6} ); + + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{1,2,3})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{4, 5, 6})); + assertEquals(polygon.getDrawAreaHeight(), 101); + assertEquals(polygon.getDrawAreaWidth(), 102); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + polygon = (HSSFPolygon) patriarch.getChildren().get(0); + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{1, 2, 3})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{4, 5, 6})); + assertEquals(polygon.getDrawAreaHeight(), 101); + assertEquals(polygon.getDrawAreaWidth(), 102); + + polygon.setPolygonDrawArea( 1021, 1011 ); + polygon.setPoints( new int[]{11,21,31}, new int[]{41,51,61} ); + + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{11, 21, 31})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{41, 51, 61})); + assertEquals(polygon.getDrawAreaHeight(), 1011); + assertEquals(polygon.getDrawAreaWidth(), 1021); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + polygon = (HSSFPolygon) patriarch.getChildren().get(0); + + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{11, 21, 31})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{41, 51, 61})); + assertEquals(polygon.getDrawAreaHeight(), 1011); + assertEquals(polygon.getDrawAreaWidth(), 1021); + } + + public void testAddToExistingFile(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPolygonDrawArea( 102, 101 ); + polygon.setPoints( new int[]{1,2,3}, new int[]{4,5,6} ); + + HSSFPolygon polygon1 = patriarch.createPolygon(new HSSFClientAnchor()); + polygon1.setPolygonDrawArea( 103, 104 ); + polygon1.setPoints( new int[]{11,12,13}, new int[]{14,15,16} ); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 2); + + HSSFPolygon polygon2 = patriarch.createPolygon(new HSSFClientAnchor()); + polygon2.setPolygonDrawArea( 203, 204 ); + polygon2.setPoints( new int[]{21,22,23}, new int[]{24,25,26} ); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 3); + + polygon = (HSSFPolygon) patriarch.getChildren().get(0); + polygon1 = (HSSFPolygon) patriarch.getChildren().get(1); + polygon2 = (HSSFPolygon) patriarch.getChildren().get(2); + + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{1, 2, 3})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{4,5,6})); + assertEquals(polygon.getDrawAreaHeight(), 101); + assertEquals(polygon.getDrawAreaWidth(), 102); + + assertTrue(Arrays.equals(polygon1.getXPoints(), new int[]{11,12,13})); + assertTrue(Arrays.equals(polygon1.getYPoints(), new int[]{14,15,16})); + assertEquals(polygon1.getDrawAreaHeight(), 104); + assertEquals(polygon1.getDrawAreaWidth(), 103); + + assertTrue(Arrays.equals(polygon2.getXPoints(), new int[]{21,22,23})); + assertTrue(Arrays.equals(polygon2.getYPoints(), new int[]{24,25,26})); + assertEquals(polygon2.getDrawAreaHeight(), 204); + assertEquals(polygon2.getDrawAreaWidth(), 203); + } + + public void testExistingFile() throws IOException { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + HSSFSheet sheet = wb.getSheet("polygon"); + HSSFPatriarch drawing = sheet.getDrawingPatriarch(); + assertEquals(1, drawing.getChildren().size()); + + HSSFPolygon polygon = (HSSFPolygon) drawing.getChildren().get(0); + assertEquals(polygon.getDrawAreaHeight(), 2466975); + assertEquals(polygon.getDrawAreaWidth(), 3686175); + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{0, 0, 31479, 16159, 19676, 20502})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{0, 0, 36, 56, 34, 18})); + } + + public void testPolygonType(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPolygonDrawArea( 102, 101 ); + polygon.setPoints( new int[]{1,2,3}, new int[]{4,5,6} ); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + HSSFPolygon polygon1 = patriarch.createPolygon(new HSSFClientAnchor()); + polygon1.setPolygonDrawArea( 102, 101 ); + polygon1.setPoints( new int[]{1,2,3}, new int[]{4,5,6} ); + + EscherSpRecord spRecord = polygon1.getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); + + spRecord.setShapeType((short)77/**RANDOM**/); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 2); + assertTrue(patriarch.getChildren().get(0) instanceof HSSFPolygon); + assertTrue(patriarch.getChildren().get(1) instanceof HSSFPolygon); + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestShapeGroup.java b/src/testcases/org/apache/poi/hssf/usermodel/TestShapeGroup.java new file mode 100644 index 000000000..bdbf25fc4 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestShapeGroup.java @@ -0,0 +1,225 @@ +package org.apache.poi.hssf.usermodel; + +import junit.framework.TestCase; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.record.ObjRecord; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Evgeniy Berlog + * @date 29.06.12 + */ +public class TestShapeGroup extends TestCase{ + + public void testResultEqualsToAbstractShape() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + HSSFShapeGroup group = patriarch.createGroup(new HSSFClientAnchor()); + + EscherContainerRecord container = new EscherContainerRecord(); + Map shapeToObj = new HashMap(); + HSSFTestHelper.convertHSSFGroup(group, container, shapeToObj); + + byte [] actual = group.getEscherContainer().serialize(); + byte [] expected = container.getChild(0).serialize(); + + assertEquals(actual.length, expected.length); + assertTrue(Arrays.equals(actual, expected)); + + actual = group.getObjRecord().serialize(); + expected = ((ObjRecord)shapeToObj.values().toArray()[0]).serialize(); + + assertEquals(actual.length, expected.length); + assertTrue(Arrays.equals(actual, expected)); + } + + public void testSetGetCoordinates(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + HSSFShapeGroup group = patriarch.createGroup(new HSSFClientAnchor()); + assertEquals(group.getX1(), 0); + assertEquals(group.getY1(), 0); + assertEquals(group.getX2(), 1023); + assertEquals(group.getY2(), 255); + + group.setCoordinates(1,2,3,4); + + assertEquals(group.getX1(), 1); + assertEquals(group.getY1(), 2); + assertEquals(group.getX2(), 3); + assertEquals(group.getY2(), 4); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + group = (HSSFShapeGroup) patriarch.getChildren().get(0); + assertEquals(group.getX1(), 1); + assertEquals(group.getY1(), 2); + assertEquals(group.getX2(), 3); + assertEquals(group.getY2(), 4); + } + + public void testAddToExistingFile(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + HSSFShapeGroup group1 = patriarch.createGroup(new HSSFClientAnchor()); + HSSFShapeGroup group2 = patriarch.createGroup(new HSSFClientAnchor()); + + group1.setCoordinates(1,2,3,4); + group2.setCoordinates(5,6,7,8); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 2); + + HSSFShapeGroup group3 = patriarch.createGroup(new HSSFClientAnchor()); + group3.setCoordinates(9,10,11,12); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 3); + } + + public void testModify() throws Exception { + HSSFWorkbook wb = new HSSFWorkbook(); + + // create a sheet with a text box + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFShapeGroup group1 = patriarch.createGroup(new + HSSFClientAnchor(0,0,0,0, + (short)0, 0, (short)15, 25)); + group1.setCoordinates(0, 0, 792, 612); + + HSSFTextbox textbox1 = group1.createTextbox(new + HSSFChildAnchor(100, 100, 300, 300)); + HSSFRichTextString rt1 = new HSSFRichTextString("Hello, World!"); + textbox1.setString(rt1); + + // write, read back and check that our text box is there + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + assertEquals(1, patriarch.getChildren().size()); + + group1 = (HSSFShapeGroup)patriarch.getChildren().get(0); + assertEquals(1, group1.getChildren().size()); + textbox1 = (HSSFTextbox)group1.getChildren().get(0); + assertEquals("Hello, World!", textbox1.getString().getString()); + + // modify anchor + assertEquals(new HSSFChildAnchor(100, 100, 300, 300), + textbox1.getAnchor()); + HSSFChildAnchor newAnchor = new HSSFChildAnchor(200,200, 400, 400); + textbox1.setAnchor(newAnchor); + // modify text + textbox1.setString(new HSSFRichTextString("Hello, World! (modified)")); + + // add a new text box + HSSFTextbox textbox2 = group1.createTextbox(new + HSSFChildAnchor(400, 400, 600, 600)); + HSSFRichTextString rt2 = new HSSFRichTextString("Hello, World-2"); + textbox2.setString(rt2); + assertEquals(2, group1.getChildren().size()); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + assertEquals(1, patriarch.getChildren().size()); + + group1 = (HSSFShapeGroup)patriarch.getChildren().get(0); + assertEquals(2, group1.getChildren().size()); + textbox1 = (HSSFTextbox)group1.getChildren().get(0); + assertEquals("Hello, World! (modified)", + textbox1.getString().getString()); + assertEquals(new HSSFChildAnchor(200,200, 400, 400), + textbox1.getAnchor()); + + textbox2 = (HSSFTextbox)group1.getChildren().get(1); + assertEquals("Hello, World-2", textbox2.getString().getString()); + assertEquals(new HSSFChildAnchor(400, 400, 600, 600), + textbox2.getAnchor()); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + group1 = (HSSFShapeGroup)patriarch.getChildren().get(0); + textbox1 = (HSSFTextbox)group1.getChildren().get(0); + textbox2 = (HSSFTextbox)group1.getChildren().get(1); + HSSFTextbox textbox3 = group1.createTextbox(new + HSSFChildAnchor(400,200, 600, 400)); + HSSFRichTextString rt3 = new HSSFRichTextString("Hello, World-3"); + textbox3.setString(rt3); + } + + public void testAddShapesToGroup(){ + HSSFWorkbook wb = new HSSFWorkbook(); + + // create a sheet with a text box + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFShapeGroup group = patriarch.createGroup(new HSSFClientAnchor()); + int index = wb.addPicture(new byte[]{1,2,3}, HSSFWorkbook.PICTURE_TYPE_JPEG); + group.createPicture(new HSSFChildAnchor(), index); + HSSFPolygon polygon = group.createPolygon(new HSSFChildAnchor()); + polygon.setPoints(new int[]{1,100, 1}, new int[]{1, 50, 100}); + group.createTextbox(new HSSFChildAnchor()); + group.createShape(new HSSFChildAnchor()); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + assertEquals(1, patriarch.getChildren().size()); + + assertTrue(patriarch.getChildren().get(0) instanceof HSSFShapeGroup); + group = (HSSFShapeGroup) patriarch.getChildren().get(0); + + assertEquals(group.getChildren().size(), 4); + + assertTrue(group.getChildren().get(0) instanceof HSSFPicture); + assertTrue(group.getChildren().get(1) instanceof HSSFPolygon); + assertTrue(group.getChildren().get(2) instanceof HSSFTextbox); + assertTrue(group.getChildren().get(3) instanceof HSSFSimpleShape); + + HSSFShapeGroup group2 = patriarch.createGroup(new HSSFClientAnchor()); + + index = wb.addPicture(new byte[]{2,2,2}, HSSFWorkbook.PICTURE_TYPE_JPEG); + group2.createPicture(new HSSFChildAnchor(), index); + polygon = group2.createPolygon(new HSSFChildAnchor()); + polygon.setPoints(new int[]{1,100, 1}, new int[]{1, 50, 100}); + group2.createTextbox(new HSSFChildAnchor()); + group2.createShape(new HSSFChildAnchor()); + group2.createShape(new HSSFChildAnchor()); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + assertEquals(2, patriarch.getChildren().size()); + + group = (HSSFShapeGroup) patriarch.getChildren().get(1); + + assertEquals(group.getChildren().size(), 5); + + assertTrue(group.getChildren().get(0) instanceof HSSFPicture); + assertTrue(group.getChildren().get(1) instanceof HSSFPolygon); + assertTrue(group.getChildren().get(2) instanceof HSSFTextbox); + assertTrue(group.getChildren().get(3) instanceof HSSFSimpleShape); + assertTrue(group.getChildren().get(4) instanceof HSSFSimpleShape); + + group.getShapeId(); + } +} diff --git a/test-data/spreadsheet/drawings.xls b/test-data/spreadsheet/drawings.xls index 24684cb6ccc0880829ab8de111fe6263a07ebcad..9700c91086f0b771299ce855063de7b2e1c19bcd 100644 GIT binary patch delta 6068 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 WB@9Z%wx-?hvanr{(IXX~m;4*T$o=I2