| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| |
| package org.apache.poi.hssf.usermodel; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.poi.ddf.EscherBoolProperty; |
| import org.apache.poi.ddf.EscherChildAnchorRecord; |
| import org.apache.poi.ddf.EscherClientAnchorRecord; |
| 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.EscherPropertyTypes; |
| import org.apache.poi.ddf.EscherRGBProperty; |
| import org.apache.poi.ddf.EscherSimpleProperty; |
| import org.apache.poi.ddf.EscherSpRecord; |
| import org.apache.poi.hssf.record.CommonObjectDataSubRecord; |
| import org.apache.poi.hssf.record.ObjRecord; |
| import org.apache.poi.ss.usermodel.Shape; |
| import org.apache.poi.util.LittleEndian; |
| import org.apache.poi.util.StringUtil; |
| |
| /** |
| * An abstract shape. |
| * |
| * Note: Microsoft Excel seems to sometimes disallow |
| * higher y1 than y2 or higher x1 than x2 in the anchor, you might need to |
| * reverse them and draw shapes vertically or horizontally flipped via |
| * setFlipVertical() or setFlipHorizontally(). |
| */ |
| public abstract class HSSFShape implements Shape { |
| private static final Logger LOG = LogManager.getLogger(HSSFShape.class); |
| |
| public static final int LINEWIDTH_ONE_PT = 12700; |
| public static final int LINEWIDTH_DEFAULT = 9525; |
| public static final int LINESTYLE__COLOR_DEFAULT = 0x08000040; |
| public static final int FILL__FILLCOLOR_DEFAULT = 0x08000009; |
| public static final boolean NO_FILL_DEFAULT = true; |
| |
| public static final int LINESTYLE_SOLID = 0; // Solid (continuous) pen |
| public static final int LINESTYLE_DASHSYS = 1; // PS_DASH system dash style |
| public static final int LINESTYLE_DOTSYS = 2; // PS_DOT system dash style |
| public static final int LINESTYLE_DASHDOTSYS = 3; // PS_DASHDOT system dash style |
| public static final int LINESTYLE_DASHDOTDOTSYS = 4; // PS_DASHDOTDOT system dash style |
| public static final int LINESTYLE_DOTGEL = 5; // square dot style |
| public static final int LINESTYLE_DASHGEL = 6; // dash style |
| public static final int LINESTYLE_LONGDASHGEL = 7; // long dash style |
| public static final int LINESTYLE_DASHDOTGEL = 8; // dash short dash |
| public static final int LINESTYLE_LONGDASHDOTGEL = 9; // long dash short dash |
| public static final int LINESTYLE_LONGDASHDOTDOTGEL = 10; // long dash short dash short dash |
| public static final int LINESTYLE_NONE = -1; |
| |
| public static final int LINESTYLE_DEFAULT = LINESTYLE_NONE; |
| |
| // TODO - make all these fields private |
| private HSSFShape parent; |
| HSSFAnchor anchor; |
| private HSSFPatriarch _patriarch; |
| |
| private final EscherContainerRecord _escherContainer; |
| private final ObjRecord _objRecord; |
| private final EscherOptRecord _optRecord; |
| |
| public static final int NO_FILLHITTEST_TRUE = 0x00110000; |
| public static final int NO_FILLHITTEST_FALSE = 0x00010000; |
| |
| /** |
| * creates shapes from existing file |
| * @param spContainer |
| * @param objRecord |
| */ |
| public HSSFShape(EscherContainerRecord spContainer, ObjRecord objRecord) { |
| this._escherContainer = spContainer; |
| this._objRecord = objRecord; |
| this._optRecord = spContainer.getChildById(EscherOptRecord.RECORD_ID); |
| this.anchor = HSSFAnchor.createAnchorFromEscher(spContainer); |
| } |
| |
| /** |
| * Create a new shape with the specified parent and anchor. |
| */ |
| public HSSFShape(HSSFShape parent, HSSFAnchor anchor) { |
| this.parent = parent; |
| this.anchor = anchor; |
| this._escherContainer = createSpContainer(); |
| _optRecord = _escherContainer.getChildById(EscherOptRecord.RECORD_ID); |
| _objRecord = createObjRecord(); |
| |
| } |
| |
| protected abstract EscherContainerRecord createSpContainer(); |
| |
| protected abstract ObjRecord createObjRecord(); |
| |
| /** |
| * remove escher container from the patriarch.escherAggregate |
| * remove obj, textObj and note records if it's necessary |
| * in case of ShapeGroup remove all contained shapes |
| * @param patriarch |
| */ |
| protected abstract void afterRemove(HSSFPatriarch patriarch); |
| |
| /** |
| * @param shapeId - global shapeId which must be set to EscherSpRecord |
| */ |
| 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)); |
| } |
| |
| /** |
| * @return global shapeId(from EscherSpRecord) |
| */ |
| int getShapeId(){ |
| return ((EscherSpRecord)_escherContainer.getChildById(EscherSpRecord.RECORD_ID)).getShapeId(); |
| } |
| |
| abstract void afterInsert(HSSFPatriarch patriarch); |
| |
| protected EscherContainerRecord getEscherContainer() { |
| return _escherContainer; |
| } |
| |
| protected ObjRecord getObjRecord() { |
| return _objRecord; |
| } |
| |
| /** |
| * Return the low-level EscherOptRecord to read/modify not yet wrapped escher properties |
| * |
| * @return the low-level EscherOptRecord |
| */ |
| public EscherOptRecord getOptRecord() { |
| return _optRecord; |
| } |
| |
| @Override |
| public HSSFShape getParent() { |
| return parent; |
| } |
| |
| /** |
| * @return the anchor that is used by this shape. |
| */ |
| @Override |
| public HSSFAnchor getAnchor() { |
| return anchor; |
| } |
| |
| /** |
| * Sets a particular anchor. A top-level shape must have an anchor of |
| * HSSFClientAnchor. A child anchor must have an anchor of HSSFChildAnchor |
| * |
| * @param anchor the anchor to use. |
| * @throws IllegalArgumentException when the wrong anchor is used for |
| * this particular shape. |
| * @see HSSFChildAnchor |
| * @see HSSFClientAnchor |
| */ |
| public void setAnchor(HSSFAnchor anchor) { |
| int i = 0; |
| int recordId = -1; |
| if (parent == null) { |
| if (anchor instanceof HSSFChildAnchor) |
| throw new IllegalArgumentException("Must use client anchors for shapes directly attached to sheet."); |
| EscherClientAnchorRecord anch = _escherContainer.getChildById(EscherClientAnchorRecord.RECORD_ID); |
| if (null != anch) { |
| for (i=0; i< _escherContainer.getChildCount(); i++){ |
| if (_escherContainer.getChild(i).getRecordId() == EscherClientAnchorRecord.RECORD_ID){ |
| if (i != _escherContainer.getChildCount() -1){ |
| recordId = _escherContainer.getChild(i+1).getRecordId(); |
| } |
| } |
| } |
| _escherContainer.removeChildRecord(anch); |
| } |
| } else { |
| if (anchor instanceof HSSFClientAnchor) |
| throw new IllegalArgumentException("Must use child anchors for shapes attached to groups."); |
| EscherChildAnchorRecord anch = _escherContainer.getChildById(EscherChildAnchorRecord.RECORD_ID); |
| if (null != anch) { |
| for (i=0; i< _escherContainer.getChildCount(); i++){ |
| if (_escherContainer.getChild(i).getRecordId() == EscherChildAnchorRecord.RECORD_ID){ |
| if (i != _escherContainer.getChildCount() -1){ |
| recordId = _escherContainer.getChild(i+1).getRecordId(); |
| } |
| } |
| } |
| _escherContainer.removeChildRecord(anch); |
| } |
| } |
| if (-1 == recordId){ |
| _escherContainer.addChildRecord(anchor.getEscherAnchor()); |
| } else { |
| _escherContainer.addChildBefore(anchor.getEscherAnchor(), recordId); |
| } |
| this.anchor = anchor; |
| } |
| |
| /** |
| * The color applied to the lines of this shape. |
| */ |
| public int getLineStyleColor() { |
| EscherRGBProperty rgbProperty = _optRecord.lookup(EscherPropertyTypes.LINESTYLE__COLOR); |
| return rgbProperty == null ? LINESTYLE__COLOR_DEFAULT : rgbProperty.getRgbColor(); |
| } |
| |
| /** |
| * The color applied to the lines of this shape. |
| */ |
| public void setLineStyleColor(int lineStyleColor) { |
| setPropertyValue(new EscherRGBProperty(EscherPropertyTypes.LINESTYLE__COLOR, lineStyleColor)); |
| } |
| |
| @Override |
| public void setLineStyleColor(int red, int green, int blue) { |
| int lineStyleColor = ((blue) << 16) | ((green) << 8) | red; |
| setPropertyValue(new EscherRGBProperty(EscherPropertyTypes.LINESTYLE__COLOR, lineStyleColor)); |
| } |
| |
| /** |
| * The color used to fill this shape. |
| */ |
| public int getFillColor() { |
| EscherRGBProperty rgbProperty = _optRecord.lookup(EscherPropertyTypes.FILL__FILLCOLOR); |
| return rgbProperty == null ? FILL__FILLCOLOR_DEFAULT : rgbProperty.getRgbColor(); |
| } |
| |
| /** |
| * The color used to fill this shape. |
| */ |
| public void setFillColor(int fillColor) { |
| setPropertyValue(new EscherRGBProperty(EscherPropertyTypes.FILL__FILLCOLOR, fillColor)); |
| } |
| |
| @Override |
| public void setFillColor(int red, int green, int blue) { |
| int fillColor = ((blue) << 16) | ((green) << 8) | red; |
| setPropertyValue(new EscherRGBProperty(EscherPropertyTypes.FILL__FILLCOLOR, fillColor)); |
| } |
| |
| /** |
| * @return returns with width of the line in EMUs. 12700 = 1 pt. |
| */ |
| public int getLineWidth() { |
| EscherSimpleProperty property = _optRecord.lookup(EscherPropertyTypes.LINESTYLE__LINEWIDTH); |
| return property == null ? LINEWIDTH_DEFAULT: property.getPropertyValue(); |
| } |
| |
| /** |
| * Sets the width of the line. 12700 = 1 pt. |
| * |
| * @param lineWidth width in EMU's. 12700EMU's = 1 pt |
| * @see HSSFShape#LINEWIDTH_ONE_PT |
| */ |
| public void setLineWidth(int lineWidth) { |
| setPropertyValue(new EscherSimpleProperty(EscherPropertyTypes.LINESTYLE__LINEWIDTH, lineWidth)); |
| } |
| |
| /** |
| * @return One of the constants in LINESTYLE_* |
| */ |
| public int getLineStyle() { |
| EscherSimpleProperty property = _optRecord.lookup(EscherPropertyTypes.LINESTYLE__LINEDASHING); |
| if (null == property){ |
| return LINESTYLE_DEFAULT; |
| } |
| return property.getPropertyValue(); |
| } |
| |
| /** |
| * Sets the line style. |
| * |
| * @param lineStyle One of the constants in LINESTYLE_* |
| */ |
| public void setLineStyle(int lineStyle) { |
| setPropertyValue(new EscherSimpleProperty(EscherPropertyTypes.LINESTYLE__LINEDASHING, lineStyle)); |
| if (getLineStyle() != HSSFShape.LINESTYLE_SOLID) { |
| setPropertyValue(new EscherSimpleProperty(EscherPropertyTypes.LINESTYLE__LINEENDCAPSTYLE, 0)); |
| if (getLineStyle() == HSSFShape.LINESTYLE_NONE){ |
| setPropertyValue(new EscherBoolProperty( EscherPropertyTypes.LINESTYLE__NOLINEDRAWDASH, 0x00080000)); |
| } else { |
| setPropertyValue( new EscherBoolProperty( EscherPropertyTypes.LINESTYLE__NOLINEDRAWDASH, 0x00080008)); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isNoFill() { |
| EscherBoolProperty property = _optRecord.lookup(EscherPropertyTypes.FILL__NOFILLHITTEST); |
| return property == null ? NO_FILL_DEFAULT : property.getPropertyValue() == NO_FILLHITTEST_TRUE; |
| } |
| |
| @Override |
| public void setNoFill(boolean noFill) { |
| setPropertyValue(new EscherBoolProperty(EscherPropertyTypes.FILL__NOFILLHITTEST, noFill ? NO_FILLHITTEST_TRUE : NO_FILLHITTEST_FALSE)); |
| } |
| |
| protected void setPropertyValue(EscherProperty property){ |
| _optRecord.setEscherProperty(property); |
| } |
| |
| /** |
| * @param value specifies whether this shape is vertically flipped. |
| */ |
| public void setFlipVertical(boolean value){ |
| EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); |
| if (value){ |
| sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPVERT); |
| } else { |
| sp.setFlags(sp.getFlags() & (Integer.MAX_VALUE - EscherSpRecord.FLAG_FLIPVERT)); |
| } |
| } |
| |
| /** |
| * @param value specifies whether this shape is horizontally flipped. |
| */ |
| public void setFlipHorizontal(boolean value){ |
| EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); |
| if (value){ |
| sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPHORIZ); |
| } else { |
| sp.setFlags(sp.getFlags() & (Integer.MAX_VALUE - EscherSpRecord.FLAG_FLIPHORIZ)); |
| } |
| } |
| |
| /** |
| * @return whether this shape is vertically flipped. |
| */ |
| public boolean isFlipVertical(){ |
| EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); |
| return (sp.getFlags() & EscherSpRecord.FLAG_FLIPVERT) != 0; |
| } |
| |
| /** |
| * @return whether this shape is horizontally flipped. |
| */ |
| public boolean isFlipHorizontal(){ |
| EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); |
| return (sp.getFlags() & EscherSpRecord.FLAG_FLIPHORIZ) != 0; |
| } |
| |
| /** |
| * @return the rotation, in degrees, that is applied to a shape. |
| */ |
| public int getRotationDegree(){ |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| EscherSimpleProperty property = getOptRecord().lookup(EscherPropertyTypes.TRANSFORM__ROTATION); |
| if (null == property){ |
| return 0; |
| } |
| try { |
| LittleEndian.putInt(property.getPropertyValue(), bos); |
| return LittleEndian.getShort(bos.toByteArray(), 2); |
| } catch (IOException e) { |
| LOG.atError().withThrowable(e).log("can't determine rotation degree"); |
| return 0; |
| } |
| } |
| |
| /** |
| * specifies the rotation, in degrees, that is applied to a shape. |
| * Positive values specify rotation in the clockwise direction. |
| * Negative values specify rotation in the counterclockwise direction. |
| * Rotation occurs around the center of the shape. |
| * The default value for this property is 0x00000000 |
| * @param value |
| */ |
| public void setRotationDegree(short value){ |
| setPropertyValue(new EscherSimpleProperty(EscherPropertyTypes.TRANSFORM__ROTATION , (value << 16))); |
| } |
| |
| /** |
| * Count of all children and their children's children. |
| */ |
| public int countOfAllChildren() { |
| return 1; |
| } |
| |
| protected abstract HSSFShape cloneShape(); |
| |
| protected void setPatriarch(HSSFPatriarch _patriarch) { |
| this._patriarch = _patriarch; |
| } |
| |
| public HSSFPatriarch getPatriarch() { |
| return _patriarch; |
| } |
| |
| protected void setParent(HSSFShape parent) { |
| this.parent = parent; |
| } |
| |
| /** |
| * @return the name of this shape |
| */ |
| public String getShapeName() { |
| EscherOptRecord eor = getOptRecord(); |
| if (eor == null) { |
| return null; |
| } |
| EscherProperty ep = eor.lookup(EscherPropertyTypes.GROUPSHAPE__SHAPENAME); |
| if (ep instanceof EscherComplexProperty) { |
| return StringUtil.getFromUnicodeLE(((EscherComplexProperty)ep).getComplexData()); |
| } |
| return null; |
| } |
| } |