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.

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