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