| /* ==================================================================== |
| 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.hslf.usermodel; |
| |
| import java.awt.geom.Rectangle2D; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.poi.ddf.EscherChildAnchorRecord; |
| import org.apache.poi.ddf.EscherClientAnchorRecord; |
| 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.sl.usermodel.GroupShape; |
| import org.apache.poi.sl.usermodel.PictureData; |
| import org.apache.poi.sl.usermodel.ShapeContainer; |
| import org.apache.poi.sl.usermodel.ShapeType; |
| import org.apache.poi.util.LittleEndian; |
| import org.apache.poi.util.Units; |
| |
| /** |
| * Represents a group of shapes. |
| */ |
| public class HSLFGroupShape extends HSLFShape |
| implements HSLFShapeContainer, GroupShape<HSLFShape,HSLFTextParagraph> { |
| private static final Logger LOG = LogManager.getLogger(HSLFGroupShape.class); |
| |
| /** |
| * Create a new ShapeGroup. This constructor is used when a new shape is created. |
| * |
| */ |
| public HSLFGroupShape(){ |
| this(null, null); |
| createSpContainer(false); |
| } |
| |
| /** |
| * Create a new ShapeGroup. This constructor is used when a new shape is created. |
| * |
| * @param parent the parent of the shape |
| */ |
| public HSLFGroupShape(ShapeContainer<HSLFShape,HSLFTextParagraph> parent){ |
| this(null, parent); |
| createSpContainer(parent instanceof HSLFGroupShape); |
| } |
| |
| /** |
| * Create a ShapeGroup object and initialize it from the supplied Record container. |
| * |
| * @param escherRecord <code>EscherSpContainer</code> container which holds information about this shape |
| * @param parent the parent of the shape |
| */ |
| protected HSLFGroupShape(EscherContainerRecord escherRecord, ShapeContainer<HSLFShape,HSLFTextParagraph> parent){ |
| super(escherRecord, parent); |
| } |
| |
| @Override |
| public void setAnchor(Rectangle2D anchor) { |
| EscherClientAnchorRecord clientAnchor = getEscherChild(EscherClientAnchorRecord.RECORD_ID); |
| boolean isInitialized = !(clientAnchor.getDx1() == 0 && clientAnchor.getRow1() == 0); |
| |
| if (isInitialized) { |
| moveAndScale(anchor); |
| } else { |
| setExteriorAnchor(anchor); |
| } |
| } |
| |
| @Override |
| public void setInteriorAnchor(Rectangle2D anchor){ |
| EscherSpgrRecord spgr = getEscherChild(EscherSpgrRecord.RECORD_ID); |
| |
| int x1 = Units.pointsToMaster(anchor.getX()); |
| int y1 = Units.pointsToMaster(anchor.getY()); |
| int x2 = Units.pointsToMaster(anchor.getX() + anchor.getWidth()); |
| int y2 = Units.pointsToMaster(anchor.getY() + anchor.getHeight()); |
| |
| spgr.setRectX1(x1); |
| spgr.setRectY1(y1); |
| spgr.setRectX2(x2); |
| spgr.setRectY2(y2); |
| } |
| |
| @Override |
| public Rectangle2D getInteriorAnchor(){ |
| EscherSpgrRecord rec = getEscherChild(EscherSpgrRecord.RECORD_ID); |
| double x1 = Units.masterToPoints(rec.getRectX1()); |
| double y1 = Units.masterToPoints(rec.getRectY1()); |
| double x2 = Units.masterToPoints(rec.getRectX2()); |
| double y2 = Units.masterToPoints(rec.getRectY2()); |
| return new Rectangle2D.Double(x1,y1,x2-x1,y2-y1); |
| } |
| |
| protected void setExteriorAnchor(Rectangle2D anchor) { |
| EscherClientAnchorRecord clientAnchor = getEscherChild(EscherClientAnchorRecord.RECORD_ID); |
| |
| //hack. internal variable EscherClientAnchorRecord.shortRecord can be |
| //initialized only in fillFields(). We need to set shortRecord=false; |
| byte[] header = new byte[16]; |
| LittleEndian.putUShort(header, 0, 0); |
| LittleEndian.putUShort(header, 2, 0); |
| LittleEndian.putInt(header, 4, 8); |
| clientAnchor.fillFields(header, 0, null); |
| |
| // All coordinates need to be converted to Master units (576 dpi) |
| clientAnchor.setFlag((short)Units.pointsToMaster(anchor.getY())); |
| clientAnchor.setCol1((short)Units.pointsToMaster(anchor.getX())); |
| clientAnchor.setDx1((short)Units.pointsToMaster(anchor.getWidth() + anchor.getX())); |
| clientAnchor.setRow1((short)Units.pointsToMaster(anchor.getHeight() + anchor.getY())); |
| |
| // TODO: does this make sense? |
| setInteriorAnchor(anchor); |
| } |
| |
| /** |
| * Create a new ShapeGroup and create an instance of <code>EscherSpgrContainer</code> which represents a group of shapes |
| */ |
| @Override |
| protected EscherContainerRecord createSpContainer(boolean isChild) { |
| EscherContainerRecord ecr = super.createSpContainer(isChild); |
| ecr.setRecordId(EscherContainerRecord.SPGR_CONTAINER); |
| |
| //The group itself is a shape, and always appears as the first EscherSpContainer in the group container. |
| EscherContainerRecord spcont = new EscherContainerRecord(); |
| spcont.setRecordId(EscherContainerRecord.SP_CONTAINER); |
| spcont.setOptions((short)15); |
| |
| EscherSpgrRecord spg = new EscherSpgrRecord(); |
| spg.setOptions((short)1); |
| spcont.addChildRecord(spg); |
| |
| EscherSpRecord sp = new EscherSpRecord(); |
| short type = (short)((ShapeType.NOT_PRIMITIVE.nativeId << 4) + 2); |
| sp.setOptions(type); |
| sp.setFlags(EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_GROUP); |
| spcont.addChildRecord(sp); |
| |
| EscherClientAnchorRecord anchor = new EscherClientAnchorRecord(); |
| spcont.addChildRecord(anchor); |
| |
| ecr.addChildRecord(spcont); |
| return ecr; |
| } |
| |
| /** |
| * Add a shape to this group. |
| * |
| * @param shape - the Shape to add |
| */ |
| @Override |
| public void addShape(HSLFShape shape){ |
| getSpContainer().addChildRecord(shape.getSpContainer()); |
| |
| HSLFSheet sheet = getSheet(); |
| shape.setSheet(sheet); |
| shape.setShapeId(sheet.allocateShapeId()); |
| shape.afterInsert(sheet); |
| } |
| |
| /** |
| * Moves and scales this <code>ShapeGroup</code> to the specified anchor. |
| */ |
| protected void moveAndScale(Rectangle2D anchorDest){ |
| Rectangle2D anchorSrc = getAnchor(); |
| double scaleX = (anchorSrc.getWidth() == 0) ? 0 : anchorDest.getWidth() / anchorSrc.getWidth(); |
| double scaleY = (anchorSrc.getHeight() == 0) ? 0 : anchorDest.getHeight() / anchorSrc.getHeight(); |
| |
| setExteriorAnchor(anchorDest); |
| |
| for (HSLFShape shape : getShapes()) { |
| Rectangle2D chanchor = shape.getAnchor(); |
| double x = anchorDest.getX()+(chanchor.getX()-anchorSrc.getX())*scaleX; |
| double y = anchorDest.getY()+(chanchor.getY()-anchorSrc.getY())*scaleY; |
| double width = chanchor.getWidth()*scaleX; |
| double height = chanchor.getHeight()*scaleY; |
| shape.setAnchor(new Rectangle2D.Double(x, y, width, height)); |
| } |
| } |
| |
| /** |
| * Returns the anchor (the bounding box rectangle) of this shape group. |
| * All coordinates are expressed in points (72 dpi). |
| * |
| * @return the anchor of this shape group |
| */ |
| @Override |
| public Rectangle2D getAnchor(){ |
| EscherClientAnchorRecord clientAnchor = getEscherChild(EscherClientAnchorRecord.RECORD_ID); |
| int x1,y1,x2,y2; |
| if(clientAnchor == null){ |
| LOG.atInfo().log("EscherClientAnchorRecord was not found for shape group. Searching for EscherChildAnchorRecord."); |
| EscherChildAnchorRecord rec = getEscherChild(EscherChildAnchorRecord.RECORD_ID); |
| x1 = rec.getDx1(); |
| y1 = rec.getDy1(); |
| x2 = rec.getDx2(); |
| y2 = rec.getDy2(); |
| } else { |
| x1 = clientAnchor.getCol1(); |
| y1 = clientAnchor.getFlag(); |
| x2 = clientAnchor.getDx1(); |
| y2 = clientAnchor.getRow1(); |
| } |
| |
| return new Rectangle2D.Double( |
| (x1 == -1 ? -1 : Units.masterToPoints(x1)), |
| (y1 == -1 ? -1 : Units.masterToPoints(y1)), |
| (x2 == -1 ? -1 : Units.masterToPoints(x2-x1)), |
| (y2 == -1 ? -1 : Units.masterToPoints(y2-y1)) |
| ); |
| } |
| |
| /** |
| * Return type of the shape. |
| * In most cases shape group type is {@link ShapeType#NOT_PRIMITIVE} |
| * |
| * @return type of the shape. |
| */ |
| @Override |
| public ShapeType getShapeType(){ |
| EscherSpRecord spRecord = getEscherChild(EscherSpRecord.RECORD_ID); |
| int nativeId = spRecord.getOptions() >> 4; |
| return ShapeType.forId(nativeId, false); |
| } |
| |
| /** |
| * Returns <code>null</code> - shape groups can't have hyperlinks |
| * |
| * @return <code>null</code>. |
| */ |
| public HSLFHyperlink getHyperlink(){ |
| return null; |
| } |
| |
| @Override |
| public <T extends EscherRecord> T getEscherChild(int recordId){ |
| EscherContainerRecord groupInfoContainer = (EscherContainerRecord)getSpContainer().getChild(0); |
| return groupInfoContainer.getChildById((short)recordId); |
| } |
| |
| @Override |
| public Iterator<HSLFShape> iterator() { |
| return getShapes().iterator(); |
| } |
| |
| @Override |
| public boolean removeShape(HSLFShape shape) { |
| // TODO: implement! |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public List<HSLFShape> getShapes() { |
| // Our escher container record should contain several |
| // SpContainers, the first of which is the group shape itself |
| List<HSLFShape> shapeList = new ArrayList<>(); |
| boolean isFirst = true; |
| for (EscherRecord r : getSpContainer()) { |
| if (isFirst) { |
| // Don't include the first SpContainer, it is always NotPrimitive |
| isFirst = false; |
| continue; |
| } |
| |
| if(r instanceof EscherContainerRecord) { |
| // Create the Shape for it |
| EscherContainerRecord container = (EscherContainerRecord)r; |
| HSLFShape shape = HSLFShapeFactory.createShape(container, this); |
| shape.setSheet(getSheet()); |
| shapeList.add( shape ); |
| } else { |
| // Should we do anything special with these non |
| // Container records? |
| LOG.atError().log("Shape contained non container escher record, was {}", r.getClass().getName()); |
| } |
| } |
| |
| return shapeList; |
| } |
| |
| @Override |
| public HSLFTextBox createTextBox() { |
| HSLFTextBox s = new HSLFTextBox(this); |
| s.setHorizontalCentered(true); |
| s.setAnchor(new Rectangle2D.Double(0, 0, 100, 100)); |
| addShape(s); |
| return s; |
| } |
| |
| @Override |
| public HSLFAutoShape createAutoShape() { |
| HSLFAutoShape s = new HSLFAutoShape(ShapeType.RECT, this); |
| s.setHorizontalCentered(true); |
| s.setAnchor(new Rectangle2D.Double(0, 0, 100, 100)); |
| addShape(s); |
| return s; |
| } |
| |
| @Override |
| public HSLFFreeformShape createFreeform() { |
| HSLFFreeformShape s = new HSLFFreeformShape(this); |
| s.setHorizontalCentered(true); |
| s.setAnchor(new Rectangle2D.Double(0, 0, 100, 100)); |
| addShape(s); |
| return s; |
| } |
| |
| @Override |
| public HSLFConnectorShape createConnector() { |
| HSLFConnectorShape s = new HSLFConnectorShape(this); |
| s.setAnchor(new Rectangle2D.Double(0, 0, 100, 100)); |
| addShape(s); |
| return s; |
| } |
| |
| @Override |
| public HSLFGroupShape createGroup() { |
| HSLFGroupShape s = new HSLFGroupShape(this); |
| s.setAnchor(new Rectangle2D.Double(0, 0, 100, 100)); |
| addShape(s); |
| return s; |
| } |
| |
| @Override |
| public HSLFPictureShape createPicture(PictureData pictureData) { |
| if (!(pictureData instanceof HSLFPictureData)) { |
| throw new IllegalArgumentException("pictureData needs to be of type HSLFPictureData"); |
| } |
| HSLFPictureShape s = new HSLFPictureShape((HSLFPictureData)pictureData, this); |
| s.setAnchor(new Rectangle2D.Double(0, 0, 100, 100)); |
| addShape(s); |
| return s; |
| } |
| |
| @Override |
| public HSLFTable createTable(int numRows, int numCols) { |
| if (numRows < 1 || numCols < 1) { |
| throw new IllegalArgumentException("numRows and numCols must be greater than 0"); |
| } |
| HSLFTable s = new HSLFTable(numRows,numCols,this); |
| s.setAnchor(new Rectangle2D.Double(0, 0, 100, 100)); |
| addShape(s); |
| return s; |
| } |
| |
| @Override |
| public HSLFObjectShape createOleShape(PictureData pictureData) { |
| if (!(pictureData instanceof HSLFPictureData)) { |
| throw new IllegalArgumentException("pictureData needs to be of type HSLFPictureData"); |
| } |
| HSLFObjectShape s = new HSLFObjectShape((HSLFPictureData)pictureData, this); |
| s.setAnchor(new Rectangle2D.Double(0, 0, 100, 100)); |
| addShape(s); |
| return s; |
| } |
| } |