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

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