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.

CollapsingBorderResolver.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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.fo.flow.table;
  19. import java.util.ArrayList;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
  23. import org.apache.fop.layoutmgr.table.CollapsingBorderModel;
  24. /**
  25. * A class that implements the border-collapsing model.
  26. */
  27. class CollapsingBorderResolver implements BorderResolver {
  28. private Table table;
  29. private CollapsingBorderModel collapsingBorderModel;
  30. /**
  31. * The previously registered row, either in the header or the body(-ies), but not in
  32. * the footer (handled separately).
  33. */
  34. private List/*<GridUnit>*/ previousRow;
  35. private boolean firstInTable;
  36. private List/*<GridUnit>*/ footerFirstRow;
  37. /** The last currently registered footer row. */
  38. private List/*<GridUnit>*/ footerLastRow;
  39. private Resolver delegate;
  40. // Re-use the same ResolverInBody for every table-body
  41. // Important to properly handle firstInBody!!
  42. private Resolver resolverInBody = new ResolverInBody();
  43. private Resolver resolverInFooter;
  44. private List/*<ConditionalBorder>*/ leadingBorders;
  45. private List/*<ConditionalBorder>*/ trailingBorders;
  46. /* TODO Temporary hack for resolved borders in header */
  47. /* Currently the normal border is always used. */
  48. private List/*<GridUnit>*/ headerLastRow = null;
  49. /* End of temporary hack */
  50. /**
  51. * Base class for delegate resolvers. Implementation of the State design pattern: the
  52. * treatment differs slightly whether we are in the table's header, footer or body. To
  53. * avoid complicated if statements, specialised delegate resolvers will be used
  54. * instead.
  55. */
  56. private abstract class Resolver {
  57. protected TablePart tablePart;
  58. protected boolean firstInPart;
  59. private BorderSpecification borderStartTableAndBody;
  60. private BorderSpecification borderEndTableAndBody;
  61. /**
  62. * Integrates border-before specified on the table and its column.
  63. *
  64. * @param row the first row of the table (in the header, or in the body if the
  65. * table has no header)
  66. * @param withNormal
  67. * @param withLeadingTrailing
  68. * @param withRest
  69. */
  70. void resolveBordersFirstRowInTable(List/*<GridUnit>*/ row, boolean withNormal,
  71. boolean withLeadingTrailing, boolean withRest) {
  72. assert firstInTable;
  73. for (int i = 0; i < row.size(); i++) {
  74. TableColumn column = table.getColumn(i);
  75. ((GridUnit) row.get(i)).integrateBorderSegment(
  76. CommonBorderPaddingBackground.BEFORE, column, withNormal,
  77. withLeadingTrailing, withRest);
  78. }
  79. firstInTable = false;
  80. }
  81. /**
  82. * Resolves border-after for the first row, border-before for the second one.
  83. *
  84. * @param rowBefore
  85. * @param rowAfter
  86. */
  87. void resolveBordersBetweenRows(List/*<GridUnit>*/ rowBefore, List/*<GridUnit>*/ rowAfter) {
  88. assert rowBefore != null && rowAfter != null;
  89. for (int i = 0; i < rowAfter.size(); i++) {
  90. GridUnit gu = (GridUnit) rowAfter.get(i);
  91. if (gu.getRowSpanIndex() == 0) {
  92. GridUnit beforeGU = (GridUnit) rowBefore.get(i);
  93. gu.resolveBorder(beforeGU, CommonBorderPaddingBackground.BEFORE);
  94. }
  95. }
  96. }
  97. /** Integrates the border-after of the part. */
  98. void resolveBordersLastRowInPart(List/*<GridUnit>*/ row, boolean withNormal,
  99. boolean withLeadingTrailing, boolean withRest) {
  100. for (int i = 0; i < row.size(); i++) {
  101. ((GridUnit) row.get(i)).integrateBorderSegment(CommonBorderPaddingBackground.AFTER,
  102. tablePart, withNormal, withLeadingTrailing, withRest);
  103. }
  104. }
  105. /**
  106. * Integrates border-after specified on the table and its columns.
  107. *
  108. * @param row the last row of the footer, or of the last body if the table has no
  109. * footer
  110. * @param withNormal
  111. * @param withLeadingTrailing
  112. * @param withRest
  113. */
  114. void resolveBordersLastRowInTable(List/*<GridUnit>*/ row, boolean withNormal,
  115. boolean withLeadingTrailing, boolean withRest) {
  116. for (int i = 0; i < row.size(); i++) {
  117. TableColumn column = table.getColumn(i);
  118. ((GridUnit) row.get(i)).integrateBorderSegment(CommonBorderPaddingBackground.AFTER,
  119. column, withNormal, withLeadingTrailing, withRest);
  120. }
  121. }
  122. /**
  123. * Integrates either border-before specified on the table and its columns if the
  124. * table has no header, or border-after specified on the cells of the header's
  125. * last row. For the case the grid unit are at the top of a page.
  126. *
  127. * @param row
  128. */
  129. void integrateLeadingBorders(List/*<GridUnit>*/ row) {
  130. for (int i = 0; i < table.getNumberOfColumns(); i++) {
  131. GridUnit gu = (GridUnit) row.get(i);
  132. ConditionalBorder border = (ConditionalBorder) leadingBorders.get(i);
  133. gu.integrateCompetingBorder(CommonBorderPaddingBackground.BEFORE, border,
  134. false, true, true);
  135. }
  136. }
  137. /**
  138. * Integrates either border-after specified on the table and its columns if the
  139. * table has no footer, or border-before specified on the cells of the footer's
  140. * first row. For the case the grid unit are at the bottom of a page.
  141. *
  142. * @param row
  143. */
  144. void integrateTrailingBorders(List/*<GridUnit>*/ row) {
  145. for (int i = 0; i < table.getNumberOfColumns(); i++) {
  146. GridUnit gu = (GridUnit) row.get(i);
  147. ConditionalBorder border = (ConditionalBorder) trailingBorders.get(i);
  148. gu.integrateCompetingBorder(CommonBorderPaddingBackground.AFTER, border,
  149. false, true, true);
  150. }
  151. }
  152. void startPart(TablePart part) {
  153. tablePart = part;
  154. firstInPart = true;
  155. borderStartTableAndBody = collapsingBorderModel.determineWinner(table.borderStart,
  156. tablePart.borderStart);
  157. borderEndTableAndBody = collapsingBorderModel.determineWinner(table.borderEnd,
  158. tablePart.borderEnd);
  159. }
  160. /**
  161. * Resolves the applicable borders for the given row.
  162. * <ul>
  163. * <li>Integrates the border-before/after of the containing table-row if any;</li>
  164. * <li>Integrates the border-before of the containing part, if first row;</li>
  165. * <li>Resolves border-start/end between grid units.</li>
  166. * </ul>
  167. *
  168. * @param row the row being finished
  169. * @param container the containing element
  170. */
  171. void endRow(List/*<GridUnit>*/ row, TableCellContainer container) {
  172. BorderSpecification borderStart = borderStartTableAndBody;
  173. BorderSpecification borderEnd = borderEndTableAndBody;
  174. // Resolve before- and after-borders for the table-row
  175. if (container instanceof TableRow) {
  176. TableRow tableRow = (TableRow) container;
  177. for (Iterator iter = row.iterator(); iter.hasNext();) {
  178. GridUnit gu = (GridUnit) iter.next();
  179. boolean first = (gu.getRowSpanIndex() == 0);
  180. boolean last = gu.isLastGridUnitRowSpan();
  181. gu.integrateBorderSegment(CommonBorderPaddingBackground.BEFORE, tableRow,
  182. first, first, true);
  183. gu.integrateBorderSegment(CommonBorderPaddingBackground.AFTER, tableRow,
  184. last, last, true);
  185. }
  186. borderStart = collapsingBorderModel.determineWinner(borderStart,
  187. tableRow.borderStart);
  188. borderEnd = collapsingBorderModel.determineWinner(borderEnd,
  189. tableRow.borderEnd);
  190. }
  191. if (firstInPart) {
  192. // Integrate the border-before of the part
  193. for (int i = 0; i < row.size(); i++) {
  194. ((GridUnit) row.get(i)).integrateBorderSegment(
  195. CommonBorderPaddingBackground.BEFORE, tablePart, true, true, true);
  196. }
  197. firstInPart = false;
  198. }
  199. // Resolve start/end borders in the row
  200. Iterator guIter = row.iterator();
  201. GridUnit gu = (GridUnit) guIter.next();
  202. Iterator colIter = table.getColumns().iterator();
  203. TableColumn col = (TableColumn) colIter.next();
  204. gu.integrateBorderSegment(CommonBorderPaddingBackground.START, col);
  205. gu.integrateBorderSegment(CommonBorderPaddingBackground.START, borderStart);
  206. while (guIter.hasNext()) {
  207. GridUnit nextGU = (GridUnit) guIter.next();
  208. TableColumn nextCol = (TableColumn) colIter.next();
  209. if (gu.isLastGridUnitColSpan()) {
  210. gu.integrateBorderSegment(CommonBorderPaddingBackground.END, col);
  211. nextGU.integrateBorderSegment(CommonBorderPaddingBackground.START, nextCol);
  212. gu.resolveBorder(nextGU, CommonBorderPaddingBackground.END);
  213. }
  214. gu = nextGU;
  215. col = nextCol;
  216. }
  217. gu.integrateBorderSegment(CommonBorderPaddingBackground.END, col);
  218. gu.integrateBorderSegment(CommonBorderPaddingBackground.END, borderEnd);
  219. }
  220. void endPart() {
  221. resolveBordersLastRowInPart(previousRow, true, true, true);
  222. }
  223. abstract void endTable();
  224. }
  225. private class ResolverInHeader extends Resolver {
  226. void endRow(List/*<GridUnit>*/ row, TableCellContainer container) {
  227. super.endRow(row, container);
  228. if (previousRow != null) {
  229. resolveBordersBetweenRows(previousRow, row);
  230. } else {
  231. /*
  232. * This is a bit hacky...
  233. * The two only sensible values for border-before on the header's first row are:
  234. * - at the beginning of the table (normal case)
  235. * - if the header is repeated after each page break
  236. * To represent those values we (ab)use the normal and the rest fields of
  237. * ConditionalBorder. But strictly speaking this is not their purposes.
  238. */
  239. for (Iterator guIter = row.iterator(); guIter.hasNext();) {
  240. ConditionalBorder borderBefore = ((GridUnit) guIter.next()).borderBefore;
  241. borderBefore.leadingTrailing = borderBefore.normal;
  242. borderBefore.rest = borderBefore.normal;
  243. }
  244. resolveBordersFirstRowInTable(row, true, false, true);
  245. }
  246. previousRow = row;
  247. }
  248. void endPart() {
  249. super.endPart();
  250. leadingBorders = new ArrayList(table.getNumberOfColumns());
  251. /*
  252. * Another hack...
  253. * The border-after of a header is always the same. Leading and rest don't
  254. * apply to cells in the header since they are never broken. To ease
  255. * resolution we override the (normally unused) leadingTrailing and rest
  256. * fields of ConditionalBorder with the only sensible normal field. That way
  257. * grid units from the body will always resolve against the same, normal
  258. * header border.
  259. */
  260. for (Iterator guIter = previousRow.iterator(); guIter.hasNext();) {
  261. ConditionalBorder borderAfter = ((GridUnit) guIter.next()).borderAfter;
  262. borderAfter.leadingTrailing = borderAfter.normal;
  263. borderAfter.rest = borderAfter.normal;
  264. leadingBorders.add(borderAfter);
  265. }
  266. /* TODO Temporary hack for resolved borders in header */
  267. headerLastRow = previousRow;
  268. /* End of temporary hack */
  269. }
  270. void endTable() {
  271. throw new IllegalStateException();
  272. }
  273. }
  274. private class ResolverInFooter extends Resolver {
  275. void endRow(List/*<GridUnit>*/ row, TableCellContainer container) {
  276. super.endRow(row, container);
  277. if (footerFirstRow == null) {
  278. footerFirstRow = row;
  279. } else {
  280. // There is a previous row
  281. resolveBordersBetweenRows(footerLastRow, row);
  282. }
  283. footerLastRow = row;
  284. }
  285. void endPart() {
  286. resolveBordersLastRowInPart(footerLastRow, true, true, true);
  287. trailingBorders = new ArrayList(table.getNumberOfColumns());
  288. // See same method in ResolverInHeader for an explanation of the hack
  289. for (Iterator guIter = footerFirstRow.iterator(); guIter.hasNext();) {
  290. ConditionalBorder borderBefore = ((GridUnit) guIter.next()).borderBefore;
  291. borderBefore.leadingTrailing = borderBefore.normal;
  292. borderBefore.rest = borderBefore.normal;
  293. trailingBorders.add(borderBefore);
  294. }
  295. }
  296. void endTable() {
  297. // Resolve after/before border between the last row of table-body and the
  298. // first row of table-footer
  299. resolveBordersBetweenRows(previousRow, footerFirstRow);
  300. // See endRow method in ResolverInHeader for an explanation of the hack
  301. for (Iterator guIter = footerLastRow.iterator(); guIter.hasNext();) {
  302. ConditionalBorder borderAfter = ((GridUnit) guIter.next()).borderAfter;
  303. borderAfter.leadingTrailing = borderAfter.normal;
  304. borderAfter.rest = borderAfter.normal;
  305. }
  306. resolveBordersLastRowInTable(footerLastRow, true, false, true);
  307. }
  308. }
  309. private class ResolverInBody extends Resolver {
  310. private boolean firstInBody = true;
  311. void endRow(List/*<GridUnit>*/ row, TableCellContainer container) {
  312. super.endRow(row, container);
  313. if (firstInTable) {
  314. resolveBordersFirstRowInTable(row, true, true, true);
  315. } else {
  316. // Either there is a header, and then previousRow is set to the header's last row,
  317. // or this is not the first row in the body, and previousRow is not null
  318. resolveBordersBetweenRows(previousRow, row);
  319. integrateLeadingBorders(row);
  320. }
  321. integrateTrailingBorders(row);
  322. previousRow = row;
  323. if (firstInBody) {
  324. firstInBody = false;
  325. for (Iterator iter = row.iterator(); iter.hasNext();) {
  326. GridUnit gu = (GridUnit) iter.next();
  327. gu.borderBefore.leadingTrailing = gu.borderBefore.normal;
  328. }
  329. }
  330. }
  331. void endTable() {
  332. if (resolverInFooter != null) {
  333. resolverInFooter.endTable();
  334. } else {
  335. // Trailing and rest borders already resolved with integrateTrailingBorders
  336. resolveBordersLastRowInTable(previousRow, true, false, false);
  337. }
  338. for (Iterator iter = previousRow.iterator(); iter.hasNext();) {
  339. GridUnit gu = (GridUnit) iter.next();
  340. gu.borderAfter.leadingTrailing = gu.borderAfter.normal;
  341. }
  342. }
  343. }
  344. CollapsingBorderResolver(Table table) {
  345. this.table = table;
  346. collapsingBorderModel = CollapsingBorderModel.getBorderModelFor(table.getBorderCollapse());
  347. firstInTable = true;
  348. // Resolve before and after borders between the table and each table-column
  349. int index = 0;
  350. do {
  351. TableColumn col = table.getColumn(index);
  352. // See endRow method in ResolverInHeader for an explanation of the hack
  353. col.borderBefore.integrateSegment(table.borderBefore, true, false, true);
  354. col.borderBefore.leadingTrailing = col.borderBefore.rest;
  355. col.borderAfter.integrateSegment(table.borderAfter, true, false, true);
  356. col.borderAfter.leadingTrailing = col.borderAfter.rest;
  357. /*
  358. * TODO The border resolution must be done only once for each table column,
  359. * even if it's repeated; otherwise, re-resolving against the table's borders
  360. * will lead to null border specifications.
  361. *
  362. * Eventually table columns should probably be cloned instead.
  363. */
  364. index += col.getNumberColumnsRepeated();
  365. } while (index < table.getNumberOfColumns());
  366. }
  367. /** {@inheritDoc} */
  368. public void endRow(List/*<GridUnit>*/ row, TableCellContainer container) {
  369. delegate.endRow(row, container);
  370. }
  371. /** {@inheritDoc} */
  372. public void startPart(TablePart part) {
  373. if (part instanceof TableHeader) {
  374. delegate = new ResolverInHeader();
  375. } else {
  376. if (leadingBorders == null || table.omitHeaderAtBreak()) {
  377. // No header, leading borders determined by the table
  378. leadingBorders = new ArrayList(table.getNumberOfColumns());
  379. for (Iterator colIter = table.getColumns().iterator(); colIter.hasNext();) {
  380. ConditionalBorder border = ((TableColumn) colIter.next()).borderBefore;
  381. leadingBorders.add(border);
  382. }
  383. }
  384. if (part instanceof TableFooter) {
  385. resolverInFooter = new ResolverInFooter();
  386. delegate = resolverInFooter;
  387. } else {
  388. if (trailingBorders == null || table.omitFooterAtBreak()) {
  389. // No footer, trailing borders determined by the table
  390. trailingBorders = new ArrayList(table.getNumberOfColumns());
  391. for (Iterator colIter = table.getColumns().iterator(); colIter.hasNext();) {
  392. ConditionalBorder border = ((TableColumn) colIter.next()).borderAfter;
  393. trailingBorders.add(border);
  394. }
  395. }
  396. delegate = resolverInBody;
  397. }
  398. }
  399. delegate.startPart(part);
  400. }
  401. /** {@inheritDoc} */
  402. public void endPart() {
  403. delegate.endPart();
  404. }
  405. /** {@inheritDoc} */
  406. public void endTable() {
  407. delegate.endTable();
  408. delegate = null;
  409. /* TODO Temporary hack for resolved borders in header */
  410. if (headerLastRow != null) {
  411. for (Iterator iter = headerLastRow.iterator(); iter.hasNext();) {
  412. GridUnit gu = (GridUnit) iter.next();
  413. gu.borderAfter.leadingTrailing = gu.borderAfter.normal;
  414. }
  415. }
  416. if (footerLastRow != null) {
  417. for (Iterator iter = footerLastRow.iterator(); iter.hasNext();) {
  418. GridUnit gu = (GridUnit) iter.next();
  419. gu.borderAfter.leadingTrailing = gu.borderAfter.normal;
  420. }
  421. }
  422. /* End of temporary hack */
  423. }
  424. }