diff --git a/src/java/org/apache/poi/ss/usermodel/ClientAnchor.java b/src/java/org/apache/poi/ss/usermodel/ClientAnchor.java
index 702970c69..d11edc56a 100644
--- a/src/java/org/apache/poi/ss/usermodel/ClientAnchor.java
+++ b/src/java/org/apache/poi/ss/usermodel/ClientAnchor.java
@@ -20,10 +20,17 @@ import org.apache.poi.util.Internal;
import org.apache.poi.util.Removal;
/**
- * A client anchor is attached to an excel worksheet. It anchors against a
- * top-left and bottom-right cell.
- *
- * @author Yegor Kozlov
+ * A client anchor is attached to an excel worksheet. It anchors against
+ * absolute coordinates, a top-left cell and fixed height and width, or
+ * a top-left and bottom-right cell, depending on the {@link AnchorType}:
+ *
+ * - {@link AnchorType#DONT_MOVE_AND_RESIZE} == absolute top-left coordinates and width/height, no cell references
+ *
- {@link AnchorType#MOVE_DONT_RESIZE} == fixed top-left cell reference, absolute width/height
+ *
- {@link AnchorType#MOVE_AND_RESIZE} == fixed top-left and bottom-right cell references, dynamic width/height
+ *
+ * Note this class only reports the current values for possibly calculated positions and sizes.
+ * If the sheet row/column sizes or positions shift, this needs updating via external calculations.
+ *
*/
public interface ClientAnchor {
@@ -91,8 +98,9 @@ public interface ClientAnchor {
*
* Specifies that the current drawing shall not move with its
* row and column, but should be resized. This option is not normally
- * used, but is included for completeness.
+ * used, but is included for completeness.
*
+ * Note: Excel has no setting for this combination, nor does the ECMA standard.
*/
DONT_MOVE_DO_RESIZE(1),
@@ -145,9 +153,10 @@ public interface ClientAnchor {
}
/**
- * Returns the column (0 based) of the first cell.
+ * Returns the column (0 based) of the first cell, or -1 if there is no top-left anchor cell.
+ * This is the case for absolute positioning (AnchorType{@link #DONT_MOVE_AND_RESIZE}).
*
- * @return 0-based column of the first cell.
+ * @return 0-based column of the first cell or -1 if none.
*/
public short getCol1();
@@ -159,9 +168,11 @@ public interface ClientAnchor {
public void setCol1(int col1);
/**
- * Returns the column (0 based) of the second cell.
+ * Returns the column (0 based) of the second cell, or -1 if there is no bottom-right anchor cell.
+ * This is the case for absolute positioning ({@link AnchorType#DONT_MOVE_AND_RESIZE})
+ * and absolute sizing ({@link AnchorType#MOVE_DONT_RESIZE}.
*
- * @return 0-based column of the second cell.
+ * @return 0-based column of the second cell or -1 if none.
*/
public short getCol2();
@@ -173,9 +184,10 @@ public interface ClientAnchor {
public void setCol2(int col2);
/**
- * Returns the row (0 based) of the first cell.
+ * Returns the row (0 based) of the first cell, or -1 if there is no bottom-right anchor cell.
+ * This is the case for absolute positioning ({@link AnchorType#DONT_MOVE_AND_RESIZE}).
*
- * @return 0-based row of the first cell.
+ * @return 0-based row of the first cell or -1 if none.
*/
public int getRow1();
@@ -187,9 +199,11 @@ public interface ClientAnchor {
public void setRow1(int row1);
/**
- * Returns the row (0 based) of the second cell.
+ * Returns the row (0 based) of the second cell, or -1 if there is no bottom-right anchor cell.
+ * This is the case for absolute positioning ({@link AnchorType#DONT_MOVE_AND_RESIZE})
+ * and absolute sizing ({@link AnchorType#MOVE_DONT_RESIZE}.
*
- * @return 0-based row of the second cell.
+ * @return 0-based row of the second cell or -1 if none.
*/
public int getRow2();
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFClientAnchor.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFClientAnchor.java
index 0f2c1a186..0fa3f96ff 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFClientAnchor.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFClientAnchor.java
@@ -18,50 +18,73 @@
package org.apache.poi.xssf.usermodel;
import org.apache.poi.ss.usermodel.ClientAnchor;
+import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.util.Internal;
import org.apache.poi.util.Removal;
+import org.apache.poi.xssf.util.EMUUtils;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
/**
- * A client anchor is attached to an excel worksheet. It anchors against
- * top-left and bottom-right cells.
+ * A client anchor is attached to an excel worksheet. It anchors against:
+ *
+ * - A fixed position and fixed size
+ *
- A position relative to a cell (top-left) and a fixed size
+ *
- A position relative to a cell (top-left) and sized relative to another cell (bottom right)
+ *
*
- * @author Yegor Kozlov
+ * which method is used is determined by the {@link AnchorType}.
*/
-public final class XSSFClientAnchor extends XSSFAnchor implements ClientAnchor {
- private AnchorType DEFAULT_ANCHOR_TYPE = AnchorType.MOVE_AND_RESIZE;
+public class XSSFClientAnchor extends XSSFAnchor implements ClientAnchor {
+
+ /**
+ * placeholder for zeros when needed for dynamic position calculations
+ */
+ private static final CTMarker EMPTY_MARKER = CTMarker.Factory.newInstance();
+
private AnchorType anchorType;
/**
- * Starting anchor point
+ * Starting anchor point (top-left cell + relative offset)
+ * if left null recalculate as needed from point
*/
private CTMarker cell1;
/**
- * Ending anchor point
+ * Ending anchor point (bottom-right cell + relative offset)
+ * if left null, re-calculate as needed from size and cell1
*/
private CTMarker cell2;
+ /**
+ * if present, fixed size of the object to use instead of cell2, which is inferred instead
+ */
+ private CTPositiveSize2D size;
+
+ /**
+ * if present, fixed top-left position to use instead of cell1, which is inferred instead
+ */
+ private CTPoint2D position;
+
+ /**
+ * sheet to base dynamic calculations on, if needed. Required if size and/or position or set.
+ * Not needed if cell1/2 are set explicitly (dynamic sizing and position relative to cells).
+ */
+ private XSSFSheet sheet;
+
/**
* Creates a new client anchor and defaults all the anchor positions to 0.
+ * Sets the type to {@link AnchorType#MOVE_AND_RESIZE} relative to cell range A1:A1.
*/
public XSSFClientAnchor() {
- anchorType = DEFAULT_ANCHOR_TYPE;
- cell1 = CTMarker.Factory.newInstance();
- cell1.setCol(0);
- cell1.setColOff(0);
- cell1.setRow(0);
- cell1.setRowOff(0);
- cell2 = CTMarker.Factory.newInstance();
- cell2.setCol(0);
- cell2.setColOff(0);
- cell2.setRow(0);
- cell2.setRowOff(0);
+ this(0,0,0,0,0,0,0,0);
}
/**
* Creates a new client anchor and sets the top-left and bottom-right
- * coordinates of the anchor.
+ * coordinates of the anchor by cell references and offsets.
+ * Sets the type to {@link AnchorType#MOVE_AND_RESIZE}.
*
* @param dx1 the x coordinate within the first cell.
* @param dy1 the y coordinate within the first cell.
@@ -73,11 +96,13 @@ public final class XSSFClientAnchor extends XSSFAnchor implements ClientAnchor {
* @param row2 the row (0 based) of the second cell.
*/
public XSSFClientAnchor(int dx1, int dy1, int dx2, int dy2, int col1, int row1, int col2, int row2) {
- this();
+ anchorType = AnchorType.MOVE_AND_RESIZE;
+ cell1 = CTMarker.Factory.newInstance();
cell1.setCol(col1);
cell1.setColOff(dx1);
cell1.setRow(row1);
cell1.setRowOff(dy1);
+ cell2 = CTMarker.Factory.newInstance();
cell2.setCol(col2);
cell2.setColOff(dx2);
cell2.setRow(row2);
@@ -85,77 +110,203 @@ public final class XSSFClientAnchor extends XSSFAnchor implements ClientAnchor {
}
/**
- * Create XSSFClientAnchor from existing xml beans
- *
+ * Create XSSFClientAnchor from existing xml beans, sized and positioned relative to a pair of cells.
+ * Sets the type to {@link AnchorType#MOVE_AND_RESIZE}.
* @param cell1 starting anchor point
* @param cell2 ending anchor point
*/
protected XSSFClientAnchor(CTMarker cell1, CTMarker cell2) {
- anchorType = DEFAULT_ANCHOR_TYPE;
+ anchorType = AnchorType.MOVE_AND_RESIZE;
this.cell1 = cell1;
this.cell2 = cell2;
}
+ /**
+ * Create XSSFClientAnchor from existing xml beans, sized and positioned relative to a pair of cells.
+ * Sets the type to {@link AnchorType#MOVE_DONT_RESIZE}.
+ *
+ * @param sheet needed to calculate ending point based on column/row sizes
+ * @param cell1 starting anchor point
+ * @param size object size, to calculate ending anchor point
+ */
+ protected XSSFClientAnchor(XSSFSheet sheet, CTMarker cell1, CTPositiveSize2D size) {
+ anchorType = AnchorType.MOVE_DONT_RESIZE;
+ this.sheet = sheet;
+ this.size = size;
+ this.cell1 = cell1;
+// this.cell2 = calcCell(sheet, cell1, size.getCx(), size.getCy());
+ }
+
+ /**
+ * Create XSSFClientAnchor from existing xml beans, sized and positioned relative to a pair of cells.
+ * Sets the type to {@link AnchorType#DONT_MOVE_AND_RESIZE}.
+ *
+ * @param sheet needed to calculate starting and ending points based on column/row sizes
+ * @param position starting absolute position
+ * @param size object size, to calculate ending position
+ */
+ protected XSSFClientAnchor(XSSFSheet sheet, CTPoint2D position, CTPositiveSize2D size) {
+ anchorType = AnchorType.DONT_MOVE_AND_RESIZE;
+ this.sheet = sheet;
+ this.position = position;
+ this.size = size;
+ // zeros for row/col/offsets
+// this.cell1 = calcCell(sheet, EMPTY_MARKER, position.getCx(), position.getCy());
+// this.cell2 = calcCell(sheet, cell1, size.getCx(), size.getCy());
+ }
+
+ /**
+ *
+ * @param sheet
+ * @param cell starting point and offsets (may be zeros)
+ * @param size dimensions to calculate relative to starting point
+ */
+ private CTMarker calcCell(CTMarker cell, long w, long h) {
+ CTMarker c2 = CTMarker.Factory.newInstance();
+
+ int r = cell.getRow();
+ int c = cell.getCol();
+
+ int cw = EMUUtils.EMUsFromColumnWidth(sheet.getColumnWidth(c));
+
+ // start with width - offset, then keep adding column widths until the next one puts us over w
+ long wPos = cw - cell.getColOff();
+
+ while (wPos < w) {
+ c++;
+ cw = EMUUtils.EMUsFromColumnWidth(sheet.getColumnWidth(c));
+ wPos += cw;
+ }
+ // now wPos >= w, so end column = c, now figure offset
+ c2.setCol(c);
+ c2.setColOff(cw - (wPos - w));
+
+ int rh = EMUUtils.EMUsFromPoints(getRowHeight(sheet, r));
+ // start with height - offset, then keep adding row heights until the next one puts us over h
+ long hPos = rh - cell.getRowOff();
+
+ while (hPos < h) {
+ r++;
+ rh = EMUUtils.EMUsFromPoints(getRowHeight(sheet, r));
+ hPos += rh;
+ }
+ // now hPos >= h, so end row = r, now figure offset
+ c2.setRow(r);
+ c2.setRowOff(rh - (hPos - h));
+
+ return c2;
+ }
+
+ /**
+ * @param sheet
+ * @param row
+ * @return height in twips (1/20th of point) for row or default
+ */
+ private static float getRowHeight(XSSFSheet sheet, int row) {
+ XSSFRow r = sheet.getRow(row);
+ return r == null ? sheet.getDefaultRowHeightInPoints() : r.getHeightInPoints();
+ }
+
+ private CTMarker getCell1() {
+ return cell1 != null ? cell1 : calcCell(EMPTY_MARKER, position.getX(), position.getY());
+ }
+
+ private CTMarker getCell2() {
+ return cell2 != null ? cell2 : calcCell(getCell1(), size.getCx(), size.getCy());
+ }
+
public short getCol1() {
- return (short)cell1.getCol();
+ return (short)getCell1().getCol();
}
+ /**
+ * @throws NullPointerException if cell1 is null (fixed position)
+ * @see org.apache.poi.ss.usermodel.ClientAnchor#setCol1(int)
+ */
public void setCol1(int col1) {
cell1.setCol(col1);
}
public short getCol2() {
- return (short)cell2.getCol();
+ return (short) getCell2().getCol();
}
+ /**
+ * @throws NullPointerException if cell2 is null (fixed size)
+ * @see org.apache.poi.ss.usermodel.ClientAnchor#setCol2(int)
+ */
public void setCol2(int col2) {
cell2.setCol(col2);
}
public int getRow1() {
- return cell1.getRow();
+ return getCell1().getRow();
}
+ /**
+ * @throws NullPointerException if cell1 is null (fixed position)
+ * @see org.apache.poi.ss.usermodel.ClientAnchor#setRow1(int)
+ */
public void setRow1(int row1) {
cell1.setRow(row1);
}
public int getRow2() {
- return cell2.getRow();
+ return getCell2().getRow();
}
+ /**
+ * @throws NullPointerException if cell2 is null (fixed size)
+ * @see org.apache.poi.ss.usermodel.ClientAnchor#setRow2(int)
+ */
public void setRow2(int row2) {
cell2.setRow(row2);
}
public int getDx1() {
- return (int)cell1.getColOff();
+ return (int) getCell1().getColOff();
}
+ /**
+ * @throws NullPointerException if cell1 is null (fixed position)
+ * @see org.apache.poi.ss.usermodel.ChildAnchor#setDx1(int)
+ */
public void setDx1(int dx1) {
cell1.setColOff(dx1);
}
public int getDy1() {
- return (int)cell1.getRowOff();
+ return (int) getCell1().getRowOff();
}
+ /**
+ * @throws NullPointerException if cell1 is null (fixed position)
+ * @see org.apache.poi.ss.usermodel.ChildAnchor#setDy1(int)
+ */
public void setDy1(int dy1) {
cell1.setRowOff(dy1);
}
public int getDy2() {
- return (int)cell2.getRowOff();
+ return (int) getCell2().getRowOff();
}
+ /**
+ * @throws NullPointerException if cell2 is null (fixed size)
+ * @see org.apache.poi.ss.usermodel.ChildAnchor#setDy2(int)
+ */
public void setDy2(int dy2) {
cell2.setRowOff(dy2);
}
public int getDx2() {
- return (int)cell2.getColOff();
+ return (int) getCell2().getColOff();
}
+ /**
+ * @throws NullPointerException if cell2 is null (fixed size)
+ * @see org.apache.poi.ss.usermodel.ChildAnchor#setDx2(int)
+ */
public void setDx2(int dx2) {
cell2.setColOff(dx2);
}
@@ -184,7 +335,7 @@ public final class XSSFClientAnchor extends XSSFAnchor implements ClientAnchor {
@Override
public String toString(){
- return "from : " + cell1 + "; to: " + cell2;
+ return "from : " + getCell1() + "; to: " + getCell2();
}
/**
@@ -194,7 +345,7 @@ public final class XSSFClientAnchor extends XSSFAnchor implements ClientAnchor {
*/
@Internal
public CTMarker getFrom(){
- return cell1;
+ return getCell1();
}
protected void setFrom(CTMarker from){
@@ -208,13 +359,47 @@ public final class XSSFClientAnchor extends XSSFAnchor implements ClientAnchor {
*/
@Internal
public CTMarker getTo(){
- return cell2;
+ return getCell2();
}
protected void setTo(CTMarker to){
cell2 = to;
}
+ /**
+ * @return absolute top-left position, or null if position is determined from the "from" cell
+ * @since POI 3.17 beta 1
+ */
+ public CTPoint2D getPosition() {
+ return position;
+ }
+
+ /**
+ * Sets the top-left absolute position of the object. To use this, "from" must be set to null.
+ * @param position
+ * @since POI 3.17 beta 1
+ */
+ public void setPosition(CTPoint2D position) {
+ this.position = position;
+ }
+
+ /**
+ *
+ * @return size or null, if size is determined from the to and from cells
+ * @since POI 3.17 beta 1
+ */
+ public CTPositiveSize2D getSize() {
+ return size;
+ }
+
+ /**
+ * Sets the size of the object. To use this, "to" must be set to null.
+ * @param size
+ * @since POI 3.17 beta 1
+ */
+ public void setSize(CTPositiveSize2D size) {
+ this.size = size;
+ }
/**
* Sets the anchor type
@@ -250,7 +435,9 @@ public final class XSSFClientAnchor extends XSSFAnchor implements ClientAnchor {
}
public boolean isSet(){
- return !(cell1.getCol() == 0 && cell2.getCol() == 0 &&
- cell1.getRow() == 0 && cell2.getRow() == 0);
+ CTMarker c1 = getCell1();
+ CTMarker c2 = getCell2();
+ return !(c1.getCol() == 0 && c2.getCol() == 0 &&
+ c1.getRow() == 0 && c2.getRow() == 0);
}
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java
index 42518fa9f..7078f3c09 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java
@@ -58,6 +58,7 @@ import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D;
+import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTAbsoluteAnchor;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTConnector;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTDrawing;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTGraphicalObjectFrame;
@@ -676,7 +677,10 @@ public final class XSSFDrawing extends POIXMLDocumentPart implements Drawing
+ * These are defined briefly in Wikipedia
+ * as a "rational" way to use an integer value to represent something that could be in
+ * inches, centimeters, points, or pixels.
+ * So now we get to convert between all those.
+ */
+public class EMUUtils {
+ public static final int EMUS_PER_INCH = 914400;
+ public static final int EMUS_PER_POINT = 12700;
+ public static final int EMUS_PER_CENTIMETER = 360000;
+
+ // TODO: these could move here or something to standardize definitions
+ public static final int EMU_PER_PIXEL = XSSFShape.EMU_PER_PIXEL;
+ public static final int EMU_PER_POINT = XSSFShape.EMU_PER_POINT;
+ public static final int PIXEL_DPI = XSSFShape.PIXEL_DPI;
+ public static final int POINT_DPI = XSSFShape.POINT_DPI;
+ public static final int EMU_PER_CHARACTER = (int) (EMU_PER_PIXEL * XSSFWorkbook.DEFAULT_CHARACTER_WIDTH);
+
+ /**
+ * @param columnWidth as (fractional # of characters) * 256
+ * @return EMUs
+ */
+ public static final int EMUsFromColumnWidth(int columnWidth) {
+ return (int) (columnWidth /256d * EMUUtils.EMU_PER_CHARACTER);
+ }
+
+ /**
+ * @param twips (1/20th of a point) typically a row height
+ * @return EMUs
+ */
+ public static final int EMUsFromTwips(short twips) {
+ return (int) (twips / 20d * EMU_PER_POINT);
+ }
+
+ /**
+ * @param points (fractional)
+ * @return EMUs
+ */
+ public static final int EMUsFromPoints(float points) {
+ return (int) (points * EMU_PER_POINT);
+ }
+}