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.

RowPainter.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  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. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.layoutmgr.table;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.Iterator;
  22. import java.util.List;
  23. import java.util.ListIterator;
  24. import org.apache.commons.logging.Log;
  25. import org.apache.commons.logging.LogFactory;
  26. import org.apache.fop.area.Block;
  27. import org.apache.fop.area.Trait;
  28. import org.apache.fop.fo.flow.table.ConditionalBorder;
  29. import org.apache.fop.fo.flow.table.EffRow;
  30. import org.apache.fop.fo.flow.table.EmptyGridUnit;
  31. import org.apache.fop.fo.flow.table.GridUnit;
  32. import org.apache.fop.fo.flow.table.PrimaryGridUnit;
  33. import org.apache.fop.fo.flow.table.Table;
  34. import org.apache.fop.fo.flow.table.TableColumn;
  35. import org.apache.fop.fo.flow.table.TablePart;
  36. import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
  37. import org.apache.fop.fo.properties.CommonBorderPaddingBackground.BorderInfo;
  38. import org.apache.fop.layoutmgr.ElementListUtils;
  39. import org.apache.fop.layoutmgr.KnuthElement;
  40. import org.apache.fop.layoutmgr.KnuthPossPosIter;
  41. import org.apache.fop.layoutmgr.LayoutContext;
  42. import org.apache.fop.layoutmgr.SpaceResolver;
  43. import org.apache.fop.layoutmgr.TraitSetter;
  44. class RowPainter {
  45. private static Log log = LogFactory.getLog(RowPainter.class);
  46. private int colCount;
  47. private int currentRowOffset = 0;
  48. /** Currently handled row (= last encountered row). */
  49. private EffRow currentRow = null;
  50. private LayoutContext layoutContext;
  51. /**
  52. * Index of the first row of the current part present on the current page.
  53. */
  54. private int firstRowIndex;
  55. /**
  56. * Index of the very first row on the current page. Needed to properly handle
  57. * {@link BorderProps#COLLAPSE_OUTER}. This is not the same as {@link #firstRowIndex}
  58. * when the table has headers!
  59. */
  60. private int firstRowOnPageIndex;
  61. /**
  62. * Keeps track of the y-offsets of each row on a page.
  63. * This is particularly needed for spanned cells where you need to know the y-offset
  64. * of the starting row when the area is generated at the time the cell is closed.
  65. */
  66. private List rowOffsets = new ArrayList();
  67. private int[] cellHeights;
  68. private boolean[] firstCellOnPage;
  69. private CellPart[] firstCellParts;
  70. private CellPart[] lastCellParts;
  71. /** y-offset of the current table part. */
  72. private int tablePartOffset = 0;
  73. /** See {@link RowPainter#registerPartBackgroundArea(Block)}. */
  74. private CommonBorderPaddingBackground tablePartBackground;
  75. /** See {@link RowPainter#registerPartBackgroundArea(Block)}. */
  76. private List tablePartBackgroundAreas;
  77. private TableContentLayoutManager tclm;
  78. RowPainter(TableContentLayoutManager tclm, LayoutContext layoutContext) {
  79. this.tclm = tclm;
  80. this.layoutContext = layoutContext;
  81. this.colCount = tclm.getColumns().getColumnCount();
  82. this.cellHeights = new int[colCount];
  83. this.firstCellOnPage = new boolean[colCount];
  84. this.firstCellParts = new CellPart[colCount];
  85. this.lastCellParts = new CellPart[colCount];
  86. this.firstRowIndex = -1;
  87. this.firstRowOnPageIndex = -1;
  88. }
  89. void startTablePart(TablePart tablePart) {
  90. CommonBorderPaddingBackground background = tablePart.getCommonBorderPaddingBackground();
  91. if (background.hasBackground()) {
  92. tablePartBackground = background;
  93. if (tablePartBackgroundAreas == null) {
  94. tablePartBackgroundAreas = new ArrayList();
  95. }
  96. }
  97. tablePartOffset = currentRowOffset;
  98. }
  99. /**
  100. * Signals that the end of the current table part is reached.
  101. *
  102. * @param lastInBody true if the part is the last table-body element to be displayed
  103. * on the current page. In which case all the cells must be flushed even if they
  104. * aren't finished, plus the proper collapsed borders must be selected (trailing
  105. * instead of normal, or rest if the cell is unfinished)
  106. * @param lastOnPage true if the part is the last to be displayed on the current page.
  107. * In which case collapsed after borders for the cells on the last row must be drawn
  108. * in the outer mode
  109. */
  110. void endTablePart(boolean lastInBody, boolean lastOnPage) {
  111. addAreasAndFlushRow(lastInBody, lastOnPage);
  112. if (tablePartBackground != null) {
  113. TableLayoutManager tableLM = tclm.getTableLM();
  114. for (Iterator iter = tablePartBackgroundAreas.iterator(); iter.hasNext();) {
  115. Block backgroundArea = (Block) iter.next();
  116. TraitSetter.addBackground(backgroundArea, tablePartBackground, tableLM,
  117. -backgroundArea.getXOffset(), tablePartOffset - backgroundArea.getYOffset(),
  118. tableLM.getContentAreaIPD(), currentRowOffset - tablePartOffset);
  119. }
  120. tablePartBackground = null;
  121. tablePartBackgroundAreas.clear();
  122. }
  123. }
  124. int getAccumulatedBPD() {
  125. return currentRowOffset;
  126. }
  127. /**
  128. * Records the fragment of row represented by the given position. If it belongs to
  129. * another (grid) row than the current one, that latter is painted and flushed first.
  130. *
  131. * @param tcpos a position representing the row fragment
  132. */
  133. void handleTableContentPosition(TableContentPosition tcpos) {
  134. if (log.isDebugEnabled()) {
  135. log.debug("===handleTableContentPosition(" + tcpos);
  136. }
  137. if (currentRow == null) {
  138. currentRow = tcpos.getNewPageRow();
  139. } else {
  140. EffRow row = tcpos.getRow();
  141. if (row.getIndex() > currentRow.getIndex()) {
  142. addAreasAndFlushRow(false, false);
  143. currentRow = row;
  144. }
  145. }
  146. if (firstRowIndex < 0) {
  147. firstRowIndex = currentRow.getIndex();
  148. if (firstRowOnPageIndex < 0) {
  149. firstRowOnPageIndex = firstRowIndex;
  150. }
  151. }
  152. Iterator partIter = tcpos.cellParts.iterator();
  153. //Iterate over all grid units in the current step
  154. while (partIter.hasNext()) {
  155. CellPart cellPart = (CellPart)partIter.next();
  156. if (log.isDebugEnabled()) {
  157. log.debug(">" + cellPart);
  158. }
  159. int colIndex = cellPart.pgu.getColIndex();
  160. if (firstCellParts[colIndex] == null) {
  161. firstCellParts[colIndex] = cellPart;
  162. cellHeights[colIndex] = cellPart.getBorderPaddingBefore(firstCellOnPage[colIndex]);
  163. } else {
  164. assert firstCellParts[colIndex].pgu == cellPart.pgu;
  165. cellHeights[colIndex] += cellPart.getConditionalBeforeContentLength();
  166. }
  167. cellHeights[colIndex] += cellPart.getLength();
  168. lastCellParts[colIndex] = cellPart;
  169. }
  170. }
  171. /**
  172. * Creates the areas corresponding to the last row. That is, an area with background
  173. * for the row, plus areas for all the cells that finish on the row (not spanning over
  174. * further rows).
  175. *
  176. * @param lastInPart true if the row is the last from its table part to be displayed
  177. * on the current page. In which case all the cells must be flushed even if they
  178. * aren't finished, plus the proper collapsed borders must be selected (trailing
  179. * instead of normal, or rest if the cell is unfinished)
  180. * @param lastOnPage true if the row is the very last row of the table that will be
  181. * displayed on the current page. In which case collapsed after borders must be drawn
  182. * in the outer mode
  183. */
  184. private void addAreasAndFlushRow(boolean lastInPart, boolean lastOnPage) {
  185. if (log.isDebugEnabled()) {
  186. log.debug("Remembering yoffset for row " + currentRow.getIndex() + ": "
  187. + currentRowOffset);
  188. }
  189. recordRowOffset(currentRow.getIndex(), currentRowOffset);
  190. // Need to compute the actual row height first
  191. // and determine border behaviour for empty cells
  192. boolean firstCellPart = true;
  193. boolean lastCellPart = true;
  194. int actualRowHeight = 0;
  195. for (int i = 0; i < colCount; i++) {
  196. GridUnit currentGU = currentRow.getGridUnit(i);
  197. if (currentGU.isEmpty()) {
  198. continue;
  199. }
  200. if (currentGU.getColSpanIndex() == 0
  201. && (lastInPart || currentGU.isLastGridUnitRowSpan())
  202. && firstCellParts[i] != null) {
  203. // TODO
  204. // The last test above is a workaround for the stepping algorithm's
  205. // fundamental flaw making it unable to produce the right element list for
  206. // multiple breaks inside a same row group.
  207. // (see http://wiki.apache.org/xmlgraphics-fop/TableLayout/KnownProblems)
  208. // In some extremely rare cases (forced breaks, very small page height), a
  209. // TableContentPosition produced during row delaying may end up alone on a
  210. // page. It will not contain the CellPart instances for the cells starting
  211. // the next row, so firstCellParts[i] will still be null for those ones.
  212. int cellHeight = cellHeights[i];
  213. cellHeight += lastCellParts[i].getConditionalAfterContentLength();
  214. cellHeight += lastCellParts[i].getBorderPaddingAfter(lastInPart);
  215. int cellOffset = getRowOffset(Math.max(firstCellParts[i].pgu.getRowIndex(),
  216. firstRowIndex));
  217. actualRowHeight = Math.max(actualRowHeight, cellOffset + cellHeight
  218. - currentRowOffset);
  219. }
  220. if (firstCellParts[i] != null && !firstCellParts[i].isFirstPart()) {
  221. firstCellPart = false;
  222. }
  223. if (lastCellParts[i] != null && !lastCellParts[i].isLastPart()) {
  224. lastCellPart = false;
  225. }
  226. }
  227. // Then add areas for cells finishing on the current row
  228. for (int i = 0; i < colCount; i++) {
  229. GridUnit currentGU = currentRow.getGridUnit(i);
  230. if (currentGU.isEmpty() && !tclm.isSeparateBorderModel()) {
  231. int borderBeforeWhich;
  232. if (firstCellPart) {
  233. if (firstCellOnPage[i]) {
  234. borderBeforeWhich = ConditionalBorder.LEADING_TRAILING;
  235. } else {
  236. borderBeforeWhich = ConditionalBorder.NORMAL;
  237. }
  238. } else {
  239. borderBeforeWhich = ConditionalBorder.REST;
  240. }
  241. int borderAfterWhich;
  242. if (lastCellPart) {
  243. if (lastInPart) {
  244. borderAfterWhich = ConditionalBorder.LEADING_TRAILING;
  245. } else {
  246. borderAfterWhich = ConditionalBorder.NORMAL;
  247. }
  248. } else {
  249. borderAfterWhich = ConditionalBorder.REST;
  250. }
  251. addAreaForEmptyGridUnit((EmptyGridUnit)currentGU,
  252. currentRow.getIndex(), i,
  253. actualRowHeight,
  254. borderBeforeWhich, borderAfterWhich,
  255. lastOnPage);
  256. firstCellOnPage[i] = false;
  257. } else if (currentGU.getColSpanIndex() == 0
  258. && (lastInPart || currentGU.isLastGridUnitRowSpan())
  259. && firstCellParts[i] != null) {
  260. assert firstCellParts[i].pgu == currentGU.getPrimary();
  261. int borderBeforeWhich;
  262. if (firstCellParts[i].isFirstPart()) {
  263. if (firstCellOnPage[i]) {
  264. borderBeforeWhich = ConditionalBorder.LEADING_TRAILING;
  265. } else {
  266. borderBeforeWhich = ConditionalBorder.NORMAL;
  267. }
  268. } else {
  269. assert firstCellOnPage[i];
  270. borderBeforeWhich = ConditionalBorder.REST;
  271. }
  272. int borderAfterWhich;
  273. if (lastCellParts[i].isLastPart()) {
  274. if (lastInPart) {
  275. borderAfterWhich = ConditionalBorder.LEADING_TRAILING;
  276. } else {
  277. borderAfterWhich = ConditionalBorder.NORMAL;
  278. }
  279. } else {
  280. borderAfterWhich = ConditionalBorder.REST;
  281. }
  282. // when adding the areas for the TableCellLayoutManager this helps with the isLast trait
  283. // if, say, the first cell of a row has content that fits in the page, but the content of
  284. // the second cell does not fit this will assure that the isLast trait for the first cell
  285. // will also be false
  286. lastCellParts[i].pgu.getCellLM().setLastTrait(lastCellParts[i].isLastPart());
  287. addAreasForCell(firstCellParts[i].pgu,
  288. firstCellParts[i].start, lastCellParts[i].end,
  289. actualRowHeight, borderBeforeWhich, borderAfterWhich,
  290. lastOnPage);
  291. firstCellParts[i] = null; // why? what about the lastCellParts[i]?
  292. Arrays.fill(firstCellOnPage, i, i + currentGU.getCell().getNumberColumnsSpanned(),
  293. false);
  294. }
  295. }
  296. currentRowOffset += actualRowHeight;
  297. if (lastInPart) {
  298. /*
  299. * Either the end of the page is reached, then this was the last call of this
  300. * method and we no longer care about currentRow; or the end of a table-part
  301. * (header, footer, body) has been reached, and the next row will anyway be
  302. * different from the current one, and this is unnecessary to call this method
  303. * again in the first lines of handleTableContentPosition, so we may reset the
  304. * following variables.
  305. */
  306. currentRow = null;
  307. firstRowIndex = -1;
  308. rowOffsets.clear();
  309. /*
  310. * The current table part has just been handled. Be it the first one or not,
  311. * the header or the body, in any case the borders-before of the next row
  312. * (i.e., the first row of the next part if any) must be painted in
  313. * COLLAPSE_INNER mode. So the firstRowOnPageIndex indicator must be kept
  314. * disabled. The following way is not the most elegant one but will be good
  315. * enough.
  316. */
  317. firstRowOnPageIndex = Integer.MAX_VALUE;
  318. }
  319. }
  320. // TODO this is not very efficient and should probably be done another way
  321. // this method is only necessary when display-align = center or after, in which case
  322. // the exact content length is needed to compute the size of the empty block that will
  323. // be used as padding.
  324. // This should be handled automatically by a proper use of Knuth elements
  325. private int computeContentLength(PrimaryGridUnit pgu, int startIndex, int endIndex) {
  326. if (startIndex > endIndex) {
  327. // May happen if the cell contributes no content on the current page (empty
  328. // cell, in most cases)
  329. return 0;
  330. } else {
  331. ListIterator iter = pgu.getElements().listIterator(startIndex);
  332. // Skip from the content length calculation glues and penalties occurring at the
  333. // beginning of the page
  334. boolean nextIsBox = false;
  335. while (iter.nextIndex() <= endIndex && !nextIsBox) {
  336. nextIsBox = ((KnuthElement) iter.next()).isBox();
  337. }
  338. int len = 0;
  339. if (((KnuthElement) iter.previous()).isBox()) {
  340. while (iter.nextIndex() < endIndex) {
  341. KnuthElement el = (KnuthElement) iter.next();
  342. if (el.isBox() || el.isGlue()) {
  343. len += el.getWidth();
  344. }
  345. }
  346. len += ActiveCell.getElementContentLength((KnuthElement) iter.next());
  347. }
  348. return len;
  349. }
  350. }
  351. private void addAreasForCell(PrimaryGridUnit pgu, int startPos, int endPos,
  352. int rowHeight, int borderBeforeWhich, int borderAfterWhich, boolean lastOnPage) {
  353. /*
  354. * Determine the index of the first row of this cell that will be displayed on the
  355. * current page.
  356. */
  357. int currentRowIndex = currentRow.getIndex();
  358. int startRowIndex;
  359. int firstRowHeight;
  360. if (pgu.getRowIndex() >= firstRowIndex) {
  361. startRowIndex = pgu.getRowIndex();
  362. if (startRowIndex < currentRowIndex) {
  363. firstRowHeight = getRowOffset(startRowIndex + 1) - getRowOffset(startRowIndex);
  364. } else {
  365. firstRowHeight = rowHeight;
  366. }
  367. } else {
  368. startRowIndex = firstRowIndex;
  369. firstRowHeight = 0;
  370. }
  371. /*
  372. * In collapsing-border model, if the cell spans over several columns/rows then
  373. * dedicated areas will be created for each grid unit to hold the corresponding
  374. * borders. For that we need to know the height of each grid unit, that is of each
  375. * grid row spanned over by the cell
  376. */
  377. int[] spannedGridRowHeights = null;
  378. if (!tclm.getTableLM().getTable().isSeparateBorderModel() && pgu.hasSpanning()) {
  379. spannedGridRowHeights = new int[currentRowIndex - startRowIndex + 1];
  380. int prevOffset = getRowOffset(startRowIndex);
  381. for (int i = 0; i < currentRowIndex - startRowIndex; i++) {
  382. int newOffset = getRowOffset(startRowIndex + i + 1);
  383. spannedGridRowHeights[i] = newOffset - prevOffset;
  384. prevOffset = newOffset;
  385. }
  386. spannedGridRowHeights[currentRowIndex - startRowIndex] = rowHeight;
  387. }
  388. int cellOffset = getRowOffset(startRowIndex);
  389. int cellTotalHeight = rowHeight + currentRowOffset - cellOffset;
  390. if (log.isDebugEnabled()) {
  391. log.debug("Creating area for cell:");
  392. log.debug(" start row: " + pgu.getRowIndex() + " " + currentRowOffset + " "
  393. + cellOffset);
  394. log.debug(" rowHeight=" + rowHeight + " cellTotalHeight=" + cellTotalHeight);
  395. }
  396. TableCellLayoutManager cellLM = pgu.getCellLM();
  397. cellLM.setXOffset(tclm.getXOffsetOfGridUnit(pgu));
  398. cellLM.setYOffset(cellOffset);
  399. cellLM.setContentHeight(computeContentLength(pgu, startPos, endPos));
  400. cellLM.setTotalHeight(cellTotalHeight);
  401. int prevBreak = ElementListUtils.determinePreviousBreak(pgu.getElements(), startPos);
  402. if (endPos >= 0) {
  403. SpaceResolver.performConditionalsNotification(pgu.getElements(),
  404. startPos, endPos, prevBreak);
  405. }
  406. cellLM.addAreas(new KnuthPossPosIter(pgu.getElements(), startPos, endPos + 1),
  407. layoutContext, spannedGridRowHeights, startRowIndex - pgu.getRowIndex(),
  408. currentRowIndex - pgu.getRowIndex(), borderBeforeWhich, borderAfterWhich,
  409. startRowIndex == firstRowOnPageIndex, lastOnPage, this, firstRowHeight);
  410. }
  411. private void addAreaForEmptyGridUnit(EmptyGridUnit gu, int rowIndex, int colIndex,
  412. int actualRowHeight,
  413. int borderBeforeWhich, int borderAfterWhich, boolean lastOnPage) {
  414. //get effective borders
  415. BorderInfo borderBefore = gu.getBorderBefore(borderBeforeWhich);
  416. BorderInfo borderAfter = gu.getBorderAfter(borderAfterWhich);
  417. BorderInfo borderStart = gu.getBorderStart();
  418. BorderInfo borderEnd = gu.getBorderEnd();
  419. if (borderBefore.getRetainedWidth() == 0
  420. && borderAfter.getRetainedWidth() == 0
  421. && borderStart.getRetainedWidth() == 0
  422. && borderEnd.getRetainedWidth() == 0) {
  423. return; //no borders, no area necessary
  424. }
  425. TableLayoutManager tableLM = tclm.getTableLM();
  426. Table table = tableLM.getTable();
  427. TableColumn col = tclm.getColumns().getColumn(colIndex + 1);
  428. //position information
  429. boolean firstOnPage = (rowIndex == firstRowOnPageIndex);
  430. boolean inFirstColumn = (colIndex == 0);
  431. boolean inLastColumn = (colIndex == table.getNumberOfColumns() - 1);
  432. //determine the block area's size
  433. int ipd = col.getColumnWidth().getValue(tableLM);
  434. ipd -= (borderStart.getRetainedWidth() + borderEnd.getRetainedWidth()) / 2;
  435. int bpd = actualRowHeight;
  436. bpd -= (borderBefore.getRetainedWidth() + borderAfter.getRetainedWidth()) / 2;
  437. //generate the block area
  438. Block block = new Block();
  439. block.setPositioning(Block.ABSOLUTE);
  440. block.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
  441. block.setIPD(ipd);
  442. block.setBPD(bpd);
  443. block.setXOffset(tclm.getXOffsetOfGridUnit(colIndex, 1)
  444. + (borderStart.getRetainedWidth() / 2));
  445. block.setYOffset(getRowOffset(rowIndex)
  446. - (borderBefore.getRetainedWidth() / 2));
  447. boolean[] outer = new boolean[] {firstOnPage, lastOnPage, inFirstColumn,
  448. inLastColumn};
  449. TraitSetter.addCollapsingBorders(block,
  450. borderBefore,
  451. borderAfter,
  452. borderStart,
  453. borderEnd, outer);
  454. tableLM.addChildArea(block);
  455. }
  456. /**
  457. * Registers the given area, that will be used to render the part of
  458. * table-header/footer/body background covered by a table-cell. If percentages are
  459. * used to place the background image, the final bpd of the (fraction of) table part
  460. * that will be rendered on the current page must be known. The traits can't then be
  461. * set when the areas for the cell are created since at that moment this bpd is yet
  462. * unknown. So they will instead be set in
  463. * {@link #addAreasAndFlushRow(boolean, boolean)}.
  464. *
  465. * @param backgroundArea the block of the cell's dimensions that will hold the part
  466. * background
  467. */
  468. void registerPartBackgroundArea(Block backgroundArea) {
  469. tclm.getTableLM().addBackgroundArea(backgroundArea);
  470. tablePartBackgroundAreas.add(backgroundArea);
  471. }
  472. /**
  473. * Records the y-offset of the row with the given index.
  474. *
  475. * @param rowIndex index of the row
  476. * @param offset y-offset of the row on the page
  477. */
  478. private void recordRowOffset(int rowIndex, int offset) {
  479. /*
  480. * In some very rare cases a row may be skipped. See for example Bugzilla #43633:
  481. * in a two-column table, a row contains a row-spanning cell and a missing cell.
  482. * In TableStepper#goToNextRowIfCurrentFinished this row will immediately be
  483. * considered as finished, since it contains no cell ending on this row. Thus no
  484. * TableContentPosition will be created for this row. Thus its index will never be
  485. * recorded by the #handleTableContentPosition method.
  486. *
  487. * The offset of such a row is the same as the next non-empty row. It's needed
  488. * to correctly offset blocks for cells starting on this row. Hence the loop
  489. * below.
  490. */
  491. for (int i = rowOffsets.size(); i <= rowIndex - firstRowIndex; i++) {
  492. rowOffsets.add(new Integer(offset));
  493. }
  494. }
  495. /**
  496. * Returns the offset of the row with the given index.
  497. *
  498. * @param rowIndex index of the row
  499. * @return its y-offset on the page
  500. */
  501. private int getRowOffset(int rowIndex) {
  502. return ((Integer) rowOffsets.get(rowIndex - firstRowIndex)).intValue();
  503. }
  504. // TODO get rid of that
  505. /** Signals that the first table-body instance has started. */
  506. void startBody() {
  507. Arrays.fill(firstCellOnPage, true);
  508. }
  509. // TODO get rid of that
  510. /** Signals that the last table-body instance has ended. */
  511. void endBody() {
  512. Arrays.fill(firstCellOnPage, false);
  513. }
  514. }