123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- /* ====================================================================
- 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.Arc2D;
- import java.awt.geom.Path2D;
- import java.awt.geom.Point2D;
- import java.awt.geom.Rectangle2D;
- import java.util.Iterator;
- import java.util.List;
-
- import org.apache.poi.ddf.AbstractEscherOptRecord;
- import org.apache.poi.ddf.EscherArrayProperty;
- import org.apache.poi.ddf.EscherContainerRecord;
- import org.apache.poi.ddf.EscherProperties;
- import org.apache.poi.ddf.EscherProperty;
- import org.apache.poi.ddf.EscherSimpleProperty;
- import org.apache.poi.sl.draw.binding.CTAdjPoint2D;
- import org.apache.poi.sl.draw.binding.CTCustomGeometry2D;
- import org.apache.poi.sl.draw.binding.CTPath2D;
- import org.apache.poi.sl.draw.binding.CTPath2DArcTo;
- import org.apache.poi.sl.draw.binding.CTPath2DCubicBezierTo;
- import org.apache.poi.sl.draw.binding.CTPath2DLineTo;
- import org.apache.poi.sl.draw.binding.CTPath2DList;
- import org.apache.poi.sl.draw.binding.CTPath2DMoveTo;
- import org.apache.poi.sl.draw.binding.ObjectFactory;
- import org.apache.poi.sl.draw.geom.CustomGeometry;
- import org.apache.poi.sl.usermodel.AutoShape;
- import org.apache.poi.sl.usermodel.ShapeContainer;
- import org.apache.poi.sl.usermodel.ShapeType;
- import org.apache.poi.sl.usermodel.VerticalAlignment;
- import org.apache.poi.ss.usermodel.ShapeTypes;
- import org.apache.poi.util.BitField;
- import org.apache.poi.util.BitFieldFactory;
- import org.apache.poi.util.LittleEndian;
- import org.apache.poi.util.POILogFactory;
- import org.apache.poi.util.POILogger;
-
- /**
- * Represents an AutoShape.<p>
- *
- * AutoShapes are drawing objects with a particular shape that may be customized through smart resizing and adjustments.
- * See {@link ShapeTypes}
- */
- public class HSLFAutoShape extends HSLFTextShape implements AutoShape<HSLFShape,HSLFTextParagraph> {
- private static final POILogger LOG = POILogFactory.getLogger(HSLFAutoShape.class);
-
- static final byte[] SEGMENTINFO_MOVETO = new byte[]{0x00, 0x40};
- static final byte[] SEGMENTINFO_LINETO = new byte[]{0x00, (byte)0xAC};
- static final byte[] SEGMENTINFO_ESCAPE = new byte[]{0x01, 0x00};
- static final byte[] SEGMENTINFO_ESCAPE2 = new byte[]{0x01, 0x20};
- static final byte[] SEGMENTINFO_CUBICTO = new byte[]{0x00, (byte)0xAD};
- // OpenOffice inserts 0xB3 instead of 0xAD.
- // protected static final byte[] SEGMENTINFO_CUBICTO2 = new byte[]{0x00, (byte)0xB3};
- static final byte[] SEGMENTINFO_CLOSE = new byte[]{0x01, (byte)0x60};
- static final byte[] SEGMENTINFO_END = new byte[]{0x00, (byte)0x80};
-
- private static final BitField PATH_INFO = BitFieldFactory.getInstance(0xE000);
- private static final BitField ESCAPE_INFO = BitFieldFactory.getInstance(0x1F00);
-
- enum PathInfo {
- lineTo(0),curveTo(1),moveTo(2),close(3),end(4),escape(5),clientEscape(6);
- private final int flag;
- PathInfo(int flag) {
- this.flag = flag;
- }
- public int getFlag() {
- return flag;
- }
- static PathInfo valueOf(int flag) {
- for (PathInfo v : values()) {
- if (v.flag == flag) {
- return v;
- }
- }
- return null;
- }
- }
-
- enum EscapeInfo {
- EXTENSION(0x0000),
- ANGLE_ELLIPSE_TO(0x0001),
- ANGLE_ELLIPSE(0x0002),
- ARC_TO(0x0003),
- ARC(0x0004),
- CLOCKWISE_ARC_TO(0x0005),
- CLOCKWISE_ARC(0x0006),
- ELLIPTICAL_QUADRANT_X(0x0007),
- ELLIPTICAL_QUADRANT_Y(0x0008),
- QUADRATIC_BEZIER(0x0009),
- NO_FILL(0X000A),
- NO_LINE(0X000B),
- AUTO_LINE(0X000C),
- AUTO_CURVE(0X000D),
- CORNER_LINE(0X000E),
- CORNER_CURVE(0X000F),
- SMOOTH_LINE(0X0010),
- SMOOTH_CURVE(0X0011),
- SYMMETRIC_LINE(0X0012),
- SYMMETRIC_CURVE(0X0013),
- FREEFORM(0X0014),
- FILL_COLOR(0X0015),
- LINE_COLOR(0X0016);
-
- private final int flag;
- EscapeInfo(int flag) {
- this.flag = flag;
- }
- public int getFlag() {
- return flag;
- }
- static EscapeInfo valueOf(int flag) {
- for (EscapeInfo v : values()) {
- if (v.flag == flag) {
- return v;
- }
- }
- return null;
- }
- }
-
- protected HSLFAutoShape(EscherContainerRecord escherRecord, ShapeContainer<HSLFShape,HSLFTextParagraph> parent){
- super(escherRecord, parent);
- }
-
- public HSLFAutoShape(ShapeType type, ShapeContainer<HSLFShape,HSLFTextParagraph> parent){
- super(null, parent);
- createSpContainer(type, parent instanceof HSLFGroupShape);
- }
-
- public HSLFAutoShape(ShapeType type){
- this(type, null);
- }
-
- protected EscherContainerRecord createSpContainer(ShapeType shapeType, boolean isChild){
- EscherContainerRecord ecr = super.createSpContainer(isChild);
-
- setShapeType(shapeType);
-
- //set default properties for an autoshape
- setEscherProperty(EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 0x40000);
- setEscherProperty(EscherProperties.FILL__FILLCOLOR, 0x8000004);
- setEscherProperty(EscherProperties.FILL__FILLCOLOR, 0x8000004);
- setEscherProperty(EscherProperties.FILL__FILLBACKCOLOR, 0x8000000);
- setEscherProperty(EscherProperties.FILL__NOFILLHITTEST, 0x100010);
- setEscherProperty(EscherProperties.LINESTYLE__COLOR, 0x8000001);
- setEscherProperty(EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x80008);
- setEscherProperty(EscherProperties.SHADOWSTYLE__COLOR, 0x8000002);
-
- return ecr;
- }
-
- @Override
- protected void setDefaultTextProperties(HSLFTextParagraph _txtrun){
- setVerticalAlignment(VerticalAlignment.MIDDLE);
- setHorizontalCentered(true);
- setWordWrap(false);
- }
-
- /**
- * Gets adjust value which controls smart resizing of the auto-shape.<p>
- *
- * The adjustment values are given in shape coordinates:
- * the origin is at the top-left, positive-x is to the right, positive-y is down.
- * The region from (0,0) to (S,S) maps to the geometry box of the shape (S=21600 is a constant).
- *
- * @param idx the adjust index in the [0, 9] range
- * @return the adjustment value
- */
- public int getAdjustmentValue(int idx){
- if(idx < 0 || idx > 9) throw new IllegalArgumentException("The index of an adjustment value must be in the [0, 9] range");
-
- return getEscherProperty((short)(EscherProperties.GEOMETRY__ADJUSTVALUE + idx));
- }
-
- /**
- * Sets adjust value which controls smart resizing of the auto-shape.<p>
- *
- * The adjustment values are given in shape coordinates:
- * the origin is at the top-left, positive-x is to the right, positive-y is down.
- * The region from (0,0) to (S,S) maps to the geometry box of the shape (S=21600 is a constant).
- *
- * @param idx the adjust index in the [0, 9] range
- * @param val the adjustment value
- */
- public void setAdjustmentValue(int idx, int val){
- if(idx < 0 || idx > 9) throw new IllegalArgumentException("The index of an adjustment value must be in the [0, 9] range");
-
- setEscherProperty((short)(EscherProperties.GEOMETRY__ADJUSTVALUE + idx), val);
- }
-
- @Override
- public CustomGeometry getGeometry() {
- return getGeometry(new Path2D.Double());
- }
-
- CustomGeometry getGeometry(Path2D path2D) {
- final ObjectFactory of = new ObjectFactory();
- final CTCustomGeometry2D cusGeo = of.createCTCustomGeometry2D();
- cusGeo.setAvLst(of.createCTGeomGuideList());
- cusGeo.setGdLst(of.createCTGeomGuideList());
- cusGeo.setAhLst(of.createCTAdjustHandleList());
- cusGeo.setCxnLst(of.createCTConnectionSiteList());
-
- final AbstractEscherOptRecord opt = getEscherOptRecord();
-
- EscherArrayProperty verticesProp = getShapeProp(opt, EscherProperties.GEOMETRY__VERTICES);
- EscherArrayProperty segmentsProp = getShapeProp(opt, EscherProperties.GEOMETRY__SEGMENTINFO);
-
- // return empty path if either GEOMETRY__VERTICES or GEOMETRY__SEGMENTINFO is missing, see Bugzilla 54188
-
- //sanity check
- if(verticesProp == null) {
- LOG.log(POILogger.WARN, "Freeform is missing GEOMETRY__VERTICES ");
- return super.getGeometry();
- }
- if(segmentsProp == null) {
- LOG.log(POILogger.WARN, "Freeform is missing GEOMETRY__SEGMENTINFO ");
- return super.getGeometry();
- }
-
- final Iterator<byte[]> vertIter = verticesProp.iterator();
- final Iterator<byte[]> segIter = segmentsProp.iterator();
- final int[] xyPoints = new int[2];
- boolean isClosed = false;
-
- final CTPath2DList pathLst = of.createCTPath2DList();
- final CTPath2D pathCT = of.createCTPath2D();
- final List<Object> moveLst = pathCT.getCloseOrMoveToOrLnTo();
- pathLst.getPath().add(pathCT);
- cusGeo.setPathLst(pathLst);
-
- while (segIter.hasNext()) {
- byte[] segElem = segIter.next();
- HSLFFreeformShape.PathInfo pi = getPathInfo(segElem);
- if (pi == null) {
- continue;
- }
- switch (pi) {
- case escape: {
- handleEscapeInfo(pathCT, path2D, segElem, vertIter);
- break;
- }
- case moveTo:
- if (vertIter.hasNext()) {
- final CTPath2DMoveTo m = of.createCTPath2DMoveTo();
- m.setPt(fillPoint(vertIter.next(), xyPoints));
- moveLst.add(m);
- path2D.moveTo(xyPoints[0], xyPoints[1]);
- }
- break;
- case lineTo:
- if (vertIter.hasNext()) {
- final CTPath2DLineTo m = of.createCTPath2DLineTo();
- m.setPt(fillPoint(vertIter.next(), xyPoints));
- moveLst.add(m);
- path2D.lineTo(xyPoints[0], xyPoints[1]);
- }
- break;
- case curveTo: {
- final CTPath2DCubicBezierTo m = of.createCTPath2DCubicBezierTo();
- List<CTAdjPoint2D> mLst = m.getPt();
-
- int[] pts = new int[6];
-
- for (int i=0; vertIter.hasNext() && i<3; i++) {
- mLst.add(fillPoint(vertIter.next(), xyPoints));
- pts[i*2] = xyPoints[0];
- pts[i*2+1] = xyPoints[1];
- if (i == 2) {
- moveLst.add(m);
- path2D.curveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]);
- }
- }
- break;
- }
- case close:
- moveLst.add(of.createCTPath2DClose());
- path2D.closePath();
- isClosed = true;
- break;
- default:
- break;
- }
- }
-
- EscherSimpleProperty shapePath = getShapeProp(opt, EscherProperties.GEOMETRY__SHAPEPATH);
- HSLFFreeformShape.ShapePath sp = HSLFFreeformShape.ShapePath.valueOf(shapePath == null ? 1 : shapePath.getPropertyValue());
- if ((sp == HSLFFreeformShape.ShapePath.LINES_CLOSED || sp == HSLFFreeformShape.ShapePath.CURVES_CLOSED) && !isClosed) {
- moveLst.add(of.createCTPath2DClose());
- path2D.closePath();
- }
-
- EscherSimpleProperty geoLeft = getShapeProp(opt, EscherProperties.GEOMETRY__LEFT);
- EscherSimpleProperty geoRight = getShapeProp(opt, EscherProperties.GEOMETRY__RIGHT);
- EscherSimpleProperty geoTop = getShapeProp(opt, EscherProperties.GEOMETRY__TOP);
- EscherSimpleProperty geoBottom = getShapeProp(opt, EscherProperties.GEOMETRY__BOTTOM);
-
- final Rectangle2D bounds;
- if (geoLeft != null && geoRight != null && geoTop != null && geoBottom != null) {
- bounds = new Rectangle2D.Double();
- bounds.setFrameFromDiagonal(
- new Point2D.Double(geoLeft.getPropertyValue(), geoTop.getPropertyValue()),
- new Point2D.Double(geoRight.getPropertyValue(), geoBottom.getPropertyValue())
- );
- } else {
- bounds = path2D.getBounds2D();
- }
-
- pathCT.setW((int)Math.rint(bounds.getWidth()));
- pathCT.setH((int)Math.rint(bounds.getHeight()));
-
- return new CustomGeometry(cusGeo);
- }
-
- private void handleEscapeInfo(CTPath2D pathCT, Path2D path2D, byte[] segElem, Iterator<byte[]> vertIter) {
- final ObjectFactory of = new ObjectFactory();
- HSLFFreeformShape.EscapeInfo ei = getEscapeInfo(segElem);
- switch (ei) {
- case EXTENSION:
- break;
- case ANGLE_ELLIPSE_TO:
- break;
- case ANGLE_ELLIPSE:
- break;
- case ARC_TO: {
- // The first two POINT values specify the bounding rectangle of the ellipse.
- // The second two POINT values specify the radial vectors for the ellipse.
- // The radial vectors are cast from the center of the bounding rectangle.
- // The path starts at the POINT where the first radial vector intersects the
- // bounding rectangle and goes to the POINT where the second radial vector
- // intersects the bounding rectangle. The drawing direction is always counterclockwise.
- // If the path has already been started, a line is drawn from the last POINT to
- // the starting POINT of the arc; otherwise, a new path is started.
- // The number of arc segments drawn equals the number of segments divided by four.
-
- int[] r1 = new int[2], r2 = new int[2], start = new int[2], end = new int[2];
- fillPoint(vertIter.next(), r1);
- fillPoint(vertIter.next(), r2);
- fillPoint(vertIter.next(), start);
- fillPoint(vertIter.next(), end);
-
- Arc2D arc2D = new Arc2D.Double();
- Rectangle2D.Double bounds = new Rectangle2D.Double();
- bounds.setFrameFromDiagonal(xy2p(r1), xy2p(r2));
- arc2D.setFrame(bounds);
- arc2D.setAngles(xy2p(start), xy2p(end));
- path2D.append(arc2D, true);
-
-
- CTPath2DArcTo arcTo = of.createCTPath2DArcTo();
- arcTo.setHR(d2s(bounds.getHeight()/2.0));
- arcTo.setWR(d2s(bounds.getWidth()/2.0));
-
- arcTo.setStAng(d2s(-arc2D.getAngleStart()*60000.));
- arcTo.setSwAng(d2s(-arc2D.getAngleExtent()*60000.));
-
- pathCT.getCloseOrMoveToOrLnTo().add(arcTo);
-
- break;
- }
- case ARC:
- break;
- case CLOCKWISE_ARC_TO:
- break;
- case CLOCKWISE_ARC:
- break;
- case ELLIPTICAL_QUADRANT_X:
- break;
- case ELLIPTICAL_QUADRANT_Y:
- break;
- case QUADRATIC_BEZIER:
- break;
- case NO_FILL:
- break;
- case NO_LINE:
- break;
- case AUTO_LINE:
- break;
- case AUTO_CURVE:
- break;
- case CORNER_LINE:
- break;
- case CORNER_CURVE:
- break;
- case SMOOTH_LINE:
- break;
- case SMOOTH_CURVE:
- break;
- case SYMMETRIC_LINE:
- break;
- case SYMMETRIC_CURVE:
- break;
- case FREEFORM:
- break;
- case FILL_COLOR:
- break;
- case LINE_COLOR:
- break;
- default:
- break;
- }
- }
-
- private static String d2s(double d) {
- return Integer.toString((int)Math.rint(d));
- }
-
- private static Point2D xy2p(int[] xyPoints) {
- return new Point2D.Double(xyPoints[0],xyPoints[1]);
- }
-
- private static HSLFFreeformShape.PathInfo getPathInfo(byte[] elem) {
- int elemUS = LittleEndian.getUShort(elem, 0);
- int pathInfo = PATH_INFO.getValue(elemUS);
- return HSLFFreeformShape.PathInfo.valueOf(pathInfo);
- }
-
- private static HSLFFreeformShape.EscapeInfo getEscapeInfo(byte[] elem) {
- int elemUS = LittleEndian.getUShort(elem, 0);
- int escInfo = ESCAPE_INFO.getValue(elemUS);
- return HSLFFreeformShape.EscapeInfo.valueOf(escInfo);
- }
-
-
- private static <T extends EscherProperty> T getShapeProp(AbstractEscherOptRecord opt, int propId) {
- T prop = getEscherProperty(opt, (short)(propId + 0x4000));
- if (prop == null) {
- prop = getEscherProperty(opt, propId);
- }
- return prop;
- }
-
- private CTAdjPoint2D fillPoint(byte[] xyMaster, int[] xyPoints) {
- if (xyMaster == null || xyPoints == null) {
- LOG.log(POILogger.WARN, "Master bytes or points not set - ignore point");
- return null;
- }
- if ((xyMaster.length != 4 && xyMaster.length != 8) || xyPoints.length != 2) {
- LOG.log(POILogger.WARN, "Invalid number of master bytes for a single point - ignore point");
- return null;
- }
-
- int x, y;
- if (xyMaster.length == 4) {
- x = LittleEndian.getShort(xyMaster, 0);
- y = LittleEndian.getShort(xyMaster, 2);
- } else {
- x = LittleEndian.getInt(xyMaster, 0);
- y = LittleEndian.getInt(xyMaster, 4);
- }
-
- xyPoints[0] = x;
- xyPoints[1] = y;
-
- return toPoint(xyPoints);
- }
-
- private static CTAdjPoint2D toPoint(int[] xyPoints) {
- CTAdjPoint2D pt = new CTAdjPoint2D();
- pt.setX(Integer.toString(xyPoints[0]));
- pt.setY(Integer.toString(xyPoints[1]));
- return pt;
- }
- }
|