| /* ==================================================================== |
| 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); |
| } |
| } |
| } |