From b57fa5b4d0621d11b3f324387cebad89cd046eb8 Mon Sep 17 00:00:00 2001 From: Evgeniy Berlog Date: Tue, 26 Jun 2012 11:21:13 +0000 Subject: [PATCH] implemented creating shapes in existing files git-svn-id: https://svn.apache.org/repos/asf/poi/branches/gsoc2012@1353960 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hssf/record/EscherAggregate.java | 12 +- .../poi/hssf/usermodel/HSSFComment.java | 4 +- .../poi/hssf/usermodel/HSSFPatriarch.java | 38 ++- .../poi/hssf/usermodel/HSSFPolygon.java | 13 ++ .../apache/poi/hssf/usermodel/HSSFShape.java | 20 ++ .../poi/hssf/usermodel/HSSFShapeFactory.java | 40 +++- .../poi/hssf/usermodel/HSSFShapeGroup.java | 10 + .../poi/hssf/usermodel/HSSFSimpleShape.java | 79 ++++++- .../poi/hssf/usermodel/HSSFTextbox.java | 216 ++++++++++++------ .../poi/hssf/usermodel/HSSFUnknownShape.java | 10 + .../hssf/usermodel/drawing/HSSFShapeType.java | 13 +- .../poi/hssf/model/HSSFTestModelHelper.java | 13 ++ .../poi/hssf/model/TestDrawingShapes.java | 37 +++ .../poi/hssf/usermodel/HSSFTestHelper.java | 4 + .../apache/poi/hssf/usermodel/TestText.java | 186 +++++++++++++++ test-data/spreadsheet/drawings.xls | Bin 815104 -> 817664 bytes test-data/spreadsheet/empty.xls | Bin 0 -> 24576 bytes 17 files changed, 592 insertions(+), 103 deletions(-) create mode 100644 src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java create mode 100644 src/testcases/org/apache/poi/hssf/usermodel/TestText.java create mode 100644 test-data/spreadsheet/empty.xls diff --git a/src/java/org/apache/poi/hssf/record/EscherAggregate.java b/src/java/org/apache/poi/hssf/record/EscherAggregate.java index fb16037a37..ee75870bb8 100644 --- a/src/java/org/apache/poi/hssf/record/EscherAggregate.java +++ b/src/java/org/apache/poi/hssf/record/EscherAggregate.java @@ -323,6 +323,10 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { this.drawingManager = drawingManager; } + public DrawingManager2 getDrawingManager() { + return drawingManager; + } + /** * @return Returns the current sid. */ @@ -628,7 +632,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { /** * Associates an escher record to an OBJ record or a TXO record. */ - public Object associateShapeToObjRecord(EscherRecord r, ObjRecord objRecord) { + public Object associateShapeToObjRecord(EscherRecord r, Record objRecord) { return shapeToObj.put(r, objRecord); } @@ -1039,9 +1043,9 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { EscherDgRecord dg = new EscherDgRecord(); dg.setRecordId( EscherDgRecord.RECORD_ID ); short dgId = 1; - dg.setOptions( (short) ( dgId << 4 ) ); - dg.setNumShapes( 1 ); - dg.setLastMSOSPID( 1024 ); + dg.setOptions((short) (dgId << 4)); + dg.setNumShapes(1); + dg.setLastMSOSPID(1024); drawingGroupId = dg.getDrawingGroupId(); spgrContainer.setRecordId(EscherContainerRecord.SPGR_CONTAINER); spgrContainer.setOptions((short) 0x000F); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java index 2216971e68..f949e59fb0 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java @@ -66,7 +66,7 @@ public class HSSFComment extends HSSFTextbox implements Comment { } protected HSSFComment(NoteRecord note, TextObjectRecord txo) { - this((HSSFShape) null, (HSSFAnchor) null); + this(null, new HSSFClientAnchor()); _txo = txo; _note = note; } @@ -185,7 +185,7 @@ public class HSSFComment extends HSSFTextbox implements Comment { /** * Returns the underlying Text record */ - protected TextObjectRecord getTextObjectRecord() { + public TextObjectRecord getTextObjectRecord() { return _txo; } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java index 71549bbf31..137086d9b8 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java @@ -21,12 +21,8 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import org.apache.poi.ddf.EscherComplexProperty; -import org.apache.poi.ddf.EscherContainerRecord; -import org.apache.poi.ddf.EscherOptRecord; -import org.apache.poi.ddf.EscherProperty; -import org.apache.poi.ddf.EscherBSERecord; -import org.apache.poi.ddf.EscherSpgrRecord; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.model.DrawingManager2; import org.apache.poi.hssf.record.EscherAggregate; import org.apache.poi.ss.usermodel.Chart; import org.apache.poi.util.StringUtil; @@ -93,6 +89,8 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { HSSFSimpleShape shape = new HSSFSimpleShape(null, anchor); shape.anchor = anchor; addShape(shape); + //open existing file + onCreate(shape); return shape; } @@ -109,6 +107,8 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { shape.setPictureIndex( pictureIndex ); shape.anchor = anchor; addShape(shape); + //open existing file + onCreate(shape); EscherBSERecord bse = _sheet.getWorkbook().getWorkbook().getBSERecord(pictureIndex); bse.setRef(bse.getRef() + 1); @@ -147,6 +147,7 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { HSSFTextbox shape = new HSSFTextbox(null, anchor); shape.anchor = anchor; addShape(shape); + onCreate(shape); return shape; } @@ -200,6 +201,20 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { _shapes.add(shape); } + private void onCreate(HSSFShape shape){ + if(_boundAggregate.getPatriarch() == null){ + EscherContainerRecord spgrContainer = + _boundAggregate.getEscherContainer().getChildContainers().get(0); + + EscherContainerRecord spContainer = shape.getEscherContainer(); + int shapeId = newShapeId(); + shape.setShapeId(shapeId); + + spgrContainer.addChildRecord(spContainer); + shape.afterInsert(this); + } + } + /** * Total count of all children and their children's children. */ @@ -222,6 +237,17 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { _y2 = y2; } + int newShapeId() { + if (_boundAggregate.getEscherContainer() == null){ + throw new IllegalStateException("We can use this method for only existing files"); + } + DrawingManager2 dm = _boundAggregate.getDrawingManager(); + EscherDgRecord dg = + _boundAggregate.getEscherContainer().getChildById(EscherDgRecord.RECORD_ID); + short drawingGroupId = dg.getDrawingGroupId(); + return dm.allocateShapeId(drawingGroupId, dg); + } + /** * Does this HSSFPatriarch contain a chart? * (Technically a reference to a chart, since they diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java index 6027fb8be7..c6dc9d7f0a 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java @@ -17,6 +17,9 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.hssf.record.ObjRecord; + /** * @author Glen Stampoultzis (glens at superlinksoftware.com) */ @@ -33,6 +36,16 @@ public class HSSFPolygon super( parent, anchor ); } + @Override + protected EscherContainerRecord createSpContainer() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + protected ObjRecord createObjRecord() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + public int[] getXPoints() { return xPoints; diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java index 33dfd68643..975453ad01 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java @@ -18,6 +18,7 @@ package org.apache.poi.hssf.usermodel; import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; import org.apache.poi.hssf.record.ObjRecord; /** @@ -71,6 +72,7 @@ public abstract class HSSFShape { this.anchor = anchor; this._escherContainer = new EscherContainerRecord(); _optRecord = new EscherOptRecord(); + _optRecord.setRecordId( EscherOptRecord.RECORD_ID ); _optRecord.addEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEDASHING, LINESTYLE_SOLID)); _optRecord.addEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEWIDTH, LINEWIDTH_DEFAULT)); _optRecord.addEscherProperty(new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, FILL__FILLCOLOR_DEFAULT)); @@ -78,6 +80,24 @@ public abstract class HSSFShape { _optRecord.addEscherProperty(new EscherBoolProperty(EscherProperties.FILL__NOFILLHITTEST, 0x0)); } + protected abstract EscherContainerRecord createSpContainer(); + + protected abstract ObjRecord createObjRecord(); + + void setShapeId(int shapeId){ + EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID); + spRecord.setShapeId(shapeId); + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) _objRecord.getSubRecords().get(0); + cod.setObjectId((short) (shapeId-1024)); + } + + int getShapeId(){ + return ((EscherSpRecord)_escherContainer.getChildById(EscherSpRecord.RECORD_ID)).getShapeId(); + } + + void afterInsert(HSSFPatriarch patriarch){ + } + public EscherContainerRecord getEscherContainer() { return _escherContainer; } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java index 68f410d173..5c1be44b4d 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java @@ -115,19 +115,37 @@ public class HSSFShapeFactory { break; } } - if (null != objRecord){ - HSSFShape shape = shapeCreator.createNewShape(spRecord.getShapeType(), container, objRecord); + CommonObjectDataSubRecord cmo = (CommonObjectDataSubRecord) objRecord.getSubRecords().get(0); + HSSFShape shape = null; + switch (cmo.getObjectType()) { + case CommonObjectDataSubRecord.OBJECT_TYPE_PICTURE: + shape = new HSSFPicture(container, objRecord); + break; + case CommonObjectDataSubRecord.OBJECT_TYPE_RECTANGLE: + shape = new HSSFSimpleShape(container, objRecord); + break; + case CommonObjectDataSubRecord.OBJECT_TYPE_TEXT: + shape = new HSSFTextbox(container, objRecord, txtRecord); + break; + default: + shape = new HSSFSimpleShape(container, objRecord); + } + if (null != shape){ out.addShape(shape); } - if (null != txtRecord){ - //TODO resolve textbox -// TextboxShape shape = new TextboxShape(container, txtRecord); -// out.a - } -// -// //TODO decide what shape to create based on ObjRecord / EscherSpRecord -// HSSFShape shape = new HSSFUnknownShape(container, objRecord); -// out.addShape(shape); +// if (null != objRecord){ +// HSSFShape shape = shapeCreator.createNewShape(spRecord.getShapeType(), container, objRecord); +// out.addShape(shape); +// } +// if (null != txtRecord){ +// //TODO resolve textbox +//// TextboxShape shape = new TextboxShape(container, txtRecord); +//// out.a +// } +//// +//// //TODO decide what shape to create based on ObjRecord / EscherSpRecord +//// HSSFShape shape = new HSSFUnknownShape(container, objRecord); +//// out.addShape(shape); } } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java index b3209467ae..96b39daceb 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java @@ -74,6 +74,16 @@ public class HSSFShapeGroup _spgrRecord.setRectY2(255); } + @Override + protected EscherContainerRecord createSpContainer() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + protected ObjRecord createObjRecord() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + /** * Create another group under this group. * @param anchor the position of the new group. diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java index 6e0df0fd54..e7b2c718c6 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java @@ -19,7 +19,13 @@ package org.apache.poi.hssf.usermodel; 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.hssf.usermodel.drawing.HSSFShapeType; + +import java.util.HashMap; +import java.util.Map; /** * Represents a simple shape such as a line, rectangle or oval. @@ -56,17 +62,69 @@ public class HSSFSimpleShape int shapeType = OBJECT_TYPE_LINE; + private static final Map objTypeToShapeType = new HashMap(); + + static { + objTypeToShapeType.put(OBJECT_TYPE_RECTANGLE, HSSFShapeType.RECTANGLE.getType()); + objTypeToShapeType.put(OBJECT_TYPE_PICTURE, HSSFShapeType.PICTURE.getType()); + objTypeToShapeType.put(OBJECT_TYPE_LINE, HSSFShapeType.LINE.getType()); + } + public HSSFSimpleShape(EscherContainerRecord spContainer, ObjRecord objRecord) { super(spContainer, objRecord); - CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) objRecord.getSubRecords().get(0); - setShapeType(cod.getObjectType()); } public HSSFSimpleShape( HSSFShape parent, HSSFAnchor anchor) { super( parent, anchor ); + _escherContainer = createSpContainer(); + _objRecord = createObjRecord(); + setShapeType(OBJECT_TYPE_LINE); } + @Override + protected EscherContainerRecord createSpContainer() { + EscherContainerRecord spContainer = new EscherContainerRecord(); + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + + EscherSpRecord sp = new EscherSpRecord(); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + + EscherClientDataRecord clientData = new EscherClientDataRecord(); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) 0x0000 ); + + spContainer.addChildRecord(sp); + spContainer.addChildRecord(_optRecord); + spContainer.addChildRecord(anchor.getEscherAnchor()); + spContainer.addChildRecord(clientData); + return spContainer; + } + + @Override + protected ObjRecord createObjRecord() { + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setLocked(true); + c.setPrintable(true); + c.setAutofill(true); + c.setAutoline(true); + EndSubRecord e = new EndSubRecord(); + + obj.addSubRecord(c); + obj.addSubRecord(e); + return obj; + } + + @Override + void afterInsert(HSSFPatriarch patriarch){ + EscherAggregate agg = patriarch._getBoundAggregate(); + agg.associateShapeToObjRecord(_escherContainer.getChildById(EscherClientDataRecord.RECORD_ID), getObjRecord()); + } + + /** * Gets the shape type. * @return One of the OBJECT_TYPE_* constants. @@ -77,7 +135,10 @@ public class HSSFSimpleShape * @see #OBJECT_TYPE_PICTURE * @see #OBJECT_TYPE_COMMENT */ - public int getShapeType() { return shapeType; } + public int getShapeType() { + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) _objRecord.getSubRecords().get(0); + return cod.getObjectType(); + } /** * Sets the shape types. @@ -90,6 +151,14 @@ public class HSSFSimpleShape * @see #OBJECT_TYPE_PICTURE * @see #OBJECT_TYPE_COMMENT */ - public void setShapeType( int shapeType ){ this.shapeType = shapeType; } - + public void setShapeType( int shapeType ){ + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) _objRecord.getSubRecords().get(0); + cod.setObjectType((short) shapeType); + EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID); + if (null == objTypeToShapeType.get((short)shapeType)){ + System.out.println("Unknown shape type: "+shapeType); + return; + } + spRecord.setShapeType(objTypeToShapeType.get((short)shapeType)); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java index 2548bf5114..00dfe9590c 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java @@ -17,6 +17,8 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; import org.apache.poi.ss.usermodel.RichTextString; /** @@ -24,163 +26,233 @@ import org.apache.poi.ss.usermodel.RichTextString; * * @author Glen Stampoultzis (glens at apache.org) */ -public class HSSFTextbox - extends HSSFSimpleShape -{ - public final static short OBJECT_TYPE_TEXT = 6; +public class HSSFTextbox extends HSSFSimpleShape { + public final static short OBJECT_TYPE_TEXT = 6; /** * How to align text horizontally */ - public final static short HORIZONTAL_ALIGNMENT_LEFT = 1; - public final static short HORIZONTAL_ALIGNMENT_CENTERED = 2; - public final static short HORIZONTAL_ALIGNMENT_RIGHT = 3; - public final static short HORIZONTAL_ALIGNMENT_JUSTIFIED = 4; - public final static short HORIZONTAL_ALIGNMENT_DISTRIBUTED = 7; + public final static short HORIZONTAL_ALIGNMENT_LEFT = 1; + public final static short HORIZONTAL_ALIGNMENT_CENTERED = 2; + public final static short HORIZONTAL_ALIGNMENT_RIGHT = 3; + public final static short HORIZONTAL_ALIGNMENT_JUSTIFIED = 4; + public final static short HORIZONTAL_ALIGNMENT_DISTRIBUTED = 7; /** * How to align text vertically */ - public final static short VERTICAL_ALIGNMENT_TOP = 1; - public final static short VERTICAL_ALIGNMENT_CENTER = 2; - public final static short VERTICAL_ALIGNMENT_BOTTOM = 3; - public final static short VERTICAL_ALIGNMENT_JUSTIFY = 4; - public final static short VERTICAL_ALIGNMENT_DISTRIBUTED= 7; + public final static short VERTICAL_ALIGNMENT_TOP = 1; + public final static short VERTICAL_ALIGNMENT_CENTER = 2; + public final static short VERTICAL_ALIGNMENT_BOTTOM = 3; + public final static short VERTICAL_ALIGNMENT_JUSTIFY = 4; + public final static short VERTICAL_ALIGNMENT_DISTRIBUTED = 7; int marginLeft, marginRight, marginTop, marginBottom; short halign, valign; + private TextObjectRecord _textObjectRecord; + + public HSSFTextbox(EscherContainerRecord spContainer, ObjRecord objRecord, TextObjectRecord textObjectRecord) { + super(spContainer, objRecord); + this._textObjectRecord = textObjectRecord; + } + HSSFRichTextString string = new HSSFRichTextString(""); /** * Construct a new textbox with the given parent and anchor. + * * @param parent - * @param anchor One of HSSFClientAnchor or HSSFChildAnchor + * @param anchor One of HSSFClientAnchor or HSSFChildAnchor */ - public HSSFTextbox( HSSFShape parent, HSSFAnchor anchor ) - { - super( parent, anchor ); - setShapeType(OBJECT_TYPE_TEXT); + public HSSFTextbox(HSSFShape parent, HSSFAnchor anchor) { + super(parent, anchor); + _textObjectRecord = createTextObjRecord(); + setHorizontalAlignment(HORIZONTAL_ALIGNMENT_LEFT); + setVerticalAlignment(VERTICAL_ALIGNMENT_TOP); + setString(new HSSFRichTextString("")); - halign = HORIZONTAL_ALIGNMENT_LEFT; - valign = VERTICAL_ALIGNMENT_TOP; + } + + protected TextObjectRecord createTextObjRecord(){ + TextObjectRecord obj = new TextObjectRecord(); + obj.setTextLocked(true); + obj.setTextOrientation(TextObjectRecord.TEXT_ORIENTATION_NONE); + return obj; + } + + @Override + protected ObjRecord createObjRecord() { + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType(OBJECT_TYPE_TEXT); + c.setLocked( true ); + c.setPrintable( true ); + c.setAutofill( true ); + c.setAutoline( true ); + EndSubRecord e = new EndSubRecord(); + obj.addSubRecord( c ); + obj.addSubRecord( e ); + return obj; + } + + @Override + protected EscherContainerRecord createSpContainer() { + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + EscherTextboxRecord 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.setFlags(EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE); + opt.setRecordId(EscherOptRecord.RECORD_ID); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTID, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTLEFT, getMarginLeft())); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTRIGHT, getMarginRight())); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTBOTTOM, getMarginBottom())); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTTOP, getMarginTop())); + + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__WRAPTEXT, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__ANCHORTEXT, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GROUPSHAPE__PRINT, 0x00080000)); + + EscherRecord anchor = getAnchor().getEscherAnchor(); + 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; + } + + @Override + void afterInsert(HSSFPatriarch patriarch){ + EscherAggregate agg = patriarch._getBoundAggregate(); + agg.associateShapeToObjRecord(_escherContainer.getChildById(EscherClientDataRecord.RECORD_ID), getObjRecord()); + agg.associateShapeToObjRecord(_escherContainer.getChildById(EscherTextboxRecord.RECORD_ID), getTextObjectRecord()); } /** - * @return the rich text string for this textbox. + * @return the rich text string for this textbox. */ - public HSSFRichTextString getString() - { - return string; + public HSSFRichTextString getString() { + return _textObjectRecord.getStr(); } /** - * @param string Sets the rich text string used by this object. + * @param string Sets the rich text string used by this object. */ - public void setString( RichTextString string ) - { - HSSFRichTextString rtr = (HSSFRichTextString)string; - + public void setString(RichTextString string) { + HSSFRichTextString rtr = (HSSFRichTextString) string; // If font is not set we must set the default one - if (rtr.numFormattingRuns() == 0) rtr.applyFont((short)0); - - this.string = rtr; + if (rtr.numFormattingRuns() == 0) rtr.applyFont((short) 0); + _textObjectRecord.setStr(rtr); } /** - * @return Returns the left margin within the textbox. + * @return Returns the left margin within the textbox. */ - public int getMarginLeft() - { - return marginLeft; + public int getMarginLeft() { + EscherSimpleProperty property = _optRecord.lookup(EscherProperties.TEXT__TEXTLEFT); + return property == null ? 0: property.getPropertyValue(); } /** * Sets the left margin within the textbox. */ - public void setMarginLeft( int marginLeft ) - { - this.marginLeft = marginLeft; + public void setMarginLeft(int marginLeft) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.TEXT__TEXTLEFT, marginLeft)); } /** - * @return returns the right margin within the textbox. + * @return returns the right margin within the textbox. */ - public int getMarginRight() - { - return marginRight; + public int getMarginRight() { + EscherSimpleProperty property = _optRecord.lookup(EscherProperties.TEXT__TEXTRIGHT); + return property == null ? 0: property.getPropertyValue(); } /** * Sets the right margin within the textbox. */ - public void setMarginRight( int marginRight ) - { - this.marginRight = marginRight; + public void setMarginRight(int marginRight) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.TEXT__TEXTRIGHT, marginRight)); } /** - * @return returns the top margin within the textbox. + * @return returns the top margin within the textbox. */ - public int getMarginTop() - { - return marginTop; + public int getMarginTop() { + EscherSimpleProperty property = _optRecord.lookup(EscherProperties.TEXT__TEXTTOP); + return property == null ? 0: property.getPropertyValue(); } /** * Sets the top margin within the textbox. */ - public void setMarginTop( int marginTop ) - { - this.marginTop = marginTop; + public void setMarginTop(int marginTop) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.TEXT__TEXTTOP, marginTop)); } /** * Gets the bottom margin within the textbox. */ - public int getMarginBottom() - { - return marginBottom; + public int getMarginBottom() { + EscherSimpleProperty property = _optRecord.lookup(EscherProperties.TEXT__TEXTBOTTOM); + return property == null ? 0: property.getPropertyValue(); } /** * Sets the bottom margin within the textbox. */ - public void setMarginBottom( int marginBottom ) - { - this.marginBottom = marginBottom; + public void setMarginBottom(int marginBottom) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.TEXT__TEXTBOTTOM, marginBottom)); } /** * Gets the horizontal alignment. */ - public short getHorizontalAlignment() - { - return halign; + public short getHorizontalAlignment() { + return (short) _textObjectRecord.getHorizontalTextAlignment(); } /** * Sets the horizontal alignment. */ - public void setHorizontalAlignment( short align ) - { - this.halign = align; + public void setHorizontalAlignment(short align) { + _textObjectRecord.setHorizontalTextAlignment(align); } /** * Gets the vertical alignment. */ - public short getVerticalAlignment() - { - return valign; + public short getVerticalAlignment() { + return (short) _textObjectRecord.getVerticalTextAlignment(); } /** * Sets the vertical alignment. */ - public void setVerticalAlignment( short align ) - { - this.valign = align; + public void setVerticalAlignment(short align) { + _textObjectRecord.setVerticalTextAlignment(align); } + + public TextObjectRecord getTextObjectRecord() { + return _textObjectRecord; + } + + @Override + public void setShapeType(int shapeType) {/**DO NOTHING**/} } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFUnknownShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFUnknownShape.java index d4cac9be9a..406dfe7219 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFUnknownShape.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFUnknownShape.java @@ -30,4 +30,14 @@ public class HSSFUnknownShape extends HSSFShape { public HSSFUnknownShape(EscherRecord spContainer, ObjRecord objRecord) { super((EscherContainerRecord) spContainer, objRecord); } + + @Override + protected EscherContainerRecord createSpContainer() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + protected ObjRecord createObjRecord() { + return null; //To change body of implemented methods use File | Settings | File Templates. + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/drawing/HSSFShapeType.java b/src/java/org/apache/poi/hssf/usermodel/drawing/HSSFShapeType.java index c63a08b7e5..2e2b64eeb4 100644 --- a/src/java/org/apache/poi/hssf/usermodel/drawing/HSSFShapeType.java +++ b/src/java/org/apache/poi/hssf/usermodel/drawing/HSSFShapeType.java @@ -2,6 +2,7 @@ package org.apache.poi.hssf.usermodel.drawing; import org.apache.poi.hssf.usermodel.HSSFPicture; import org.apache.poi.hssf.usermodel.HSSFSimpleShape; +import org.apache.poi.hssf.usermodel.HSSFTextbox; /** * @author Evgeniy Berlog @@ -11,16 +12,18 @@ public enum HSSFShapeType { NOT_PRIMITIVE((short)0x0, null, (short)0), RECTANGLE((short)0x1, HSSFSimpleShape.class, HSSFSimpleShape.OBJECT_TYPE_RECTANGLE), PICTURE((short)0x004B, HSSFPicture.class, HSSFSimpleShape.OBJECT_TYPE_PICTURE), + LINE((short)0x14, HSSFSimpleShape.class, HSSFSimpleShape.OBJECT_TYPE_LINE), + TEXT((short)202, HSSFTextbox.class, HSSFTextbox.OBJECT_TYPE_TEXT), ROUND_RECTANGLE((short)0x2, null, null); private Short type; private Class shape; - private Short objectId; + private Short objectType; - private HSSFShapeType(Short type, Class shape, Short objectId) { + private HSSFShapeType(Short type, Class shape, Short objectType) { this.type = type; this.shape = shape; - this.objectId = objectId; + this.objectType = objectType; } public Short getType() { @@ -30,4 +33,8 @@ public enum HSSFShapeType { public Class getShape() { return shape; } + + public Short getObjectType() { + return objectType; + } } diff --git a/src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java b/src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java new file mode 100644 index 0000000000..cca8f2d635 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java @@ -0,0 +1,13 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.hssf.usermodel.HSSFTextbox; + +/** + * @author Evgeniy Berlog + * @date 25.06.12 + */ +public class HSSFTestModelHelper { + public static TextboxShape createTextboxShape(int shapeId, HSSFTextbox textbox){ + return new TextboxShape(textbox, shapeId); + } +} diff --git a/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java b/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java index 097d5d01ee..1beeabe94b 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java +++ b/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java @@ -251,4 +251,41 @@ public class TestDrawingShapes extends TestCase{ ((EscherContainerRecord)spgrContainer.getChild(2)).getChildById(EscherSpRecord.RECORD_ID); assertEquals(1026, sp2.getShapeId()); } + + /** + * Test get new id for shapes from existing file + * File already have for 1 shape on each sheet, because document must contain EscherDgRecord for each sheet + */ + public void testAllocateNewIds(){ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("empty.xls"); + HSSFSheet sheet = wb.getSheetAt(0); + HSSFPatriarch patriarch = sheet.getDrawingPatriarch(); + + /** + * 2048 - main SpContainer id + * 2049 - existing shape id + */ + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 2050); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 2051); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 2052); + + sheet = wb.getSheetAt(1); + patriarch = sheet.getDrawingPatriarch(); + + /** + * 3072 - main SpContainer id + * 3073 - existing shape id + */ + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 3074); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 3075); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 3076); + + + sheet = wb.getSheetAt(2); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 1026); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 1027); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 1028); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java b/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java index 2d6b14b79a..496139eaab 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java @@ -43,4 +43,8 @@ public class HSSFTestHelper { public static EscherAggregate getEscherAggregate(HSSFPatriarch patriarch){ return patriarch._getBoundAggregate(); } + + public static int allocateNewShapeId(HSSFPatriarch patriarch){ + return patriarch.newShapeId(); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestText.java b/src/testcases/org/apache/poi/hssf/usermodel/TestText.java new file mode 100644 index 0000000000..213c5be674 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestText.java @@ -0,0 +1,186 @@ +package org.apache.poi.hssf.usermodel; + +import junit.framework.TestCase; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.model.HSSFTestModelHelper; +import org.apache.poi.hssf.model.TextboxShape; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.TextObjectRecord; + +import java.util.Arrays; + +/** + * @author Evgeniy Berlog + * @date 25.06.12 + */ +public class TestText extends TestCase { + + public void testResultEqualsToAbstractShape() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + HSSFTextbox textbox = patriarch.createTextbox(new HSSFClientAnchor()); + TextboxShape textboxShape = HSSFTestModelHelper.createTextboxShape(0, textbox); + + assertEquals(textbox.getEscherContainer().getChildRecords().size(), 5); + assertEquals(textboxShape.getSpContainer().getChildRecords().size(), 5); + + //sp record + byte[] expected = textboxShape.getSpContainer().getChild(0).serialize(); + byte[] actual = textbox.getEscherContainer().getChild(0).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = textboxShape.getSpContainer().getChild(2).serialize(); + actual = textbox.getEscherContainer().getChild(2).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = textboxShape.getSpContainer().getChild(3).serialize(); + actual = textbox.getEscherContainer().getChild(3).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = textboxShape.getSpContainer().getChild(4).serialize(); + actual = textbox.getEscherContainer().getChild(4).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + ObjRecord obj = textbox.getObjRecord(); + ((CommonObjectDataSubRecord) obj.getSubRecords().get(0)).setObjectId(-1024); + ObjRecord objShape = textboxShape.getObjRecord(); + + expected = obj.serialize(); + actual = objShape.serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + TextObjectRecord tor = textbox.getTextObjectRecord(); + TextObjectRecord torShape = textboxShape.getTextObjectRecord(); + + expected = tor.serialize(); + actual = torShape.serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + } + + public void testAddTextToExistingFile() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + HSSFTextbox textbox = patriarch.createTextbox(new HSSFClientAnchor()); + textbox.setString(new HSSFRichTextString("just for test")); + HSSFTextbox textbox2 = patriarch.createTextbox(new HSSFClientAnchor()); + textbox2.setString(new HSSFRichTextString("just for test2")); + + assertEquals(patriarch.getChildren().size(), 2); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 2); + HSSFTextbox text3 = patriarch.createTextbox(new HSSFClientAnchor()); + text3.setString(new HSSFRichTextString("text3")); + assertEquals(patriarch.getChildren().size(), 3); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 3); + assertEquals(((HSSFTextbox) patriarch.getChildren().get(0)).getString().getString(), "just for test"); + assertEquals(((HSSFTextbox) patriarch.getChildren().get(1)).getString().getString(), "just for test2"); + assertEquals(((HSSFTextbox) patriarch.getChildren().get(2)).getString().getString(), "text3"); + } + + public void testSetGetProperties() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + HSSFTextbox textbox = patriarch.createTextbox(new HSSFClientAnchor()); + textbox.setString(new HSSFRichTextString("test")); + assertEquals(textbox.getString().getString(), "test"); + + textbox.setHorizontalAlignment((short) 5); + assertEquals(textbox.getHorizontalAlignment(), 5); + + textbox.setVerticalAlignment((short) 6); + assertEquals(textbox.getVerticalAlignment(), (short) 6); + + textbox.setMarginBottom(7); + assertEquals(textbox.getMarginBottom(), 7); + + textbox.setMarginLeft(8); + assertEquals(textbox.getMarginLeft(), 8); + + textbox.setMarginRight(9); + assertEquals(textbox.getMarginRight(), 9); + + textbox.setMarginTop(10); + assertEquals(textbox.getMarginTop(), 10); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + textbox = (HSSFTextbox) patriarch.getChildren().get(0); + assertEquals(textbox.getString().getString(), "test"); + assertEquals(textbox.getHorizontalAlignment(), 5); + assertEquals(textbox.getVerticalAlignment(), (short) 6); + assertEquals(textbox.getMarginBottom(), 7); + assertEquals(textbox.getMarginLeft(), 8); + assertEquals(textbox.getMarginRight(), 9); + assertEquals(textbox.getMarginTop(), 10); + + textbox.setString(new HSSFRichTextString("test1")); + textbox.setHorizontalAlignment(HSSFTextbox.HORIZONTAL_ALIGNMENT_CENTERED); + textbox.setVerticalAlignment(HSSFTextbox.VERTICAL_ALIGNMENT_TOP); + textbox.setMarginBottom(71); + textbox.setMarginLeft(81); + textbox.setMarginRight(91); + textbox.setMarginTop(101); + + assertEquals(textbox.getString().getString(), "test1"); + assertEquals(textbox.getHorizontalAlignment(), HSSFTextbox.HORIZONTAL_ALIGNMENT_CENTERED); + assertEquals(textbox.getVerticalAlignment(), HSSFTextbox.VERTICAL_ALIGNMENT_TOP); + assertEquals(textbox.getMarginBottom(), 71); + assertEquals(textbox.getMarginLeft(), 81); + assertEquals(textbox.getMarginRight(), 91); + assertEquals(textbox.getMarginTop(), 101); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + textbox = (HSSFTextbox) patriarch.getChildren().get(0); + + assertEquals(textbox.getString().getString(), "test1"); + assertEquals(textbox.getHorizontalAlignment(), HSSFTextbox.HORIZONTAL_ALIGNMENT_CENTERED); + assertEquals(textbox.getVerticalAlignment(), HSSFTextbox.VERTICAL_ALIGNMENT_TOP); + assertEquals(textbox.getMarginBottom(), 71); + assertEquals(textbox.getMarginLeft(), 81); + assertEquals(textbox.getMarginRight(), 91); + assertEquals(textbox.getMarginTop(), 101); + } + + public void testExistingFileWithText(){ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + HSSFSheet sheet = wb.getSheet("text"); + HSSFPatriarch drawing = sheet.getDrawingPatriarch(); + assertEquals(1, drawing.getChildren().size()); + HSSFTextbox textbox = (HSSFTextbox) drawing.getChildren().get(0); + assertEquals(textbox.getHorizontalAlignment(), HSSFTextbox.HORIZONTAL_ALIGNMENT_LEFT); + assertEquals(textbox.getVerticalAlignment(), HSSFTextbox.VERTICAL_ALIGNMENT_TOP); + assertEquals(textbox.getMarginTop(), 0); + assertEquals(textbox.getMarginBottom(), 3600000); + assertEquals(textbox.getMarginLeft(), 3600000); + assertEquals(textbox.getMarginRight(), 0); + assertEquals(textbox.getString().getString(), "teeeeesssstttt"); + } +} diff --git a/test-data/spreadsheet/drawings.xls b/test-data/spreadsheet/drawings.xls index 27de3b575db9c9d8b04567a1a410b869785263b3..8ea863f59a6eb022c8a5cfa3946adec55f443e46 100644 GIT binary patch delta 4422 zcma)=d0b5U|HnV)%rxt1T8tVcgIb+Vf{``F&kI$LUd4AsS_vdv!bLM^CXQWTerTykv zV$AleLj#7*DpkS5ajt>y@@1cVZ((+_4a`*B%TG4M9KnoagDPja9DoA`U;-7{S(YWn zkoB^}n6J!$E0P^$bN@dh9v5U`008`^!j0$n@2NUR#G4H+;mTQc&O{VYog);OVi>Uf zaY87I;HWAlMY3Nx)Vj!5c7`6IHGYU9l*tvwBZ_zi-0xAf2pnktyEb(N$z1vha{n1s z{WChsW-|crEp6gXn(n%aM-1-eMh^T{TXNqt@ z%r7Ac8{#M{0yw)-j0qA#6BA^HHVgi9ubof1$!*aQ7g7NLa-~eZH#Yj9wv4i(Hpdrr zBn7CTPTAn4#Wr2~#2a8>aHcw{{=BH(s5EPd$bb+dlJhjRQAa-cc4^rrC3(|uyKmv? z)H$Ucq>`9MFWjf$VY#hz7&fZr*WOaP4rWq~1KIguu? zZ8JiAO7IcY#I8i^VjC8fR3H^ZHJxKiXZ2mAcuqRn~mqyvpUr_bAA zc5=~guqB8_>eB24^9$Kpuzw4UE_~|mxxaX$GZf|0h{@w=75CJJK=BoPC%>QxM=G!5 z;)Z0_$3vC#Gyje1wuz1ekEH53L` zGa`5eHyKFe4TiX)nUOG7A=1BX$x%UXWi*sNqkZn)eEaR*t{hi*g27a(%zG&FK5NCW zgpHGFl!i@Q^(kqMGkiOjMunS;$61w>%!lf0Xf)ih**h!`eu7qMG|K+ONwu)_TLhP$ zpi#rHSA`@oz!)aiGF`a^%}k{77E@f&!rZT=P&rFhVQk-5v=JVCLHl^7cT`qJ`o4#? zEET27c#YC2)`PDBeEAcNNafYVuH0sCGS*czV9HDn4 zjmC$MEK5#pjD`srG_vuYUggsjWD0*Pq0z>&2l<}WIaA@KCKiWX(8@w8@36!bZLBtt zLZwyl>!6OUA9CT7x3o{9UaaP2pEys*RAnnw-dVH6a+N5>1R71J(Q2y(oq(G>F;XcU*bBFu)&}f(YK0*1%Rt%m$MWeuxt%bwW6MA9yE%r;5 zf;Ki%d6zA&XlM72Q>fH=|4x=W5eh2gv`cw>-YfqD$9kY4%KhIH#nX|+yzHZetle_&X*z><;_uM6LXz0w_lfkD}-4ww4W;B;@Gt)WGXDnqESnWj-R8ur5Eft zL!*^PjCr3>J23#XA;(9S^`?{UNx9UP&qLgTdP6{EmuOCL1&MEjg(yl`!JXZ;Dz z8^cwK%zRv%d$p@S4@Owhs8Q7n3u2Y36?NY6E-U}(JVc6cV9Mw9h_W_&CROoeSS z8oAXOyQd_2XTZNpX;c?>xkl?k%vdh015@v*LY!8Ni{_ z0PPcbEz7p`aGW_@qs3FI^nNX1|7O)c9`2k;BTO8>*YWT@A9!*xjhafzQ=&|l&4n!+ zX{5P@V>{x1%O3V0q|xJt zRQ9B$RCR#mpJDJI?K8#clu6-j2QEw-$5*QKl^$ZoUN7GXi|lB`*kjT@3A7Huo6Bev zljO-ghNu1n<^QEodo=qiBR;qd3Ug@G^IUxOM$HZew7p29=U7VV*reA}U|=Ud0z25v zZ`89y4`(aXtx=zex((_hqobo#w}*GS`MMZc+r<}GJmM!zP+ZA-w*IqdmkD>FbohH* zJ${~=shVY9I~Sf9PyEiuquL_FUe%^@dHmD)sh ziHW(G2dVEs#PU6qJUFe1Jmf%3#=Oa^4um$tsC9r6w+vc15;ZKoH!?#Am-d;W(6f;y zr}>ms{Ts^467HE#Tx0QlD69H6xRXb-LO}WgLK9m+nTl|wC5bGB@aqENG>g9gnHM4R z;KkqdKJ`O-JtYPNIsk#oZQF%8yN|o3cc_)O>7CDIk$mFP_ zPEFHoj)?3CBP_^G7K9jvBz_B4qCbIC9F3ij1MEk`e*P7-Paz zg3hSgQ4KKVji|WHCwK}=%8I(pO40qX7!B#$Op1198<4)A3DOkD?TVP0isMyOt+K#X zRG@2sQBhIbc2dGyvvkW(?p>`e1~XEv}iwh8muPJ1}h0I$)!bI(OqW57X334_5Mel7s{j6D_{C1h|Jd3wQiNJzOl=EnT6r> z4FjKN?SH$m>xML}Xu{fE`x6`I`UV7jIklSoOHfEc^VB}$a9^?@vj6A4y=t4n$AvfZ zgSeq(HwS&}w+3C3z}F^~!i(LyH-hI>PGQ30sXgnqH?+Rr z*OW0;w0@C==iSWO4U03H-u>PWeQGj;bt9sR+dQQ^^E-29HoG6scrb0EeuR!7_+$B> zne%756=sI(?Mms)DO^3F&Pp%z#BD9|8Mo-#uHYdRuYueHTblbaAFR%8wl&#nmls-g z@u{2D3;Xtr)||ufFJhgnB}21b_!O%hdjF{Yzk|Io>zDcUCO7?0Yx>eHRU>Kz;j%lT zffnpV(#~w{eeArgHqTSs&c2Q661b_KcKwfuj;c*vYnwHrBO!N1=v|fYo#uyDrs&Sm zz9GrZn&%Y}6@FyY=+^ZW<`(rme8QoA)9&$ zOZ9{9w^4bQv+gYUUcS38E|}BX{pD;;)uvkAi|dR)iOTER*RDUW``{9l8=&g7IKl4z z`952}yt10@aoMd-6NfL9tvD5;dMWLvN9SW~o$ovxmKS|HHYGpgd4Am1x#dYEvSWW$ z`kl&hnVIZ#NVET(>z0A@)i*wl+0@0H$TnD+=vK2Tk%)}f&(SL^?oB_ryO^V$pK5i9 zQIg&Hm^Y)|apa8(w{o(iZU))oOpN7ytuj1eif-FtjNG=07%TDje(ZJ4Oq6W%K@#CO zX65dLhlhW5b=f&Pzo**F!2HUAQz3WDgF2gbNG|KE8#+FExaof7ul1KB8=V7|=-%3< ziW~U$6%>8WsfqiLd<4Xf5HjZTU6^ph{m0n= zWPJV_mj9_~sc%^8z1ipARgvvRt34ZfSgz4J0*$xw<;(0}*%^Ka68&|+X!4k;rcN2} zeJ|T3)*S!eSxL0ke4pL(&Hj#1JD#TZw!dwf-bCAPF++TEy)Ti){pV*(E}TVJ>;Ai$ zRBrjPT_JO45tAIW(ZiBz>T*qVr2a@~VK6~Fda=#IL@4b5pdqK`gMbbg3p7z;L1`tJ zN#ULMs5JmzG1*S3; z8VSfZVMI$s1W}E1=Dq6si2m+ikQ(8H&A}+b4Evijk0Ly<4suHru>_^-dr`zR4wW;h Wmv(X_iqOTFvWZMR=(LuoV*DRPI@??T delta 3102 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