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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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. private int columnCount = -1;
  47. /**
  48. * Create a new Table of the given number of rows and columns
  49. *
  50. * @param numRows the number of rows
  51. * @param numCols the number of columns
  52. */
  53. protected HSLFTable(int numRows, int numCols) {
  54. this(numRows, numCols, null);
  55. }
  56. /**
  57. * Create a new Table of the given number of rows and columns
  58. *
  59. * @param numRows the number of rows
  60. * @param numCols the number of columns
  61. * @param parent the parent shape, or null if table is added to sheet
  62. */
  63. protected HSLFTable(int numRows, int numCols, ShapeContainer<HSLFShape,HSLFTextParagraph> parent) {
  64. super(parent);
  65. if(numRows < 1) throw new IllegalArgumentException("The number of rows must be greater than 1");
  66. if(numCols < 1) throw new IllegalArgumentException("The number of columns must be greater than 1");
  67. double x=0, y=0, tblWidth=0, tblHeight=0;
  68. cells = new HSLFTableCell[numRows][numCols];
  69. for (int i = 0; i < cells.length; i++) {
  70. x = 0;
  71. for (int j = 0; j < cells[i].length; j++) {
  72. cells[i][j] = new HSLFTableCell(this);
  73. Rectangle2D anchor = new Rectangle2D.Double(x, y, HSLFTableCell.DEFAULT_WIDTH, HSLFTableCell.DEFAULT_HEIGHT);
  74. cells[i][j].setAnchor(anchor);
  75. x += HSLFTableCell.DEFAULT_WIDTH;
  76. }
  77. y += HSLFTableCell.DEFAULT_HEIGHT;
  78. }
  79. tblWidth = x;
  80. tblHeight = y;
  81. setExteriorAnchor(new Rectangle2D.Double(0, 0, tblWidth, tblHeight));
  82. EscherContainerRecord spCont = (EscherContainerRecord) getSpContainer().getChild(0);
  83. AbstractEscherOptRecord opt = new EscherOptRecord();
  84. opt.setRecordId((short)RecordTypes.EscherUserDefined);
  85. opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GROUPSHAPE__TABLEPROPERTIES, 1));
  86. EscherArrayProperty p = new EscherArrayProperty((short)(0x4000 | EscherProperties.GROUPSHAPE__TABLEROWPROPERTIES), false, null);
  87. p.setSizeOfElements(0x0004);
  88. p.setNumberOfElementsInArray(numRows);
  89. p.setNumberOfElementsInMemory(numRows);
  90. opt.addEscherProperty(p);
  91. spCont.addChildBefore(opt, RecordTypes.EscherClientAnchor);
  92. }
  93. /**
  94. * Create a Table object and initialize it from the supplied Record container.
  95. *
  96. * @param escherRecord <code>EscherSpContainer</code> container which holds information about this shape
  97. * @param parent the parent of the shape
  98. */
  99. protected HSLFTable(EscherContainerRecord escherRecord, ShapeContainer<HSLFShape,HSLFTextParagraph> parent) {
  100. super(escherRecord, parent);
  101. }
  102. @Override
  103. public HSLFTableCell getCell(int row, int col) {
  104. if (row < 0 || cells.length <= row) {
  105. return null;
  106. }
  107. HSLFTableCell[] r = cells[row];
  108. if (r == null || col < 0 || r.length <= col) {
  109. // empty row
  110. return null;
  111. }
  112. // cell can be potentially empty ...
  113. return r[col];
  114. }
  115. @Override
  116. public int getNumberOfColumns() {
  117. if (columnCount == -1) {
  118. // check all rows in case of merged rows
  119. for (HSLFTableCell[] hc : cells) {
  120. if (hc != null) {
  121. columnCount = Math.max(columnCount, hc.length);
  122. }
  123. }
  124. }
  125. return columnCount;
  126. }
  127. @Override
  128. public int getNumberOfRows() {
  129. return cells.length;
  130. }
  131. protected void afterInsert(HSLFSheet sh){
  132. super.afterInsert(sh);
  133. Set<HSLFLine> lineSet = new HashSet<HSLFLine>();
  134. for (HSLFTableCell row[] : cells) {
  135. for (HSLFTableCell c : row) {
  136. addShape(c);
  137. for (HSLFLine bt : new HSLFLine[]{ c.borderTop, c.borderRight, c.borderBottom, c.borderLeft }) {
  138. if (bt != null) {
  139. lineSet.add(bt);
  140. }
  141. }
  142. }
  143. }
  144. for (HSLFLine l : lineSet) {
  145. addShape(l);
  146. }
  147. updateRowHeightsProperty();
  148. }
  149. private static class TableCellComparator implements Comparator<HSLFShape> {
  150. public int compare( HSLFShape o1, HSLFShape o2 ) {
  151. Rectangle2D anchor1 = o1.getAnchor();
  152. Rectangle2D anchor2 = o2.getAnchor();
  153. double delta = anchor1.getY() - anchor2.getY();
  154. if (delta == 0) {
  155. delta = anchor1.getX() - anchor2.getX();
  156. }
  157. // descending size
  158. if (delta == 0) {
  159. delta = (anchor2.getWidth()*anchor2.getHeight())-(anchor1.getWidth()*anchor1.getHeight());
  160. }
  161. return (int)Math.signum(delta);
  162. }
  163. }
  164. private void cellListToArray() {
  165. List<HSLFTableCell> htc = new ArrayList<HSLFTableCell>();
  166. for (HSLFShape h : getShapes()) {
  167. if (h instanceof HSLFTableCell) {
  168. htc.add((HSLFTableCell)h);
  169. }
  170. }
  171. if (htc.isEmpty()) {
  172. throw new IllegalStateException("HSLFTable without HSLFTableCells");
  173. }
  174. Collections.sort(htc, new TableCellComparator());
  175. List<HSLFTableCell[]> lst = new ArrayList<HSLFTableCell[]>();
  176. List<HSLFTableCell> row = new ArrayList<HSLFTableCell>();
  177. double y0 = htc.get(0).getAnchor().getY();
  178. for (HSLFTableCell sh : htc) {
  179. Rectangle2D anchor = sh.getAnchor();
  180. boolean isNextRow = (anchor.getY() > y0);
  181. if (isNextRow) {
  182. y0 = anchor.getY();
  183. lst.add(row.toArray(new HSLFTableCell[row.size()]));
  184. row.clear();
  185. }
  186. row.add(sh);
  187. }
  188. lst.add(row.toArray(new HSLFTableCell[row.size()]));
  189. cells = lst.toArray(new HSLFTableCell[lst.size()][]);
  190. }
  191. static class LineRect {
  192. final HSLFLine l;
  193. final double lx1, lx2, ly1, ly2;
  194. LineRect(HSLFLine l) {
  195. this.l = l;
  196. Rectangle2D r = l.getAnchor();
  197. lx1 = r.getMinX();
  198. lx2 = r.getMaxX();
  199. ly1 = r.getMinY();
  200. ly2 = r.getMaxY();
  201. }
  202. int leftFit(double x1, double x2, double y1, double y2) {
  203. return (int)(Math.abs(x1-lx1)+Math.abs(y1-ly1)+Math.abs(x1-lx2)+Math.abs(y2-ly2));
  204. }
  205. int topFit(double x1, double x2, double y1, double y2) {
  206. return (int)(Math.abs(x1-lx1)+Math.abs(y1-ly1)+Math.abs(x2-lx2)+Math.abs(y1-ly2));
  207. }
  208. int rightFit(double x1, double x2, double y1, double y2) {
  209. return (int)(Math.abs(x2-lx1)+Math.abs(y1-ly1)+Math.abs(x2-lx2)+Math.abs(y2-ly2));
  210. }
  211. int bottomFit(double x1, double x2, double y1, double y2) {
  212. return (int)(Math.abs(x1-lx1)+Math.abs(y2-ly1)+Math.abs(x2-lx2)+Math.abs(y2-ly2));
  213. }
  214. }
  215. private void fitLinesToCells() {
  216. List<LineRect> lines = new ArrayList<LineRect>();
  217. for (HSLFShape h : getShapes()) {
  218. if (h instanceof HSLFLine) {
  219. lines.add(new LineRect((HSLFLine)h));
  220. }
  221. }
  222. final int threshold = 5;
  223. // TODO: this only works for non-rotated tables
  224. for (HSLFTableCell[] tca : cells) {
  225. for (HSLFTableCell tc : tca) {
  226. final Rectangle2D cellAnchor = tc.getAnchor();
  227. /**
  228. * x1/y1 --------+
  229. * | |
  230. * +---------x2/y2
  231. */
  232. final double x1 = cellAnchor.getMinX();
  233. final double x2 = cellAnchor.getMaxX();
  234. final double y1 = cellAnchor.getMinY();
  235. final double y2 = cellAnchor.getMaxY();
  236. LineRect lline = null, tline = null, rline = null, bline = null;
  237. int lfit = Integer.MAX_VALUE, tfit = Integer.MAX_VALUE, rfit = Integer.MAX_VALUE, bfit = Integer.MAX_VALUE;
  238. for (LineRect lr : lines) {
  239. // calculate border fit
  240. int lfitx = lr.leftFit(x1, x2, y1, y2);
  241. if (lfitx < lfit) {
  242. lfit = lfitx;
  243. lline = lr;
  244. }
  245. int tfitx = lr.topFit(x1, x2, y1, y2);
  246. if (tfitx < tfit) {
  247. tfit = tfitx;
  248. tline = lr;
  249. }
  250. int rfitx = lr.rightFit(x1, x2, y1, y2);
  251. if (rfitx < rfit) {
  252. rfit = rfitx;
  253. rline = lr;
  254. }
  255. int bfitx = lr.bottomFit(x1, x2, y1, y2);
  256. if (bfitx < bfit) {
  257. bfit = bfitx;
  258. bline = lr;
  259. }
  260. }
  261. if (lfit < threshold) {
  262. tc.borderLeft = lline.l;
  263. }
  264. if (tfit < threshold) {
  265. tc.borderTop = tline.l;
  266. }
  267. if (rfit < threshold) {
  268. tc.borderRight = rline.l;
  269. }
  270. if (bfit < threshold) {
  271. tc.borderBottom = bline.l;
  272. }
  273. }
  274. }
  275. }
  276. protected void initTable(){
  277. cellListToArray();
  278. fitLinesToCells();
  279. }
  280. /**
  281. * Assign the <code>SlideShow</code> this shape belongs to
  282. *
  283. * @param sheet owner of this shape
  284. */
  285. public void setSheet(HSLFSheet sheet){
  286. super.setSheet(sheet);
  287. if (cells == null) {
  288. initTable();
  289. } else {
  290. for (HSLFTableCell cols[] : cells) {
  291. for (HSLFTableCell col : cols) {
  292. col.setSheet(sheet);
  293. }
  294. }
  295. }
  296. }
  297. @Override
  298. public double getRowHeight(int row) {
  299. if (row < 0 || row >= cells.length) {
  300. throw new IllegalArgumentException("Row index '"+row+"' is not within range [0-"+(cells.length-1)+"]");
  301. }
  302. return cells[row][0].getAnchor().getHeight();
  303. }
  304. @Override
  305. public void setRowHeight(int row, double height) {
  306. if (row < 0 || row >= cells.length) {
  307. throw new IllegalArgumentException("Row index '"+row+"' is not within range [0-"+(cells.length-1)+"]");
  308. }
  309. int pxHeight = Units.pointsToPixel(height);
  310. double currentHeight = cells[row][0].getAnchor().getHeight();
  311. double dy = pxHeight - currentHeight;
  312. for (int i = row; i < cells.length; i++) {
  313. for (int j = 0; j < cells[i].length; j++) {
  314. Rectangle2D anchor = cells[i][j].getAnchor();
  315. if(i == row) {
  316. anchor.setRect(anchor.getX(), anchor.getY(), anchor.getWidth(), pxHeight);
  317. } else {
  318. anchor.setRect(anchor.getX(), anchor.getY()+dy, anchor.getWidth(), pxHeight);
  319. }
  320. cells[i][j].setAnchor(anchor);
  321. }
  322. }
  323. Rectangle2D tblanchor = getAnchor();
  324. tblanchor.setRect(tblanchor.getX(), tblanchor.getY(), tblanchor.getWidth(), tblanchor.getHeight() + dy);
  325. setExteriorAnchor(tblanchor);
  326. }
  327. @Override
  328. public double getColumnWidth(int col) {
  329. if (col < 0 || col >= cells[0].length) {
  330. throw new IllegalArgumentException("Column index '"+col+"' is not within range [0-"+(cells[0].length-1)+"]");
  331. }
  332. // TODO: check for merged cols
  333. double width = cells[0][col].getAnchor().getWidth();
  334. return width;
  335. }
  336. @Override
  337. public void setColumnWidth(int col, final double width){
  338. if (col < 0 || col >= cells[0].length) {
  339. throw new IllegalArgumentException("Column index '"+col+"' is not within range [0-"+(cells[0].length-1)+"]");
  340. }
  341. double currentWidth = cells[0][col].getAnchor().getWidth();
  342. double dx = width - currentWidth;
  343. for (HSLFTableCell cols[] : cells) {
  344. Rectangle2D anchor = cols[col].getAnchor();
  345. anchor.setRect(anchor.getX(), anchor.getY(), width, anchor.getHeight());
  346. cols[col].setAnchor(anchor);
  347. if (col < cols.length - 1) {
  348. for (int j = col+1; j < cols.length; j++) {
  349. anchor = cols[j].getAnchor();
  350. anchor.setRect(anchor.getX()+dx, anchor.getY(), anchor.getWidth(), anchor.getHeight());
  351. cols[j].setAnchor(anchor);
  352. }
  353. }
  354. }
  355. Rectangle2D tblanchor = getAnchor();
  356. tblanchor.setRect(tblanchor.getX(), tblanchor.getY(), tblanchor.getWidth() + dx, tblanchor.getHeight());
  357. setExteriorAnchor(tblanchor);
  358. }
  359. protected HSLFTableCell getRelativeCell(HSLFTableCell origin, int row, int col) {
  360. int thisRow = 0, thisCol = 0;
  361. boolean found = false;
  362. outer: for (HSLFTableCell[] tca : cells) {
  363. thisCol = 0;
  364. for (HSLFTableCell tc : tca) {
  365. if (tc == origin) {
  366. found = true;
  367. break outer;
  368. }
  369. thisCol++;
  370. }
  371. thisRow++;
  372. }
  373. int otherRow = thisRow + row;
  374. int otherCol = thisCol + col;
  375. return (found
  376. && 0 <= otherRow && otherRow < cells.length
  377. && 0 <= otherCol && otherCol < cells[otherRow].length)
  378. ? cells[otherRow][otherCol] : null;
  379. }
  380. @Override
  381. protected void moveAndScale(Rectangle2D anchorDest){
  382. super.moveAndScale(anchorDest);
  383. updateRowHeightsProperty();
  384. }
  385. private void updateRowHeightsProperty() {
  386. AbstractEscherOptRecord opt = getEscherOptRecord();
  387. EscherArrayProperty p = opt.lookup(EscherProperties.GROUPSHAPE__TABLEROWPROPERTIES);
  388. byte[] val = new byte[4];
  389. for (int rowIdx = 0; rowIdx < cells.length; rowIdx++) {
  390. int rowHeight = Units.pointsToMaster(cells[rowIdx][0].getAnchor().getHeight());
  391. LittleEndian.putInt(val, 0, rowHeight);
  392. p.setElement(rowIdx, val);
  393. }
  394. }
  395. }