469 lines
16 KiB
Java
469 lines
16 KiB
Java
/* ====================================================================
|
|
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.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.SortedSet;
|
|
import java.util.TreeSet;
|
|
|
|
import org.apache.poi.ddf.AbstractEscherOptRecord;
|
|
import org.apache.poi.ddf.EscherArrayProperty;
|
|
import org.apache.poi.ddf.EscherContainerRecord;
|
|
import org.apache.poi.ddf.EscherOptRecord;
|
|
import org.apache.poi.ddf.EscherProperties;
|
|
import org.apache.poi.ddf.EscherSimpleProperty;
|
|
import org.apache.poi.hslf.record.RecordTypes;
|
|
import org.apache.poi.sl.usermodel.ShapeContainer;
|
|
import org.apache.poi.sl.usermodel.TableShape;
|
|
import org.apache.poi.util.LittleEndian;
|
|
import org.apache.poi.util.Units;
|
|
|
|
/**
|
|
* Represents a table in a PowerPoint presentation
|
|
*
|
|
* @author Yegor Kozlov
|
|
*/
|
|
public final class HSLFTable extends HSLFGroupShape
|
|
implements HSLFShapeContainer, TableShape<HSLFShape,HSLFTextParagraph> {
|
|
|
|
protected static final int BORDERS_ALL = 5;
|
|
protected static final int BORDERS_OUTSIDE = 6;
|
|
protected static final int BORDERS_INSIDE = 7;
|
|
protected static final int BORDERS_NONE = 8;
|
|
|
|
|
|
protected HSLFTableCell[][] cells;
|
|
private int columnCount = -1;
|
|
|
|
/**
|
|
* Create a new Table of the given number of rows and columns
|
|
*
|
|
* @param numRows the number of rows
|
|
* @param numCols the number of columns
|
|
*/
|
|
protected HSLFTable(int numRows, int numCols) {
|
|
this(numRows, numCols, null);
|
|
}
|
|
|
|
/**
|
|
* Create a new Table of the given number of rows and columns
|
|
*
|
|
* @param numRows the number of rows
|
|
* @param numCols the number of columns
|
|
* @param parent the parent shape, or null if table is added to sheet
|
|
*/
|
|
protected HSLFTable(int numRows, int numCols, ShapeContainer<HSLFShape,HSLFTextParagraph> parent) {
|
|
super(parent);
|
|
|
|
if(numRows < 1) {
|
|
throw new IllegalArgumentException("The number of rows must be greater than 1");
|
|
}
|
|
if(numCols < 1) {
|
|
throw new IllegalArgumentException("The number of columns must be greater than 1");
|
|
}
|
|
|
|
double x=0, y=0, tblWidth=0, tblHeight=0;
|
|
cells = new HSLFTableCell[numRows][numCols];
|
|
for (int i = 0; i < cells.length; i++) {
|
|
x = 0;
|
|
for (int j = 0; j < cells[i].length; j++) {
|
|
cells[i][j] = new HSLFTableCell(this);
|
|
Rectangle2D anchor = new Rectangle2D.Double(x, y, HSLFTableCell.DEFAULT_WIDTH, HSLFTableCell.DEFAULT_HEIGHT);
|
|
cells[i][j].setAnchor(anchor);
|
|
x += HSLFTableCell.DEFAULT_WIDTH;
|
|
}
|
|
y += HSLFTableCell.DEFAULT_HEIGHT;
|
|
}
|
|
tblWidth = x;
|
|
tblHeight = y;
|
|
setExteriorAnchor(new Rectangle2D.Double(0, 0, tblWidth, tblHeight));
|
|
|
|
EscherContainerRecord spCont = (EscherContainerRecord) getSpContainer().getChild(0);
|
|
AbstractEscherOptRecord opt = new EscherOptRecord();
|
|
opt.setRecordId(RecordTypes.EscherUserDefined.typeID);
|
|
opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GROUPSHAPE__TABLEPROPERTIES, 1));
|
|
EscherArrayProperty p = new EscherArrayProperty((short)(0x4000 | EscherProperties.GROUPSHAPE__TABLEROWPROPERTIES), false, null);
|
|
p.setSizeOfElements(0x0004);
|
|
p.setNumberOfElementsInArray(numRows);
|
|
p.setNumberOfElementsInMemory(numRows);
|
|
opt.addEscherProperty(p);
|
|
spCont.addChildBefore(opt, RecordTypes.EscherClientAnchor.typeID);
|
|
}
|
|
|
|
/**
|
|
* Create a Table 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 HSLFTable(EscherContainerRecord escherRecord, ShapeContainer<HSLFShape,HSLFTextParagraph> parent) {
|
|
super(escherRecord, parent);
|
|
}
|
|
|
|
@Override
|
|
public HSLFTableCell getCell(int row, int col) {
|
|
if (row < 0 || cells.length <= row) {
|
|
return null;
|
|
}
|
|
HSLFTableCell[] r = cells[row];
|
|
if (r == null || col < 0 || r.length <= col) {
|
|
// empty row
|
|
return null;
|
|
}
|
|
// cell can be potentially empty ...
|
|
return r[col];
|
|
}
|
|
|
|
@Override
|
|
public int getNumberOfColumns() {
|
|
if (columnCount == -1) {
|
|
// check all rows in case of merged rows
|
|
for (HSLFTableCell[] hc : cells) {
|
|
if (hc != null) {
|
|
columnCount = Math.max(columnCount, hc.length);
|
|
}
|
|
}
|
|
}
|
|
return columnCount;
|
|
}
|
|
|
|
@Override
|
|
public int getNumberOfRows() {
|
|
return cells.length;
|
|
}
|
|
|
|
@Override
|
|
protected void afterInsert(HSLFSheet sh){
|
|
super.afterInsert(sh);
|
|
|
|
Set<HSLFLine> lineSet = new HashSet<HSLFLine>();
|
|
for (HSLFTableCell row[] : cells) {
|
|
for (HSLFTableCell c : row) {
|
|
addShape(c);
|
|
for (HSLFLine bt : new HSLFLine[]{ c.borderTop, c.borderRight, c.borderBottom, c.borderLeft }) {
|
|
if (bt != null) {
|
|
lineSet.add(bt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (HSLFLine l : lineSet) {
|
|
addShape(l);
|
|
}
|
|
|
|
updateRowHeightsProperty();
|
|
}
|
|
|
|
private void cellListToArray() {
|
|
List<HSLFTableCell> htc = new ArrayList<HSLFTableCell>();
|
|
for (HSLFShape h : getShapes()) {
|
|
if (h instanceof HSLFTableCell) {
|
|
htc.add((HSLFTableCell)h);
|
|
}
|
|
}
|
|
|
|
if (htc.isEmpty()) {
|
|
throw new IllegalStateException("HSLFTable without HSLFTableCells");
|
|
}
|
|
|
|
SortedSet<Double> colSet = new TreeSet<Double>();
|
|
SortedSet<Double> rowSet = new TreeSet<Double>();
|
|
|
|
// #1 pass - determine cols and rows
|
|
for (HSLFTableCell sh : htc) {
|
|
Rectangle2D anchor = sh.getAnchor();
|
|
colSet.add(anchor.getX());
|
|
rowSet.add(anchor.getY());
|
|
}
|
|
cells = new HSLFTableCell[rowSet.size()][colSet.size()];
|
|
|
|
List<Double> colLst = new ArrayList<Double>(colSet);
|
|
List<Double> rowLst = new ArrayList<Double>(rowSet);
|
|
|
|
// #2 pass - assign shape to table cells
|
|
for (HSLFTableCell sh : htc) {
|
|
Rectangle2D anchor = sh.getAnchor();
|
|
int row = rowLst.indexOf(anchor.getY());
|
|
int col = colLst.indexOf(anchor.getX());
|
|
assert(row != -1 && col != -1);
|
|
cells[row][col] = sh;
|
|
|
|
// determine gridSpan / rowSpan
|
|
int gridSpan = calcSpan(colLst, anchor.getWidth(), col);
|
|
int rowSpan = calcSpan(rowLst, anchor.getHeight(), row);
|
|
|
|
sh.setGridSpan(gridSpan);
|
|
sh.setRowSpan(rowSpan);
|
|
}
|
|
}
|
|
|
|
private int calcSpan(List<Double> spaces, double totalSpace, int idx) {
|
|
if (idx == spaces.size()-1) {
|
|
return 1;
|
|
}
|
|
int span = 0;
|
|
double remainingSpace = totalSpace;
|
|
while (idx+1 < spaces.size() && remainingSpace > 0) {
|
|
remainingSpace -= spaces.get(idx+1)-spaces.get(idx);
|
|
span++;
|
|
idx++;
|
|
}
|
|
return span;
|
|
}
|
|
|
|
static class LineRect {
|
|
final HSLFLine l;
|
|
final double lx1, lx2, ly1, ly2;
|
|
LineRect(HSLFLine l) {
|
|
this.l = l;
|
|
Rectangle2D r = l.getAnchor();
|
|
lx1 = r.getMinX();
|
|
lx2 = r.getMaxX();
|
|
ly1 = r.getMinY();
|
|
ly2 = r.getMaxY();
|
|
}
|
|
int leftFit(double x1, double x2, double y1, double y2) {
|
|
return (int)(Math.abs(x1-lx1)+Math.abs(y1-ly1)+Math.abs(x1-lx2)+Math.abs(y2-ly2));
|
|
}
|
|
int topFit(double x1, double x2, double y1, double y2) {
|
|
return (int)(Math.abs(x1-lx1)+Math.abs(y1-ly1)+Math.abs(x2-lx2)+Math.abs(y1-ly2));
|
|
}
|
|
int rightFit(double x1, double x2, double y1, double y2) {
|
|
return (int)(Math.abs(x2-lx1)+Math.abs(y1-ly1)+Math.abs(x2-lx2)+Math.abs(y2-ly2));
|
|
}
|
|
int bottomFit(double x1, double x2, double y1, double y2) {
|
|
return (int)(Math.abs(x1-lx1)+Math.abs(y2-ly1)+Math.abs(x2-lx2)+Math.abs(y2-ly2));
|
|
}
|
|
}
|
|
|
|
private void fitLinesToCells() {
|
|
List<LineRect> lines = new ArrayList<LineRect>();
|
|
for (HSLFShape h : getShapes()) {
|
|
if (h instanceof HSLFLine) {
|
|
lines.add(new LineRect((HSLFLine)h));
|
|
}
|
|
}
|
|
|
|
final int threshold = 5;
|
|
|
|
// TODO: this only works for non-rotated tables
|
|
for (HSLFTableCell[] tca : cells) {
|
|
for (HSLFTableCell tc : tca) {
|
|
if (tc == null) {
|
|
continue;
|
|
}
|
|
final Rectangle2D cellAnchor = tc.getAnchor();
|
|
|
|
/**
|
|
* x1/y1 --------+
|
|
* | |
|
|
* +---------x2/y2
|
|
*/
|
|
final double x1 = cellAnchor.getMinX();
|
|
final double x2 = cellAnchor.getMaxX();
|
|
final double y1 = cellAnchor.getMinY();
|
|
final double y2 = cellAnchor.getMaxY();
|
|
|
|
LineRect lline = null, tline = null, rline = null, bline = null;
|
|
int lfit = Integer.MAX_VALUE, tfit = Integer.MAX_VALUE, rfit = Integer.MAX_VALUE, bfit = Integer.MAX_VALUE;
|
|
|
|
for (LineRect lr : lines) {
|
|
// calculate border fit
|
|
int lfitx = lr.leftFit(x1, x2, y1, y2);
|
|
if (lfitx < lfit) {
|
|
lfit = lfitx;
|
|
lline = lr;
|
|
}
|
|
|
|
int tfitx = lr.topFit(x1, x2, y1, y2);
|
|
if (tfitx < tfit) {
|
|
tfit = tfitx;
|
|
tline = lr;
|
|
}
|
|
|
|
int rfitx = lr.rightFit(x1, x2, y1, y2);
|
|
if (rfitx < rfit) {
|
|
rfit = rfitx;
|
|
rline = lr;
|
|
}
|
|
|
|
int bfitx = lr.bottomFit(x1, x2, y1, y2);
|
|
if (bfitx < bfit) {
|
|
bfit = bfitx;
|
|
bline = lr;
|
|
}
|
|
}
|
|
|
|
if (lfit < threshold && lline != null) {
|
|
tc.borderLeft = lline.l;
|
|
}
|
|
if (tfit < threshold && tline != null) {
|
|
tc.borderTop = tline.l;
|
|
}
|
|
if (rfit < threshold && rline != null) {
|
|
tc.borderRight = rline.l;
|
|
}
|
|
if (bfit < threshold && bline != null) {
|
|
tc.borderBottom = bline.l;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void initTable(){
|
|
cellListToArray();
|
|
fitLinesToCells();
|
|
}
|
|
|
|
/**
|
|
* Assign the <code>SlideShow</code> this shape belongs to
|
|
*
|
|
* @param sheet owner of this shape
|
|
*/
|
|
@Override
|
|
public void setSheet(HSLFSheet sheet){
|
|
super.setSheet(sheet);
|
|
if (cells == null) {
|
|
initTable();
|
|
} else {
|
|
for (HSLFTableCell cols[] : cells) {
|
|
for (HSLFTableCell col : cols) {
|
|
col.setSheet(sheet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public double getRowHeight(int row) {
|
|
if (row < 0 || row >= cells.length) {
|
|
throw new IllegalArgumentException("Row index '"+row+"' is not within range [0-"+(cells.length-1)+"]");
|
|
}
|
|
|
|
return cells[row][0].getAnchor().getHeight();
|
|
}
|
|
|
|
@Override
|
|
public void setRowHeight(int row, double height) {
|
|
if (row < 0 || row >= cells.length) {
|
|
throw new IllegalArgumentException("Row index '"+row+"' is not within range [0-"+(cells.length-1)+"]");
|
|
}
|
|
|
|
int pxHeight = Units.pointsToPixel(height);
|
|
double currentHeight = cells[row][0].getAnchor().getHeight();
|
|
double dy = pxHeight - currentHeight;
|
|
|
|
for (int i = row; i < cells.length; i++) {
|
|
for (int j = 0; j < cells[i].length; j++) {
|
|
Rectangle2D anchor = cells[i][j].getAnchor();
|
|
if(i == row) {
|
|
anchor.setRect(anchor.getX(), anchor.getY(), anchor.getWidth(), pxHeight);
|
|
} else {
|
|
anchor.setRect(anchor.getX(), anchor.getY()+dy, anchor.getWidth(), pxHeight);
|
|
}
|
|
cells[i][j].setAnchor(anchor);
|
|
}
|
|
}
|
|
Rectangle2D tblanchor = getAnchor();
|
|
tblanchor.setRect(tblanchor.getX(), tblanchor.getY(), tblanchor.getWidth(), tblanchor.getHeight() + dy);
|
|
setExteriorAnchor(tblanchor);
|
|
|
|
}
|
|
|
|
@Override
|
|
public double getColumnWidth(int col) {
|
|
if (col < 0 || col >= cells[0].length) {
|
|
throw new IllegalArgumentException("Column index '"+col+"' is not within range [0-"+(cells[0].length-1)+"]");
|
|
}
|
|
|
|
// TODO: check for merged cols
|
|
double width = cells[0][col].getAnchor().getWidth();
|
|
return width;
|
|
}
|
|
|
|
@Override
|
|
public void setColumnWidth(int col, final double width){
|
|
if (col < 0 || col >= cells[0].length) {
|
|
throw new IllegalArgumentException("Column index '"+col+"' is not within range [0-"+(cells[0].length-1)+"]");
|
|
}
|
|
double currentWidth = cells[0][col].getAnchor().getWidth();
|
|
double dx = width - currentWidth;
|
|
for (HSLFTableCell cols[] : cells) {
|
|
Rectangle2D anchor = cols[col].getAnchor();
|
|
anchor.setRect(anchor.getX(), anchor.getY(), width, anchor.getHeight());
|
|
cols[col].setAnchor(anchor);
|
|
|
|
if (col < cols.length - 1) {
|
|
for (int j = col+1; j < cols.length; j++) {
|
|
anchor = cols[j].getAnchor();
|
|
anchor.setRect(anchor.getX()+dx, anchor.getY(), anchor.getWidth(), anchor.getHeight());
|
|
cols[j].setAnchor(anchor);
|
|
}
|
|
}
|
|
}
|
|
Rectangle2D tblanchor = getAnchor();
|
|
tblanchor.setRect(tblanchor.getX(), tblanchor.getY(), tblanchor.getWidth() + dx, tblanchor.getHeight());
|
|
setExteriorAnchor(tblanchor);
|
|
}
|
|
|
|
protected HSLFTableCell getRelativeCell(HSLFTableCell origin, int row, int col) {
|
|
int thisRow = 0, thisCol = 0;
|
|
boolean found = false;
|
|
outer: for (HSLFTableCell[] tca : cells) {
|
|
thisCol = 0;
|
|
for (HSLFTableCell tc : tca) {
|
|
if (tc == origin) {
|
|
found = true;
|
|
break outer;
|
|
}
|
|
thisCol++;
|
|
}
|
|
thisRow++;
|
|
}
|
|
|
|
int otherRow = thisRow + row;
|
|
int otherCol = thisCol + col;
|
|
return (found
|
|
&& 0 <= otherRow && otherRow < cells.length
|
|
&& 0 <= otherCol && otherCol < cells[otherRow].length)
|
|
? cells[otherRow][otherCol] : null;
|
|
}
|
|
|
|
@Override
|
|
protected void moveAndScale(Rectangle2D anchorDest){
|
|
super.moveAndScale(anchorDest);
|
|
updateRowHeightsProperty();
|
|
}
|
|
|
|
private void updateRowHeightsProperty() {
|
|
AbstractEscherOptRecord opt = getEscherOptRecord();
|
|
EscherArrayProperty p = opt.lookup(EscherProperties.GROUPSHAPE__TABLEROWPROPERTIES);
|
|
byte[] val = new byte[4];
|
|
for (int rowIdx = 0; rowIdx < cells.length; rowIdx++) {
|
|
int rowHeight = Units.pointsToMaster(cells[rowIdx][0].getAnchor().getHeight());
|
|
LittleEndian.putInt(val, 0, rowHeight);
|
|
p.setElement(rowIdx, val);
|
|
}
|
|
}
|
|
}
|