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 18KB

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