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.

ActiveCell.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  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.List;
  20. import java.util.ListIterator;
  21. import org.apache.commons.logging.Log;
  22. import org.apache.commons.logging.LogFactory;
  23. import org.apache.fop.fo.Constants;
  24. import org.apache.fop.fo.flow.table.ConditionalBorder;
  25. import org.apache.fop.fo.flow.table.EffRow;
  26. import org.apache.fop.fo.flow.table.PrimaryGridUnit;
  27. import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
  28. import org.apache.fop.layoutmgr.ElementListUtils;
  29. import org.apache.fop.layoutmgr.KnuthBox;
  30. import org.apache.fop.layoutmgr.KnuthElement;
  31. import org.apache.fop.layoutmgr.KnuthPenalty;
  32. /**
  33. * A cell playing in the construction of steps for a row-group.
  34. */
  35. class ActiveCell {
  36. private static Log log = LogFactory.getLog(ActiveCell.class);
  37. private PrimaryGridUnit pgu;
  38. /** Knuth elements for this active cell. */
  39. private List elementList;
  40. /** Iterator over the Knuth element list. */
  41. private ListIterator knuthIter;
  42. /** Number of the row where the row-span ends, zero-based. */
  43. private int endRowIndex;
  44. /** Length of the Knuth elements not yet included in the steps. */
  45. private int remainingLength;
  46. /** Total length of this cell's content plus the lengths of the previous rows. */
  47. private int totalLength;
  48. /** Length of the Knuth elements already included in the steps. */
  49. private int includedLength;
  50. private int paddingBeforeNormal;
  51. private int paddingBeforeLeading;
  52. private int paddingAfterNormal;
  53. private int paddingAfterTrailing;
  54. private int bpBeforeNormal;
  55. private int bpBeforeLeading;
  56. private int bpAfterNormal;
  57. private int bpAfterTrailing;
  58. /** True if the next CellPart that will be created will be the last one for this cell. */
  59. private boolean lastCellPart;
  60. private boolean keepWithNextSignal;
  61. private int spanIndex = 0;
  62. private Step previousStep;
  63. private Step nextStep;
  64. /**
  65. * The step following nextStep. Computing it early allows to calculate
  66. * {@link Step#condBeforeContentLength}, thus to easily determine the remaining
  67. * length. That also helps for {@link #increaseCurrentStep(int)}.
  68. */
  69. private Step afterNextStep;
  70. /**
  71. * Auxiliary class to store all the informations related to a breaking step.
  72. */
  73. private static class Step {
  74. /** Index, in the list of Knuth elements, of the element starting this step. */
  75. private int start;
  76. /** Index, in the list of Knuth elements, of the element ending this step. */
  77. private int end;
  78. /** Length of the Knuth elements up to this step. */
  79. private int contentLength;
  80. /** Total length up to this step, including paddings and borders. */
  81. private int totalLength;
  82. /** Length of the penalty ending this step, if any. */
  83. private int penaltyLength;
  84. /**
  85. * One of {@link Constants#EN_AUTO}, {@link Constants#EN_COLUMN},
  86. * {@link Constants#EN_PAGE}, {@link Constants#EN_EVEN_PAGE},
  87. * {@link Constants#EN_ODD_PAGE}. Set to auto if the break isn't at a penalty
  88. * element.
  89. */
  90. private int breakClass;
  91. /**
  92. * Length of the optional content at the beginning of the step. That is, content
  93. * that will not appear if this step starts a new page.
  94. */
  95. private int condBeforeContentLength;
  96. Step(int contentLength) {
  97. this.contentLength = contentLength;
  98. this.end = -1;
  99. }
  100. Step(Step other) {
  101. set(other);
  102. }
  103. void set(Step other) {
  104. this.start = other.start;
  105. this.end = other.end;
  106. this.contentLength = other.contentLength;
  107. this.totalLength = other.totalLength;
  108. this.penaltyLength = other.penaltyLength;
  109. this.condBeforeContentLength = other.condBeforeContentLength;
  110. this.breakClass = other.breakClass;
  111. }
  112. /** {@inheritDoc} */
  113. public String toString() {
  114. return "Step: start=" + start + " end=" + end + " length=" + totalLength;
  115. }
  116. }
  117. ActiveCell(PrimaryGridUnit pgu, EffRow row, int rowIndex, int previousRowsLength,
  118. TableLayoutManager tableLM) {
  119. this.pgu = pgu;
  120. CommonBorderPaddingBackground bordersPaddings = pgu.getCell()
  121. .getCommonBorderPaddingBackground();
  122. TableCellLayoutManager cellLM = pgu.getCellLM();
  123. paddingBeforeNormal = bordersPaddings.getPaddingBefore(false, cellLM);
  124. paddingBeforeLeading = bordersPaddings.getPaddingBefore(true, cellLM);
  125. paddingAfterNormal = bordersPaddings.getPaddingAfter(false, cellLM);
  126. paddingAfterTrailing = bordersPaddings.getPaddingAfter(true, cellLM);
  127. bpBeforeNormal = paddingBeforeNormal
  128. + pgu.getBeforeBorderWidth(0, ConditionalBorder.NORMAL);
  129. bpBeforeLeading = paddingBeforeLeading
  130. + pgu.getBeforeBorderWidth(0, ConditionalBorder.REST);
  131. bpAfterNormal = paddingAfterNormal + pgu.getAfterBorderWidth(ConditionalBorder.NORMAL);
  132. bpAfterTrailing = paddingAfterTrailing + pgu.getAfterBorderWidth(0, ConditionalBorder.REST);
  133. boolean makeBoxForWholeRow = false;
  134. if (row.getExplicitHeight().min > 0) {
  135. boolean contentsSmaller = ElementListUtils.removeLegalBreaks(
  136. pgu.getElements(), row.getExplicitHeight());
  137. if (contentsSmaller) {
  138. makeBoxForWholeRow = true;
  139. }
  140. }
  141. if (makeBoxForWholeRow) {
  142. elementList = new java.util.ArrayList(1);
  143. int height = row.getHeight().opt;
  144. height -= 2 * tableLM.getHalfBorderSeparationBPD();
  145. height -= bpBeforeNormal + bpAfterNormal;
  146. elementList.add(new KnuthBoxCellWithBPD(height));
  147. } else {
  148. elementList = pgu.getElements();
  149. }
  150. knuthIter = elementList.listIterator();
  151. includedLength = -1; // Avoid troubles with cells having content of zero length
  152. totalLength = previousRowsLength + ElementListUtils.calcContentLength(elementList);
  153. endRowIndex = rowIndex + pgu.getCell().getNumberRowsSpanned() - 1;
  154. keepWithNextSignal = false;
  155. remainingLength = totalLength - previousRowsLength;
  156. afterNextStep = new Step(previousRowsLength);
  157. previousStep = new Step(afterNextStep);
  158. gotoNextLegalBreak();
  159. nextStep = new Step(afterNextStep);
  160. if (afterNextStep.end < elementList.size() - 1) {
  161. gotoNextLegalBreak();
  162. }
  163. }
  164. PrimaryGridUnit getPrimaryGridUnit() {
  165. return pgu;
  166. }
  167. /**
  168. * Returns true if this cell ends on the given row.
  169. *
  170. * @param rowIndex index of a row in the row-group, zero-based
  171. * @return true if this cell ends on the given row
  172. */
  173. boolean endsOnRow(int rowIndex) {
  174. return rowIndex == endRowIndex;
  175. }
  176. /**
  177. * Returns the length of this cell's content not yet included in the steps, plus the
  178. * cell's borders and paddings if applicable.
  179. *
  180. * @return the remaining length, zero if the cell is finished
  181. */
  182. int getRemainingLength() {
  183. if (includedInLastStep() && (nextStep.end == elementList.size() - 1)) {
  184. // The cell is finished
  185. return 0;
  186. } else {
  187. return bpBeforeLeading + remainingLength + bpAfterNormal;
  188. }
  189. }
  190. private void gotoNextLegalBreak() {
  191. afterNextStep.penaltyLength = 0;
  192. afterNextStep.condBeforeContentLength = 0;
  193. afterNextStep.breakClass = Constants.EN_AUTO;
  194. boolean breakFound = false;
  195. boolean prevIsBox = false;
  196. boolean boxFound = false;
  197. while (!breakFound && knuthIter.hasNext()) {
  198. KnuthElement el = (KnuthElement) knuthIter.next();
  199. if (el.isPenalty()) {
  200. prevIsBox = false;
  201. if (el.getP() < KnuthElement.INFINITE) {
  202. // First legal break point
  203. breakFound = true;
  204. afterNextStep.penaltyLength = el.getW();
  205. KnuthPenalty p = (KnuthPenalty) el;
  206. if (p.isForcedBreak()) {
  207. afterNextStep.breakClass = p.getBreakClass();
  208. }
  209. }
  210. } else if (el.isGlue()) {
  211. if (prevIsBox) {
  212. // Second legal break point
  213. breakFound = true;
  214. } else {
  215. afterNextStep.contentLength += el.getW();
  216. if (!boxFound) {
  217. afterNextStep.condBeforeContentLength += el.getW();
  218. }
  219. }
  220. prevIsBox = false;
  221. } else {
  222. prevIsBox = true;
  223. boxFound = true;
  224. afterNextStep.contentLength += el.getW();
  225. }
  226. }
  227. afterNextStep.end = knuthIter.nextIndex() - 1;
  228. afterNextStep.totalLength = bpBeforeNormal
  229. + afterNextStep.contentLength + afterNextStep.penaltyLength
  230. + bpAfterTrailing;
  231. }
  232. /**
  233. * Returns the minimal step that is needed for this cell to contribute some content.
  234. *
  235. * @return the step for this cell's first legal break
  236. */
  237. int getFirstStep() {
  238. log.debug(this + ": min first step = " + nextStep.totalLength);
  239. return nextStep.totalLength;
  240. }
  241. /**
  242. * Returns the last step for this cell.
  243. *
  244. * @return the step including all of the cell's content plus the normal borders and paddings
  245. */
  246. int getLastStep() {
  247. assert nextStep.end == elementList.size() - 1;
  248. assert nextStep.contentLength == totalLength && nextStep.penaltyLength == 0;
  249. int lastStep = bpBeforeNormal + totalLength + bpAfterNormal;
  250. log.debug(this + ": last step = " + lastStep);
  251. return lastStep;
  252. }
  253. /**
  254. * Increases the next step up to the given limit.
  255. *
  256. * @param limit the length up to which the next step is allowed to increase
  257. * @see #signalRowFirstStep(int)
  258. * @see #signalRowLastStep(int)
  259. */
  260. private void increaseCurrentStep(int limit) {
  261. if (nextStep.end < elementList.size() - 1) {
  262. while (afterNextStep.totalLength <= limit && nextStep.breakClass == Constants.EN_AUTO) {
  263. nextStep.set(afterNextStep);
  264. if (afterNextStep.end >= elementList.size() - 1) {
  265. break;
  266. }
  267. gotoNextLegalBreak();
  268. }
  269. }
  270. }
  271. /**
  272. * Gets the selected first step for the current row. If this cell's first step is
  273. * smaller, then it may be able to add some more of its content, since there will be
  274. * no break before the given step anyway.
  275. *
  276. * @param firstStep the current row's first step
  277. */
  278. void signalRowFirstStep(int firstStep) {
  279. increaseCurrentStep(firstStep);
  280. if (log.isTraceEnabled()) {
  281. log.trace(this + ": first step increased to " + nextStep.totalLength);
  282. }
  283. }
  284. /** See {@link #signalRowFirstStep(int)}. */
  285. void signalRowLastStep(int lastStep) {
  286. increaseCurrentStep(lastStep);
  287. if (log.isTraceEnabled()) {
  288. log.trace(this + ": next step increased to " + nextStep.totalLength);
  289. }
  290. }
  291. /**
  292. * Returns the total length up to the next legal break, not yet included in the steps.
  293. *
  294. * @return the total length up to the next legal break (-1 signals no further step)
  295. */
  296. int getNextStep() {
  297. if (includedInLastStep()) {
  298. previousStep.set(nextStep);
  299. if (nextStep.end >= elementList.size() - 1) {
  300. nextStep.start = elementList.size();
  301. return -1;
  302. } else {
  303. nextStep.set(afterNextStep);
  304. nextStep.start = previousStep.end + 1;
  305. afterNextStep.start = nextStep.start;
  306. if (afterNextStep.end < elementList.size() - 1) {
  307. gotoNextLegalBreak();
  308. }
  309. }
  310. }
  311. return nextStep.totalLength;
  312. }
  313. private boolean includedInLastStep() {
  314. return includedLength == nextStep.contentLength;
  315. }
  316. /**
  317. * Signals the length of the chosen next step, so that this cell determines whether
  318. * its own step may be included or not.
  319. *
  320. * @param minStep length of the chosen next step
  321. * @return the break class of the step, if any. One of {@link Constants#EN_AUTO},
  322. * {@link Constants#EN_COLUMN}, {@link Constants#EN_PAGE},
  323. * {@link Constants#EN_EVEN_PAGE}, {@link Constants#EN_ODD_PAGE}. EN_AUTO if this
  324. * cell's step is not included in the next step.
  325. */
  326. int signalNextStep(int minStep) {
  327. if (nextStep.totalLength <= minStep) {
  328. includedLength = nextStep.contentLength;
  329. remainingLength = totalLength - includedLength - afterNextStep.condBeforeContentLength;
  330. return nextStep.breakClass;
  331. } else {
  332. return Constants.EN_AUTO;
  333. }
  334. }
  335. /**
  336. * Receives indication that the next row is about to start, and that (collapse)
  337. * borders must be updated accordingly.
  338. */
  339. void nextRowStarts() {
  340. spanIndex++;
  341. // Subtract the old value of bpAfterTrailing...
  342. nextStep.totalLength -= bpAfterTrailing;
  343. afterNextStep.totalLength -= bpAfterTrailing;
  344. bpAfterTrailing = paddingAfterTrailing
  345. + pgu.getAfterBorderWidth(spanIndex, ConditionalBorder.REST);
  346. // ... and add the new one
  347. nextStep.totalLength += bpAfterTrailing;
  348. afterNextStep.totalLength += bpAfterTrailing;
  349. // TODO if the new after border is greater than the previous one the next step may
  350. // increase further than the row's first step, which can lead to wrong output in
  351. // some cases
  352. }
  353. /**
  354. * Receives indication that the current row is ending, and that (collapse) borders
  355. * must be updated accordingly.
  356. *
  357. * @param rowIndex the index of the ending row
  358. */
  359. void endRow(int rowIndex) {
  360. if (endsOnRow(rowIndex)) {
  361. bpAfterTrailing = paddingAfterNormal
  362. + pgu.getAfterBorderWidth(ConditionalBorder.LEADING_TRAILING);
  363. lastCellPart = true;
  364. } else {
  365. bpBeforeLeading = paddingBeforeLeading
  366. + pgu.getBeforeBorderWidth(spanIndex + 1, ConditionalBorder.REST);
  367. }
  368. }
  369. /**
  370. * Returns true if this cell would be finished after the given step. That is, it would
  371. * be included in the step and the end of its content would be reached.
  372. *
  373. * @param step the next step
  374. * @return true if this cell finishes at the given step
  375. */
  376. boolean finishes(int step) {
  377. return nextStep.totalLength <= step && (nextStep.end == elementList.size() - 1);
  378. }
  379. /**
  380. * Creates and returns a CellPart instance for the content of this cell which
  381. * is included in the next step.
  382. *
  383. * @return a CellPart instance
  384. */
  385. CellPart createCellPart() {
  386. if (nextStep.end + 1 == elementList.size()) {
  387. keepWithNextSignal = pgu.mustKeepWithNext();
  388. // TODO if keep-with-next is set on the row, must every cell of the row
  389. // contribute some content from children blocks?
  390. // see http://mail-archives.apache.org/mod_mbox/xmlgraphics-fop-dev/200802.mbox/
  391. // %3c47BDA379.4050606@anyware-tech.com%3e
  392. // Assuming no, but if yes the following code should enable this behaviour
  393. // if (pgu.getRow() != null && pgu.getRow().mustKeepWithNext()) {
  394. // keepWithNextSignal = true;
  395. // }
  396. }
  397. int bpBeforeFirst;
  398. if (nextStep.start == 0) {
  399. bpBeforeFirst = pgu.getBeforeBorderWidth(0, ConditionalBorder.LEADING_TRAILING)
  400. + paddingBeforeNormal;
  401. } else {
  402. bpBeforeFirst = bpBeforeLeading;
  403. }
  404. int length = nextStep.contentLength - nextStep.condBeforeContentLength
  405. - previousStep.contentLength;
  406. if (!includedInLastStep() || nextStep.start == elementList.size()) {
  407. return new CellPart(pgu, nextStep.start, previousStep.end, lastCellPart,
  408. 0, 0, previousStep.penaltyLength,
  409. bpBeforeNormal, bpBeforeFirst, bpAfterNormal, bpAfterTrailing);
  410. } else if (nextStep.start == 0 && nextStep.end == 0
  411. && elementList.size() == 1
  412. && elementList.get(0) instanceof KnuthBoxCellWithBPD) {
  413. //Special case: Cell with fixed BPD
  414. return new CellPart(pgu, 0, pgu.getElements().size() - 1, lastCellPart,
  415. nextStep.condBeforeContentLength, length, nextStep.penaltyLength,
  416. bpBeforeNormal, bpBeforeFirst, bpAfterNormal, bpAfterTrailing);
  417. } else {
  418. return new CellPart(pgu, nextStep.start, nextStep.end, lastCellPart,
  419. nextStep.condBeforeContentLength, length, nextStep.penaltyLength,
  420. bpBeforeNormal, bpBeforeFirst, bpAfterNormal, bpAfterTrailing);
  421. }
  422. }
  423. boolean keepWithNextSignal() {
  424. return keepWithNextSignal;
  425. }
  426. /** {@inheritDoc} */
  427. public String toString() {
  428. return "Cell " + (pgu.getRowIndex() + 1) + "." + (pgu.getColIndex() + 1);
  429. }
  430. /**
  431. * Marker class denoting table cells fitting in just one box (no legal break inside).
  432. */
  433. private static class KnuthBoxCellWithBPD extends KnuthBox {
  434. public KnuthBoxCellWithBPD(int w) {
  435. super(w, null, true);
  436. }
  437. }
  438. }