You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

HSLFTable.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.hslf.usermodel;
  16. import java.awt.geom.Rectangle2D;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.Comparator;
  20. import java.util.HashSet;
  21. import java.util.List;
  22. import java.util.Set;
  23. import org.apache.poi.ddf.AbstractEscherOptRecord;
  24. import org.apache.poi.ddf.EscherArrayProperty;
  25. import org.apache.poi.ddf.EscherContainerRecord;
  26. import org.apache.poi.ddf.EscherOptRecord;
  27. import org.apache.poi.ddf.EscherProperties;
  28. import org.apache.poi.ddf.EscherSimpleProperty;
  29. import org.apache.poi.hslf.record.RecordTypes;
  30. import org.apache.poi.sl.usermodel.ShapeContainer;
  31. import org.apache.poi.sl.usermodel.TableShape;
  32. import org.apache.poi.util.LittleEndian;
  33. import org.apache.poi.util.Units;
  34. /**
  35. * Represents a table in a PowerPoint presentation
  36. *
  37. * @author Yegor Kozlov
  38. */
  39. public final class HSLFTable extends HSLFGroupShape
  40. implements HSLFShapeContainer, TableShape<HSLFShape,HSLFTextParagraph> {
  41. protected static final int BORDERS_ALL = 5;
  42. protected static final int BORDERS_OUTSIDE = 6;
  43. protected static final int BORDERS_INSIDE = 7;
  44. protected static final int BORDERS_NONE = 8;
  45. protected HSLFTableCell[][] cells;
  46. /**
  47. * Create a new Table of the given number of rows and columns
  48. *
  49. * @param numRows the number of rows
  50. * @param numCols the number of columns
  51. */
  52. protected HSLFTable(int numRows, int numCols) {
  53. this(numRows, numCols, null);
  54. }
  55. /**
  56. * Create a new Table of the given number of rows and columns
  57. *
  58. * @param numRows the number of rows
  59. * @param numCols the number of columns
  60. * @param parent the parent shape, or null if table is added to sheet
  61. */
  62. protected HSLFTable(int numRows, int numCols, ShapeContainer<HSLFShape,HSLFTextParagraph> parent) {
  63. super(parent);
  64. if(numRows < 1) throw new IllegalArgumentException("The number of rows must be greater than 1");
  65. if(numCols < 1) throw new IllegalArgumentException("The number of columns must be greater than 1");
  66. double x=0, y=0, tblWidth=0, tblHeight=0;
  67. cells = new HSLFTableCell[numRows][numCols];
  68. for (int i = 0; i < cells.length; i++) {
  69. x = 0;
  70. for (int j = 0; j < cells[i].length; j++) {
  71. cells[i][j] = new HSLFTableCell(this);
  72. Rectangle2D anchor = new Rectangle2D.Double(x, y, HSLFTableCell.DEFAULT_WIDTH, HSLFTableCell.DEFAULT_HEIGHT);
  73. cells[i][j].setAnchor(anchor);
  74. x += HSLFTableCell.DEFAULT_WIDTH;
  75. }
  76. y += HSLFTableCell.DEFAULT_HEIGHT;
  77. }
  78. tblWidth = x;
  79. tblHeight = y;
  80. setExteriorAnchor(new Rectangle2D.Double(0, 0, tblWidth, tblHeight));
  81. EscherContainerRecord spCont = (EscherContainerRecord) getSpContainer().getChild(0);
  82. AbstractEscherOptRecord opt = new EscherOptRecord();
  83. opt.setRecordId((short)RecordTypes.EscherUserDefined);
  84. opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GROUPSHAPE__TABLEPROPERTIES, 1));
  85. EscherArrayProperty p = new EscherArrayProperty((short)(0x4000 | EscherProperties.GROUPSHAPE__TABLEROWPROPERTIES), false, null);
  86. p.setSizeOfElements(0x0004);
  87. p.setNumberOfElementsInArray(numRows);
  88. p.setNumberOfElementsInMemory(numRows);
  89. opt.addEscherProperty(p);
  90. spCont.addChildBefore(opt, RecordTypes.EscherClientAnchor);
  91. }
  92. /**
  93. * Create a Table object and initialize it from the supplied Record container.
  94. *
  95. * @param escherRecord <code>EscherSpContainer</code> container which holds information about this shape
  96. * @param parent the parent of the shape
  97. */
  98. protected HSLFTable(EscherContainerRecord escherRecord, ShapeContainer<HSLFShape,HSLFTextParagraph> parent) {
  99. super(escherRecord, parent);
  100. }
  101. /**
  102. * Gets a cell
  103. *
  104. * @param row the row index (0-based)
  105. * @param col the column index (0-based)
  106. * @return the cell
  107. */
  108. public HSLFTableCell getCell(int row, int col) {
  109. return cells[row][col];
  110. }
  111. @Override
  112. public int getNumberOfColumns() {
  113. return cells[0].length;
  114. }
  115. @Override
  116. public int getNumberOfRows() {
  117. return cells.length;
  118. }
  119. protected void afterInsert(HSLFSheet sh){
  120. super.afterInsert(sh);
  121. Set<HSLFLine> lineSet = new HashSet<HSLFLine>();
  122. for (HSLFTableCell row[] : cells) {
  123. for (HSLFTableCell c : row) {
  124. addShape(c);
  125. for (HSLFLine bt : new HSLFLine[]{ c.borderTop, c.borderRight, c.borderBottom, c.borderLeft }) {
  126. if (bt != null) {
  127. lineSet.add(bt);
  128. }
  129. }
  130. }
  131. }
  132. for (HSLFLine l : lineSet) {
  133. addShape(l);
  134. }
  135. updateRowHeightsProperty();
  136. }
  137. private static class TableCellComparator implements Comparator<HSLFShape> {
  138. public int compare( HSLFShape o1, HSLFShape o2 ) {
  139. Rectangle2D anchor1 = o1.getAnchor();
  140. Rectangle2D anchor2 = o2.getAnchor();
  141. double delta = anchor1.getY() - anchor2.getY();
  142. if (delta == 0) {
  143. delta = anchor1.getX() - anchor2.getX();
  144. }
  145. // descending size
  146. if (delta == 0) {
  147. delta = (anchor2.getWidth()*anchor2.getHeight())-(anchor1.getWidth()*anchor1.getHeight());
  148. }
  149. return (int)Math.signum(delta);
  150. }
  151. }
  152. private void cellListToArray() {
  153. List<HSLFTableCell> htc = new ArrayList<HSLFTableCell>();
  154. for (HSLFShape h : getShapes()) {
  155. if (h instanceof HSLFTableCell) {
  156. htc.add((HSLFTableCell)h);
  157. }
  158. }
  159. if (htc.isEmpty()) {
  160. throw new IllegalStateException("HSLFTable without HSLFTableCells");
  161. }
  162. Collections.sort(htc, new TableCellComparator());
  163. List<HSLFTableCell[]> lst = new ArrayList<HSLFTableCell[]>();
  164. List<HSLFTableCell> row = new ArrayList<HSLFTableCell>();
  165. double y0 = htc.get(0).getAnchor().getY();
  166. for (HSLFTableCell sh : htc) {
  167. Rectangle2D anchor = sh.getAnchor();
  168. boolean isNextRow = (anchor.getY() > y0);
  169. if (isNextRow) {
  170. y0 = anchor.getY();
  171. lst.add(row.toArray(new HSLFTableCell[row.size()]));
  172. row.clear();
  173. }
  174. row.add(sh);
  175. }
  176. lst.add(row.toArray(new HSLFTableCell[row.size()]));
  177. cells = lst.toArray(new HSLFTableCell[lst.size()][]);
  178. }
  179. static class LineRect {
  180. final HSLFLine l;
  181. final double lx1, lx2, ly1, ly2;
  182. LineRect(HSLFLine l) {
  183. this.l = l;
  184. Rectangle2D r = l.getAnchor();
  185. lx1 = r.getMinX();
  186. lx2 = r.getMaxX();
  187. ly1 = r.getMinY();
  188. ly2 = r.getMaxY();
  189. }
  190. int leftFit(double x1, double x2, double y1, double y2) {
  191. return (int)(Math.abs(x1-lx1)+Math.abs(y1-ly1)+Math.abs(x1-lx2)+Math.abs(y2-ly2));
  192. }
  193. int topFit(double x1, double x2, double y1, double y2) {
  194. return (int)(Math.abs(x1-lx1)+Math.abs(y1-ly1)+Math.abs(x2-lx2)+Math.abs(y1-ly2));
  195. }
  196. int rightFit(double x1, double x2, double y1, double y2) {
  197. return (int)(Math.abs(x2-lx1)+Math.abs(y1-ly1)+Math.abs(x2-lx2)+Math.abs(y2-ly2));
  198. }
  199. int bottomFit(double x1, double x2, double y1, double y2) {
  200. return (int)(Math.abs(x1-lx1)+Math.abs(y2-ly1)+Math.abs(x2-lx2)+Math.abs(y2-ly2));
  201. }
  202. }
  203. private void fitLinesToCells() {
  204. List<LineRect> lines = new ArrayList<LineRect>();
  205. for (HSLFShape h : getShapes()) {
  206. if (h instanceof HSLFLine) {
  207. lines.add(new LineRect((HSLFLine)h));
  208. }
  209. }
  210. final int threshold = 5;
  211. // TODO: this only works for non-rotated tables
  212. for (HSLFTableCell[] tca : cells) {
  213. for (HSLFTableCell tc : tca) {
  214. final Rectangle2D cellAnchor = tc.getAnchor();
  215. /**
  216. * x1/y1 --------+
  217. * | |
  218. * +---------x2/y2
  219. */
  220. final double x1 = cellAnchor.getMinX();
  221. final double x2 = cellAnchor.getMaxX();
  222. final double y1 = cellAnchor.getMinY();
  223. final double y2 = cellAnchor.getMaxY();
  224. LineRect lline = null, tline = null, rline = null, bline = null;
  225. int lfit = Integer.MAX_VALUE, tfit = Integer.MAX_VALUE, rfit = Integer.MAX_VALUE, bfit = Integer.MAX_VALUE;
  226. for (LineRect lr : lines) {
  227. // calculate border fit
  228. int lfitx = lr.leftFit(x1, x2, y1, y2);
  229. if (lfitx < lfit) {
  230. lfit = lfitx;
  231. lline = lr;
  232. }
  233. int tfitx = lr.topFit(x1, x2, y1, y2);
  234. if (tfitx < tfit) {
  235. tfit = tfitx;
  236. tline = lr;
  237. }
  238. int rfitx = lr.rightFit(x1, x2, y1, y2);
  239. if (rfitx < rfit) {
  240. rfit = rfitx;
  241. rline = lr;
  242. }
  243. int bfitx = lr.bottomFit(x1, x2, y1, y2);
  244. if (bfitx < bfit) {
  245. bfit = bfitx;
  246. bline = lr;
  247. }
  248. }
  249. if (lfit < threshold) {
  250. tc.borderLeft = lline.l;
  251. }
  252. if (tfit < threshold) {
  253. tc.borderTop = tline.l;
  254. }
  255. if (rfit < threshold) {
  256. tc.borderRight = rline.l;
  257. }
  258. if (bfit < threshold) {
  259. tc.borderBottom = bline.l;
  260. }
  261. }
  262. }
  263. }
  264. protected void initTable(){
  265. cellListToArray();
  266. fitLinesToCells();
  267. }
  268. /**
  269. * Assign the <code>SlideShow</code> this shape belongs to
  270. *
  271. * @param sheet owner of this shape
  272. */
  273. public void setSheet(HSLFSheet sheet){
  274. super.setSheet(sheet);
  275. if (cells == null) {
  276. initTable();
  277. } else {
  278. for (HSLFTableCell cols[] : cells) {
  279. for (HSLFTableCell col : cols) {
  280. col.setSheet(sheet);
  281. }
  282. }
  283. }
  284. }
  285. @Override
  286. public double getRowHeight(int row) {
  287. if (row < 0 || row >= cells.length) {
  288. throw new IllegalArgumentException("Row index '"+row+"' is not within range [0-"+(cells.length-1)+"]");
  289. }
  290. return cells[row][0].getAnchor().getHeight();
  291. }
  292. @Override
  293. public void setRowHeight(int row, double height) {
  294. if (row < 0 || row >= cells.length) {
  295. throw new IllegalArgumentException("Row index '"+row+"' is not within range [0-"+(cells.length-1)+"]");
  296. }
  297. int pxHeight = Units.pointsToPixel(height);
  298. double currentHeight = cells[row][0].getAnchor().getHeight();
  299. double dy = pxHeight - currentHeight;
  300. for (int i = row; i < cells.length; i++) {
  301. for (int j = 0; j < cells[i].length; j++) {
  302. Rectangle2D anchor = cells[i][j].getAnchor();
  303. if(i == row) {
  304. anchor.setRect(anchor.getX(), anchor.getY(), anchor.getWidth(), pxHeight);
  305. } else {
  306. anchor.setRect(anchor.getX(), anchor.getY()+dy, anchor.getWidth(), pxHeight);
  307. }
  308. cells[i][j].setAnchor(anchor);
  309. }
  310. }
  311. Rectangle2D tblanchor = getAnchor();
  312. tblanchor.setRect(tblanchor.getX(), tblanchor.getY(), tblanchor.getWidth(), tblanchor.getHeight() + dy);
  313. setExteriorAnchor(tblanchor);
  314. }
  315. @Override
  316. public double getColumnWidth(int col) {
  317. if (col < 0 || col >= cells[0].length) {
  318. throw new IllegalArgumentException("Column index '"+col+"' is not within range [0-"+(cells[0].length-1)+"]");
  319. }
  320. // TODO: check for merged cols
  321. double width = cells[0][col].getAnchor().getWidth();
  322. return width;
  323. }
  324. @Override
  325. public void setColumnWidth(int col, final double width){
  326. if (col < 0 || col >= cells[0].length) {
  327. throw new IllegalArgumentException("Column index '"+col+"' is not within range [0-"+(cells[0].length-1)+"]");
  328. }
  329. double currentWidth = cells[0][col].getAnchor().getWidth();
  330. double dx = width - currentWidth;
  331. for (HSLFTableCell cols[] : cells) {
  332. Rectangle2D anchor = cols[col].getAnchor();
  333. anchor.setRect(anchor.getX(), anchor.getY(), width, anchor.getHeight());
  334. cols[col].setAnchor(anchor);
  335. if (col < cols.length - 1) {
  336. for (int j = col+1; j < cols.length; j++) {
  337. anchor = cols[j].getAnchor();
  338. anchor.setRect(anchor.getX()+dx, anchor.getY(), anchor.getWidth(), anchor.getHeight());
  339. cols[j].setAnchor(anchor);
  340. }
  341. }
  342. }
  343. Rectangle2D tblanchor = getAnchor();
  344. tblanchor.setRect(tblanchor.getX(), tblanchor.getY(), tblanchor.getWidth() + dx, tblanchor.getHeight());
  345. setExteriorAnchor(tblanchor);
  346. }
  347. protected HSLFTableCell getRelativeCell(HSLFTableCell origin, int row, int col) {
  348. int thisRow = 0, thisCol = 0;
  349. boolean found = false;
  350. outer: for (HSLFTableCell[] tca : cells) {
  351. thisCol = 0;
  352. for (HSLFTableCell tc : tca) {
  353. if (tc == origin) {
  354. found = true;
  355. break outer;
  356. }
  357. thisCol++;
  358. }
  359. thisRow++;
  360. }
  361. int otherRow = thisRow + row;
  362. int otherCol = thisCol + col;
  363. return (found
  364. && 0 <= otherRow && otherRow < cells.length
  365. && 0 <= otherCol && otherCol < cells[otherRow].length)
  366. ? cells[otherRow][otherCol] : null;
  367. }
  368. @Override
  369. protected void moveAndScale(Rectangle2D anchorDest){
  370. super.moveAndScale(anchorDest);
  371. updateRowHeightsProperty();
  372. }
  373. private void updateRowHeightsProperty() {
  374. AbstractEscherOptRecord opt = getEscherOptRecord();
  375. EscherArrayProperty p = opt.lookup(EscherProperties.GROUPSHAPE__TABLEROWPROPERTIES);
  376. byte[] val = new byte[4];
  377. for (int rowIdx = 0; rowIdx < cells.length; rowIdx++) {
  378. int rowHeight = Units.pointsToMaster(cells[rowIdx][0].getAnchor().getHeight());
  379. LittleEndian.putInt(val, 0, rowHeight);
  380. p.setElement(rowIdx, val);
  381. }
  382. }
  383. }