您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

RowPainter.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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 org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. import org.apache.fop.fo.flow.table.EffRow;
  26. import org.apache.fop.fo.flow.table.GridUnit;
  27. import org.apache.fop.fo.flow.table.PrimaryGridUnit;
  28. import org.apache.fop.fo.flow.table.TableRow;
  29. import org.apache.fop.fo.properties.LengthRangeProperty;
  30. import org.apache.fop.layoutmgr.ElementListUtils;
  31. import org.apache.fop.layoutmgr.KnuthElement;
  32. import org.apache.fop.layoutmgr.KnuthPossPosIter;
  33. import org.apache.fop.layoutmgr.LayoutContext;
  34. import org.apache.fop.layoutmgr.SpaceResolver;
  35. class RowPainter {
  36. private static Log log = LogFactory.getLog(RowPainter.class);
  37. /** The fo:table-row containing the currently handled grid rows. */
  38. private TableRow rowFO = null;
  39. private int colCount;
  40. private int currentRowOffset = 0;
  41. /** Currently handled row (= last encountered row). */
  42. private EffRow currentRow = null;
  43. private LayoutContext layoutContext;
  44. /**
  45. * Index of the first row of the current part present on the current page.
  46. */
  47. private int firstRowIndex;
  48. /**
  49. * Keeps track of the y-offsets of each row on a page.
  50. * This is particularly needed for spanned cells where you need to know the y-offset
  51. * of the starting row when the area is generated at the time the cell is closed.
  52. */
  53. private List rowOffsets = new ArrayList();
  54. //These three variables are our buffer to recombine the individual steps into cells
  55. /** Primary grid units corresponding to the currently handled grid units, per row. */
  56. private PrimaryGridUnit[] primaryGridUnits;
  57. /**
  58. * Index, in the corresponding table cell's list of Knuth elements, of the first
  59. * element present on the current page, per column.
  60. */
  61. private int[] start;
  62. /**
  63. * Index, in the corresponding table cell's list of Knuth elements, of the last
  64. * element present on the current page, per column.
  65. */
  66. private int[] end;
  67. /**
  68. * Length, for each column, of the elements from the current cell put on the
  69. * current page. This is the corresponding area's bpd.
  70. */
  71. private int[] partBPD;
  72. private TableContentLayoutManager tclm;
  73. RowPainter(TableContentLayoutManager tclm, LayoutContext layoutContext) {
  74. this.tclm = tclm;
  75. this.layoutContext = layoutContext;
  76. this.colCount = tclm.getColumns().getColumnCount();
  77. this.primaryGridUnits = new PrimaryGridUnit[colCount];
  78. this.start = new int[colCount];
  79. this.end = new int[colCount];
  80. this.partBPD = new int[colCount];
  81. this.firstRowIndex = -1;
  82. Arrays.fill(end, -1);
  83. }
  84. int getAccumulatedBPD() {
  85. return currentRowOffset;
  86. }
  87. /**
  88. * Records the fragment of row represented by the given position. If it belongs to
  89. * another (grid) row than the current one, that latter is painted and flushed first.
  90. *
  91. * @param tcpos a position representing the row fragment
  92. */
  93. void handleTableContentPosition(TableContentPosition tcpos) {
  94. if (tcpos.row != currentRow && currentRow != null) {
  95. addAreasAndFlushRow(false);
  96. }
  97. if (log.isDebugEnabled()) {
  98. log.debug("===handleTableContentPosition(" + tcpos);
  99. }
  100. rowFO = tcpos.row.getTableRow();
  101. currentRow = tcpos.row;
  102. if (firstRowIndex < 0) {
  103. firstRowIndex = currentRow.getIndex();
  104. }
  105. Iterator partIter = tcpos.cellParts.iterator();
  106. //Iterate over all grid units in the current step
  107. while (partIter.hasNext()) {
  108. CellPart cellPart = (CellPart)partIter.next();
  109. if (log.isDebugEnabled()) {
  110. log.debug(">" + cellPart);
  111. }
  112. int colIndex = cellPart.pgu.getStartCol();
  113. if (primaryGridUnits[colIndex] != cellPart.pgu) {
  114. if (primaryGridUnits[colIndex] != null) {
  115. log.warn("Replacing GU in slot " + colIndex
  116. + ". Some content may not be painted.");
  117. }
  118. primaryGridUnits[colIndex] = cellPart.pgu;
  119. start[colIndex] = cellPart.start;
  120. end[colIndex] = cellPart.end;
  121. } else {
  122. if (cellPart.end < end[colIndex]) {
  123. throw new IllegalStateException("Internal Error: stepper problem");
  124. }
  125. end[colIndex] = cellPart.end;
  126. }
  127. }
  128. }
  129. /**
  130. * Create the areas corresponding to the last row. This method is called either
  131. * because the row is finished (all of the elements present on this row have been
  132. * added), or because this is the last row on the current page, and the part of it
  133. * lying on the current page must be drawn.
  134. *
  135. * @param forcedFlush true if the elements must be drawn even if the row isn't
  136. * finished yet (last row on the page), or if the row is the last of the current table
  137. * part
  138. */
  139. void addAreasAndFlushRow(boolean forcedFlush) {
  140. int actualRowHeight = 0;
  141. if (log.isDebugEnabled()) {
  142. log.debug("Remembering yoffset for row " + currentRow.getIndex() + ": "
  143. + currentRowOffset);
  144. }
  145. recordRowOffset(currentRow.getIndex(), currentRowOffset);
  146. for (int i = 0; i < primaryGridUnits.length; i++) {
  147. if ((primaryGridUnits[i] != null)
  148. && (forcedFlush || (end[i] == primaryGridUnits[i].getElements().size() - 1))) {
  149. actualRowHeight = Math.max(actualRowHeight, computeSpanHeight(
  150. primaryGridUnits[i], start[i], end[i], i));
  151. }
  152. }
  153. actualRowHeight += 2 * tclm.getTableLM().getHalfBorderSeparationBPD();
  154. //Add areas for row
  155. tclm.addRowBackgroundArea(rowFO, actualRowHeight, layoutContext.getRefIPD(),
  156. currentRowOffset);
  157. for (int i = 0; i < primaryGridUnits.length; i++) {
  158. GridUnit currentGU = currentRow.getGridUnit(i);
  159. if (!currentGU.isEmpty() && currentGU.getColSpanIndex() == 0
  160. && (forcedFlush || currentGU.isLastGridUnitRowSpan())) {
  161. addAreasForCell(currentGU.getPrimary(), start[i], end[i], currentRow, partBPD[i],
  162. actualRowHeight);
  163. primaryGridUnits[i] = null;
  164. start[i] = 0;
  165. end[i] = -1;
  166. partBPD[i] = 0;
  167. }
  168. }
  169. currentRowOffset += actualRowHeight;
  170. if (forcedFlush) {
  171. // Either the end of the page is reached, then this was the last call of this
  172. // method and we no longer care about currentRow; or the end of a table-part
  173. // (header, footer, body) has been reached, and the next row will anyway be
  174. // different from the current one, and this is unnecessary to recall this
  175. // method in the first lines of handleTableContentPosition, so we may reset
  176. // the following variables
  177. currentRow = null;
  178. firstRowIndex = -1;
  179. rowOffsets.clear();
  180. }
  181. }
  182. /**
  183. * Computes the total height of the part of the given cell spanning on the current
  184. * active row, including borders and paddings. The bpd is also stored in partBPD, and
  185. * it is ensured that the cell's or row's explicit height is respected.
  186. *
  187. * @param pgu primary grid unit corresponding to the cell
  188. * @param start index of the first element of the cell occuring on the current page
  189. * @param end index of the last element of the cell occuring on the current page
  190. * @param columnIndex column index of the cell
  191. * @param bodyType {@link TableRowIterator#HEADER}, {@link TableRowIterator#FOOTER}, or
  192. * {@link TableRowIterator#BODY}
  193. * @return the cell's height
  194. */
  195. private int computeSpanHeight(PrimaryGridUnit pgu, int start, int end, int columnIndex) {
  196. if (log.isTraceEnabled()) {
  197. log.trace("getting len for " + columnIndex + " "
  198. + start + "-" + end);
  199. }
  200. int actualStart = start;
  201. // Skip from the content length calculation glues and penalties occuring at the
  202. // beginning of the page
  203. while (actualStart <= end && !((KnuthElement)pgu.getElements().get(actualStart)).isBox()) {
  204. actualStart++;
  205. }
  206. int len = ElementListUtils.calcContentLength(
  207. pgu.getElements(), actualStart, end);
  208. KnuthElement el = (KnuthElement)pgu.getElements().get(end);
  209. if (el.isPenalty()) {
  210. len += el.getW();
  211. }
  212. partBPD[columnIndex] = len;
  213. if (log.isTraceEnabled()) {
  214. log.trace("len of part: " + len);
  215. }
  216. if (start == 0) {
  217. LengthRangeProperty bpd = pgu.getCell()
  218. .getBlockProgressionDimension();
  219. if (!bpd.getMinimum(tclm.getTableLM()).isAuto()) {
  220. int min = bpd.getMinimum(tclm.getTableLM())
  221. .getLength().getValue(tclm.getTableLM());
  222. if (min > 0) {
  223. len = Math.max(len, min);
  224. }
  225. }
  226. if (!bpd.getOptimum(tclm.getTableLM()).isAuto()) {
  227. int opt = bpd.getOptimum(tclm.getTableLM())
  228. .getLength().getValue(tclm.getTableLM());
  229. if (opt > 0) {
  230. len = Math.max(len, opt);
  231. }
  232. }
  233. if (pgu.getRow() != null) {
  234. bpd = pgu.getRow().getBlockProgressionDimension();
  235. if (!bpd.getMinimum(tclm.getTableLM()).isAuto()) {
  236. int min = bpd.getMinimum(tclm.getTableLM()).getLength()
  237. .getValue(tclm.getTableLM());
  238. if (min > 0) {
  239. len = Math.max(len, min);
  240. }
  241. }
  242. }
  243. }
  244. // Add the padding if any
  245. len += pgu.getBorders()
  246. .getPaddingBefore(false, pgu.getCellLM());
  247. len += pgu.getBorders()
  248. .getPaddingAfter(false, pgu.getCellLM());
  249. //Now add the borders to the contentLength
  250. if (tclm.isSeparateBorderModel()) {
  251. len += pgu.getBorders().getBorderBeforeWidth(false);
  252. len += pgu.getBorders().getBorderAfterWidth(false);
  253. } else {
  254. len += pgu.getHalfMaxBeforeBorderWidth();
  255. len += pgu.getHalfMaxAfterBorderWidth();
  256. }
  257. int cellOffset = getRowOffset(Math.max(pgu.getStartRow(), firstRowIndex));
  258. len -= currentRowOffset - cellOffset;
  259. return len;
  260. }
  261. private void addAreasForCell(PrimaryGridUnit pgu, int startPos, int endPos,
  262. EffRow row, int contentHeight, int rowHeight) {
  263. //Determine the first row in this sequence
  264. int startRowIndex = Math.max(pgu.getStartRow(), firstRowIndex);
  265. int currentRowIndex = currentRow.getIndex();
  266. // In collapsing-border model, if the cell spans over several columns/rows then
  267. // dedicated areas will be created for each grid unit to hold the corresponding
  268. // borders. For that we need to know the height of each grid unit, that is of each
  269. // grid row spanned over by the cell
  270. int[] spannedGridRowHeights = null;
  271. if (!tclm.getTableLM().getTable().isSeparateBorderModel() && pgu.hasSpanning()) {
  272. spannedGridRowHeights = new int[currentRowIndex - startRowIndex + 1];
  273. int prevOffset = getRowOffset(startRowIndex);
  274. for (int i = 0; i < currentRowIndex - startRowIndex; i++) {
  275. int newOffset = getRowOffset(startRowIndex + i + 1);
  276. spannedGridRowHeights[i] = newOffset - prevOffset;
  277. prevOffset = newOffset;
  278. }
  279. spannedGridRowHeights[currentRowIndex - startRowIndex] = rowHeight;
  280. }
  281. int cellOffset = getRowOffset(startRowIndex);
  282. int effCellHeight = rowHeight;
  283. effCellHeight += currentRowOffset - cellOffset;
  284. if (log.isDebugEnabled()) {
  285. log.debug("Creating area for cell:");
  286. log.debug(" current row: " + row.getIndex());
  287. log.debug(" start row: " + pgu.getStartRow() + " " + currentRowOffset + " "
  288. + cellOffset);
  289. log.debug(" contentHeight: " + contentHeight + " rowHeight=" + rowHeight
  290. + " effCellHeight=" + effCellHeight);
  291. }
  292. TableCellLayoutManager cellLM = pgu.getCellLM();
  293. cellLM.setXOffset(tclm.getXOffsetOfGridUnit(pgu));
  294. cellLM.setYOffset(cellOffset);
  295. cellLM.setContentHeight(contentHeight);
  296. cellLM.setRowHeight(effCellHeight);
  297. //cellLM.setRowHeight(row.getHeight().opt);
  298. int prevBreak = ElementListUtils.determinePreviousBreak(pgu.getElements(), startPos);
  299. if (endPos >= 0) {
  300. SpaceResolver.performConditionalsNotification(pgu.getElements(),
  301. startPos, endPos, prevBreak);
  302. }
  303. cellLM.addAreas(new KnuthPossPosIter(pgu.getElements(), startPos, endPos + 1),
  304. layoutContext, spannedGridRowHeights, startRowIndex - pgu.getStartRow(),
  305. currentRowIndex - pgu.getStartRow() + 1);
  306. }
  307. /**
  308. * Records the y-offset of the row with the given index.
  309. *
  310. * @param rowIndex index of the row
  311. * @param offset y-offset of the row on the page
  312. */
  313. private void recordRowOffset(int rowIndex, int offset) {
  314. /*
  315. * In some very rare cases a row may be skipped. See for example Bugzilla #43633:
  316. * in a two-column table, a row contains a row-spanning cell and a missing cell.
  317. * In TableStepper#goToNextRowIfCurrentFinished this row will immediately be
  318. * considered as finished, since it contains no cell ending on this row. Thus no
  319. * TableContentPosition will be created for this row. Thus its index will never be
  320. * recorded by the #handleTableContentPosition method.
  321. *
  322. * The offset of such a row is the same as the next non-empty row. It's needed
  323. * to correctly offset blocks for cells starting on this row. Hence the loop
  324. * below.
  325. */
  326. for (int i = rowOffsets.size(); i <= rowIndex - firstRowIndex; i++) {
  327. rowOffsets.add(new Integer(offset));
  328. }
  329. }
  330. /**
  331. * Returns the offset of the row with the given index.
  332. *
  333. * @param rowIndex index of the row
  334. * @return its y-offset on the page
  335. */
  336. private int getRowOffset(int rowIndex) {
  337. return ((Integer) rowOffsets.get(rowIndex - firstRowIndex)).intValue();
  338. }
  339. }