1 17 18 19 20 package org.apache.fop.layoutmgr.table; 21 22 import java.util.Arrays ; 23 import java.util.LinkedList ; 24 import java.util.List ; 25 26 import org.apache.commons.logging.Log; 27 import org.apache.commons.logging.LogFactory; 28 import org.apache.fop.fo.Constants; 29 import org.apache.fop.fo.FONode; 30 import org.apache.fop.fo.flow.TableRow; 31 import org.apache.fop.layoutmgr.BreakElement; 32 import org.apache.fop.layoutmgr.ElementListUtils; 33 import org.apache.fop.layoutmgr.KnuthBox; 34 import org.apache.fop.layoutmgr.KnuthElement; 35 import org.apache.fop.layoutmgr.KnuthPenalty; 36 import org.apache.fop.layoutmgr.LayoutContext; 37 import org.apache.fop.layoutmgr.table.TableContentLayoutManager.GridUnitPart; 38 import org.apache.fop.layoutmgr.table.TableContentLayoutManager.TableContentPosition; 39 import org.apache.fop.layoutmgr.table.TableContentLayoutManager.TableHFPenaltyPosition; 40 41 44 public class TableStepper { 45 46 47 private static Log log = LogFactory.getLog(TableStepper.class); 48 49 private TableContentLayoutManager tclm; 50 51 private EffRow[] rowGroup; 52 private int totalHeight; 53 private int activeRow; 54 private List [] elementLists; 55 private int[] startRow; 56 private int[] start; 57 private int[] end; 58 private int[] widths; 59 private int[] baseWidth; 60 private int[] borderBefore; 61 private int[] paddingBefore; 62 private int[] borderAfter; 63 private int[] paddingAfter; 64 private boolean rowBacktrackForLastStep; 65 private boolean skippedStep; 66 private boolean[] keepWithNextSignals; 67 private boolean[] forcedBreaks; 68 private int lastMaxPenalty; 69 70 74 public TableStepper(TableContentLayoutManager tclm) { 75 this.tclm = tclm; 76 } 77 78 private void setup(int columnCount) { 79 this.activeRow = 0; 80 elementLists = new List [columnCount]; 81 startRow = new int[columnCount]; 82 start = new int[columnCount]; 83 end = new int[columnCount]; 84 widths = new int[columnCount]; 85 baseWidth = new int[columnCount]; 86 borderBefore = new int[columnCount]; 87 paddingBefore = new int[columnCount]; 88 borderAfter = new int[columnCount]; 89 paddingAfter = new int[columnCount]; 90 keepWithNextSignals = new boolean[columnCount]; 91 forcedBreaks = new boolean[columnCount]; 92 Arrays.fill(end, -1); 93 } 94 95 private void clearBreakCondition() { 96 Arrays.fill(forcedBreaks, false); 97 } 98 99 private boolean isBreakCondition() { 100 for (int i = 0; i < forcedBreaks.length; i++) { 101 if (forcedBreaks[i]) { 102 return true; 103 } 104 } 105 return false; 106 } 107 108 private EffRow getActiveRow() { 109 return rowGroup[activeRow]; 110 } 111 112 private GridUnit getActiveGridUnit(int column) { 113 return getActiveRow().safelyGetGridUnit(column); 114 } 115 116 private PrimaryGridUnit getActivePrimaryGridUnit(int column) { 117 GridUnit gu = getActiveGridUnit(column); 118 if (gu == null) { 119 return null; 120 } else { 121 return gu.getPrimary(); 122 } 123 } 124 125 private void calcTotalHeight() { 126 totalHeight = 0; 127 for (int i = 0; i < rowGroup.length; i++) { 128 totalHeight += rowGroup[i].getHeight().opt; 129 } 130 log.debug("totalHeight=" + totalHeight); 131 } 132 133 private int getMaxRemainingHeight() { 134 int maxW = 0; 135 if (!rowBacktrackForLastStep) { 136 for (int i = 0; i < widths.length; i++) { 137 if (elementLists[i] == null) { 138 continue; 139 } 140 if (end[i] == elementLists[i].size() - 1) { 141 continue; 142 } 143 GridUnit gu = getActiveGridUnit(i); 144 if (!gu.isLastGridUnitRowSpan()) { 145 continue; 146 } 147 int len = widths[i]; 148 if (len > 0) { 149 len += 2 * getTableLM().getHalfBorderSeparationBPD(); 150 len += borderBefore[i] + borderAfter[i]; 151 len += paddingBefore[i] + paddingAfter[i]; 152 } 153 int nominalHeight = rowGroup[activeRow].getHeight().opt; 154 for (int r = 0; r < gu.getRowSpanIndex(); r++) { 155 nominalHeight += rowGroup[activeRow - r - 1].getHeight().opt; 156 } 157 if (len == nominalHeight) { 158 maxW = 0; 160 break; 161 } 162 maxW = Math.max(maxW, nominalHeight - len); 163 } 164 } 165 for (int i = activeRow + 1; i < rowGroup.length; i++) { 166 maxW += rowGroup[i].getHeight().opt; 167 } 168 return maxW; 170 } 171 172 private void setupElementList(int column) { 173 GridUnit gu = getActiveGridUnit(column); 174 EffRow row = getActiveRow(); 175 if (gu == null || gu.isEmpty()) { 176 elementLists[column] = null; 177 start[column] = 0; 178 end[column] = -1; 179 widths[column] = 0; 180 startRow[column] = activeRow; 181 keepWithNextSignals[column] = false; 182 forcedBreaks[column] = false; 183 } else if (gu.isPrimary()) { 184 PrimaryGridUnit pgu = (PrimaryGridUnit)gu; 185 boolean makeBoxForWholeRow = false; 186 if (row.getExplicitHeight().min > 0) { 187 boolean contentsSmaller = ElementListUtils.removeLegalBreaks( 188 pgu.getElements(), row.getExplicitHeight()); 189 if (contentsSmaller) { 190 makeBoxForWholeRow = true; 191 } 192 } 193 if (pgu.isLastGridUnitRowSpan() && pgu.getRow() != null) { 194 makeBoxForWholeRow |= pgu.getRow().mustKeepTogether(); 195 makeBoxForWholeRow |= pgu.getTable().mustKeepTogether(); 196 } 197 if (makeBoxForWholeRow) { 198 List list = new java.util.ArrayList (1); 199 int height = row.getExplicitHeight().opt; 200 if (height == 0) { 201 height = row.getHeight().opt; 202 } 203 list.add(new KnuthBoxCellWithBPD(height, pgu)); 204 elementLists[column] = list; 205 } else { 206 elementLists[column] = new java.util.ArrayList (pgu.getElements()); 209 } 210 if (isSeparateBorderModel()) { 211 borderBefore[column] = pgu.getBorders().getBorderBeforeWidth(false); 212 } else { 213 borderBefore[column] = pgu.getBorders().getBorderBeforeWidth(false) / 2; 214 } 215 paddingBefore[column] = pgu.getBorders().getPaddingBefore(false, pgu.getCellLM()); 216 paddingAfter[column] = pgu.getBorders().getPaddingAfter(false, pgu.getCellLM()); 217 start[column] = 0; 218 end[column] = -1; 219 widths[column] = 0; 220 startRow[column] = activeRow; 221 keepWithNextSignals[column] = false; 222 forcedBreaks[column] = false; 223 } 224 } 225 226 private void initializeElementLists() { 227 for (int i = 0; i < start.length; i++) { 228 setupElementList(i); 229 } 230 } 231 232 240 public LinkedList getCombinedKnuthElementsForRowGroup( 241 LayoutContext context, 242 EffRow[] rowGroup, int maxColumnCount, int bodyType) { 243 this.rowGroup = rowGroup; 244 setup(maxColumnCount); 245 initializeElementLists(); 246 calcTotalHeight(); 247 248 boolean signalKeepWithNext = false; 249 int laststep = 0; 250 int step; 251 int addedBoxLen = 0; 252 TableContentPosition lastTCPos = null; 253 LinkedList returnList = new LinkedList (); 254 while ((step = getNextStep(laststep)) >= 0) { 255 int normalRow = activeRow; 256 if (rowBacktrackForLastStep) { 257 activeRow--; 260 } 261 int increase = step - laststep; 262 int penaltyLen = step + getMaxRemainingHeight() - totalHeight; 263 int boxLen = step - addedBoxLen - penaltyLen; 264 addedBoxLen += boxLen; 265 266 List gridUnitParts = new java.util.ArrayList (maxColumnCount); 268 for (int i = 0; i < start.length; i++) { 269 if (end[i] >= start[i]) { 270 PrimaryGridUnit pgu = rowGroup[startRow[i]].getGridUnit(i).getPrimary(); 271 if (start[i] == 0 && end[i] == 0 272 && elementLists[i].size() == 1 273 && elementLists[i].get(0) instanceof KnuthBoxCellWithBPD) { 274 gridUnitParts.add(new GridUnitPart(pgu, 276 0, pgu.getElements().size() - 1)); 277 } else { 278 gridUnitParts.add(new GridUnitPart(pgu, start[i], end[i])); 279 } 280 if (end[i] + 1 == elementLists[i].size()) { 281 if (pgu.getFlag(GridUnit.KEEP_WITH_NEXT_PENDING)) { 282 log.debug("PGU has pending keep-with-next"); 283 keepWithNextSignals[i] = true; 284 } 285 if (pgu.getRow() != null && pgu.getRow().mustKeepWithNext()) { 286 log.debug("table-row causes keep-with-next"); 287 keepWithNextSignals[i] = true; 288 } 289 } 290 if (start[i] == 0 && end[i] >= 0) { 291 if (pgu.getFlag(GridUnit.KEEP_WITH_PREVIOUS_PENDING)) { 292 log.debug("PGU has pending keep-with-previous"); 293 if (returnList.size() == 0) { 294 context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING); 295 } 296 } 297 if (pgu.getRow() != null && pgu.getRow().mustKeepWithPrevious()) { 298 log.debug("table-row causes keep-with-previous"); 299 if (returnList.size() == 0) { 300 context.setFlags(LayoutContext.KEEP_WITH_PREVIOUS_PENDING); 301 } 302 } 303 } 304 } 305 } 306 308 int effPenaltyLen = penaltyLen; 310 TableContentPosition tcpos = new TableContentPosition(getTableLM(), 311 gridUnitParts, rowGroup[normalRow]); 312 if (returnList.size() == 0) { 313 tcpos.setFlag(TableContentPosition.FIRST_IN_ROWGROUP, true); 314 } 315 lastTCPos = tcpos; 316 if (log.isDebugEnabled()) { 317 log.debug(" - backtrack=" + rowBacktrackForLastStep 318 + " - row=" + activeRow + " - " + tcpos); 319 } 320 returnList.add(new KnuthBox(boxLen, tcpos, false)); 321 TableHFPenaltyPosition penaltyPos = new TableHFPenaltyPosition(getTableLM()); 322 if (bodyType == TableRowIterator.BODY) { 323 if (!getTableLM().getTable().omitHeaderAtBreak()) { 324 effPenaltyLen += tclm.getHeaderNetHeight(); 325 penaltyPos.headerElements = tclm.getHeaderElements(); 326 } 327 if (!getTableLM().getTable().omitFooterAtBreak()) { 328 effPenaltyLen += tclm.getFooterNetHeight(); 329 penaltyPos.footerElements = tclm.getFooterElements(); 330 } 331 } 332 333 if (this.lastMaxPenalty != 0) { 336 penaltyPos.nestedPenaltyLength = this.lastMaxPenalty; 337 if (log.isDebugEnabled()) { 338 log.debug("Additional penalty length from table-cell break: " 339 + this.lastMaxPenalty); 340 } 341 } 342 effPenaltyLen += this.lastMaxPenalty; 343 344 int p = 0; 345 boolean allCellsHaveContributed = true; 346 signalKeepWithNext = false; 347 for (int i = 0; i < start.length; i++) { 348 if (start[i] == 0 && end[i] < 0 && elementLists[i] != null) { 349 allCellsHaveContributed = false; 350 } 351 signalKeepWithNext |= keepWithNextSignals[i]; 352 } 353 if (!allCellsHaveContributed) { 354 p = 900; } 359 if (signalKeepWithNext || getTableLM().mustKeepTogether()) { 360 p = KnuthPenalty.INFINITE; 361 } 362 if (skippedStep) { 363 p = KnuthPenalty.INFINITE; 364 } 367 if (isBreakCondition()) { 368 if (skippedStep) { 369 log.error("This is a conflict situation. The output may be wrong." 370 + " Please send your FO file to fop-dev@xmlgraphics.apache.org!"); 371 } 372 p = -KnuthPenalty.INFINITE; clearBreakCondition(); 374 } 375 returnList.add(new BreakElement(penaltyPos, effPenaltyLen, p, -1, context)); 376 377 if (log.isDebugEnabled()) { 378 log.debug("step=" + step + " (+" + increase + ")" 379 + " box=" + boxLen 380 + " penalty=" + penaltyLen 381 + " effPenalty=" + effPenaltyLen); 382 } 383 384 laststep = step; 385 if (rowBacktrackForLastStep) { 386 activeRow++; 388 } 389 } 390 if (signalKeepWithNext) { 391 context.setFlags(LayoutContext.KEEP_WITH_NEXT_PENDING); 394 } 395 if (isBreakCondition()) { 396 ((BreakElement)returnList.getLast()).setPenaltyValue(-KnuthPenalty.INFINITE); 397 } 398 if (lastTCPos != null) { 399 lastTCPos.setFlag(TableContentPosition.LAST_IN_ROWGROUP, true); 400 } 401 return returnList; 402 } 403 404 private int getNextStep(int lastStep) { 405 this.lastMaxPenalty = 0; 406 411 412 int[] backupWidths = new int[start.length]; 413 System.arraycopy(widths, 0, backupWidths, 0, backupWidths.length); 414 415 int rowPendingIndicator = 0; 417 for (int i = 0; i < start.length; i++) { 418 if (elementLists[i] == null) { 419 continue; 420 } 421 if (end[i] < elementLists[i].size()) { 422 start[i] = end[i] + 1; 423 if (end[i] + 1 < elementLists[i].size() 424 && getActiveGridUnit(i).isLastGridUnitRowSpan()) { 425 rowPendingIndicator++; 426 } 427 } else { 428 start[i] = -1; end[i] = -1; 430 } 431 } 432 433 if (rowPendingIndicator == 0) { 434 if (activeRow < rowGroup.length - 1) { 435 TableRow rowFO = getActiveRow().getTableRow(); 436 if (rowFO != null && rowFO.getBreakAfter() != Constants.EN_AUTO) { 437 log.warn(FONode.decorateWithContextInfo( 438 "break-after ignored on table-row because of row spanning " 439 + "in progress (See XSL 1.0, 7.19.1)", rowFO)); 440 } 441 activeRow++; 442 if (log.isDebugEnabled()) { 443 log.debug("===> new row: " + activeRow); 444 } 445 initializeElementLists(); 446 for (int i = 0; i < backupWidths.length; i++) { 447 if (end[i] < 0) { 448 backupWidths[i] = 0; 449 } 450 } 451 rowFO = getActiveRow().getTableRow(); 452 if (rowFO != null && rowFO.getBreakBefore() != Constants.EN_AUTO) { 453 log.warn(FONode.decorateWithContextInfo( 454 "break-before ignored on table-row because of row spanning " 455 + "in progress (See XSL 1.0, 7.19.2)", rowFO)); 456 } 457 } 458 } 459 460 int seqCount = 0; 462 for (int i = 0; i < start.length; i++) { 463 if (elementLists[i] == null) { 464 continue; 465 } 466 while (end[i] + 1 < elementLists[i].size()) { 467 end[i]++; 468 KnuthElement el = (KnuthElement)elementLists[i].get(end[i]); 469 if (el.isPenalty()) { 470 this.lastMaxPenalty = Math.max(this.lastMaxPenalty, el.getW()); 471 if (el.getP() <= -KnuthElement.INFINITE) { 472 log.debug("FORCED break encountered!"); 473 forcedBreaks[i] = true; 474 break; 475 } else if (el.getP() < KnuthElement.INFINITE) { 476 break; 478 } 479 } else if (el.isGlue()) { 480 if (end[i] > 0) { 481 KnuthElement prev = (KnuthElement)elementLists[i].get(end[i] - 1); 482 if (prev.isBox()) { 483 break; 485 } 486 } 487 widths[i] += el.getW(); 488 } else { 489 widths[i] += el.getW(); 490 } 491 } 492 if (end[i] < start[i]) { 493 widths[i] = backupWidths[i]; 494 } else { 495 seqCount++; 496 } 497 if (end[i] + 1 >= elementLists[i].size()) { 499 if (isSeparateBorderModel()) { 501 borderAfter[i] = getActivePrimaryGridUnit(i) 502 .getBorders().getBorderAfterWidth(false); 503 } else { 504 borderAfter[i] = getActivePrimaryGridUnit(i).getHalfMaxAfterBorderWidth(); 505 } 506 } else { 507 if (isSeparateBorderModel()) { 509 borderAfter[i] = getActivePrimaryGridUnit(i) 510 .getBorders().getBorderAfterWidth(false); 511 } else { 512 borderAfter[i] = getActivePrimaryGridUnit(i).getHalfMaxAfterBorderWidth(); 514 } 515 } 516 if (log.isTraceEnabled()) { 517 log.trace("borders before=" + borderBefore[i] + " after=" + borderAfter[i]); 518 log.trace("padding before=" + paddingBefore[i] + " after=" + paddingAfter[i]); 519 } 520 } 521 if (seqCount == 0) { 522 return -1; 523 } 524 525 int minStep = Integer.MAX_VALUE; 527 StringBuffer sb = new StringBuffer (); 528 for (int i = 0; i < widths.length; i++) { 529 baseWidth[i] = 0; 530 for (int prevRow = 0; prevRow < startRow[i]; prevRow++) { 531 baseWidth[i] += rowGroup[prevRow].getHeight().opt; 532 } 533 baseWidth[i] += 2 * getTableLM().getHalfBorderSeparationBPD(); 534 baseWidth[i] += borderBefore[i] + borderAfter[i]; 535 baseWidth[i] += paddingBefore[i] + paddingAfter[i]; 536 if (end[i] >= start[i]) { 537 int len = baseWidth[i] + widths[i]; 538 sb.append(len + " "); 539 minStep = Math.min(len, minStep); 540 } 541 } 542 if (log.isDebugEnabled()) { 543 log.debug("candidate steps: " + sb + " lastStep=" + lastStep); 544 } 545 546 551 552 rowBacktrackForLastStep = false; 555 skippedStep = false; 556 for (int i = 0; i < widths.length; i++) { 557 int len = baseWidth[i] + widths[i]; 558 if (len > minStep) { 559 widths[i] = backupWidths[i]; 560 end[i] = start[i] - 1; 561 if (baseWidth[i] + widths[i] > minStep) { 562 log.debug("minStep vs. border/padding increase conflict:"); 563 if (activeRow == 0) { 564 log.debug(" First row. Skip this step."); 565 skippedStep = true; 566 } else { 567 log.debug(" row-span situation: backtracking to last row"); 568 rowBacktrackForLastStep = true; 572 } 573 } 574 } 575 } 576 if (log.isDebugEnabled()) { 577 sb = new StringBuffer (); 578 for (int i = 0; i < widths.length; i++) { 579 if (end[i] >= start[i]) { 580 sb.append(i + ": " + start[i] + "-" + end[i] + "(" + widths[i] + "), "); 581 } else { 582 sb.append(i + ": skip, "); 583 } 584 } 585 log.debug(sb.toString()); 586 } 587 588 return minStep; 589 } 590 591 592 593 private boolean isSeparateBorderModel() { 594 return getTableLM().getTable().isSeparateBorderModel(); 595 } 596 597 598 private TableLayoutManager getTableLM() { 599 return this.tclm.getTableLM(); 600 } 601 602 private class KnuthBoxCellWithBPD extends KnuthBox { 603 604 private PrimaryGridUnit pgu; 605 606 public KnuthBoxCellWithBPD(int w, PrimaryGridUnit pgu) { 607 super(w, null, true); 608 this.pgu = pgu; 609 } 610 } 611 612 } 613 | Popular Tags |