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

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