1 17 18 19 20 package org.apache.fop.layoutmgr; 21 22 import java.util.ArrayList ; 23 import java.util.LinkedList ; 24 import java.util.ListIterator ; 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.FObj; 31 import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition; 32 33 import org.apache.fop.traits.MinOptMax; 34 35 class PageBreakingAlgorithm extends BreakingAlgorithm { 36 37 38 private static Log log = LogFactory.getLog(PageBreakingAlgorithm.class); 39 40 private LayoutManager topLevelLM; 41 private PageSequenceLayoutManager.PageProvider pageProvider; 42 private PageBreakingLayoutListener layoutListener; 43 44 private LinkedList pageBreaks = null; 45 46 51 private ArrayList footnotesList = null; 52 53 private ArrayList lengthList = null; 54 55 private int totalFootnotesLength = 0; 56 61 private int insertedFootnotesLength = 0; 62 63 private boolean footnotesPending = false; 64 67 private boolean newFootnotes = false; 68 71 private int firstNewFootnoteIndex = 0; 72 73 private int footnoteListIndex = 0; 74 75 private int footnoteElementIndex = -1; 76 77 private int splitFootnoteDemerits = 5000; 79 private int deferredFootnoteDemerits = 10000; 81 private MinOptMax footnoteSeparatorLength = null; 82 83 private int storedPrevBreakIndex = -1; 87 private int storedBreakIndex = -1; 88 private boolean storedValue = false; 89 90 private boolean autoHeight = false; 92 93 private boolean favorSinglePart = false; 95 96 public PageBreakingAlgorithm(LayoutManager topLevelLM, 97 PageSequenceLayoutManager.PageProvider pageProvider, 98 PageBreakingLayoutListener layoutListener, 99 int alignment, int alignmentLast, 100 MinOptMax footnoteSeparatorLength, 101 boolean partOverflowRecovery, boolean autoHeight, 102 boolean favorSinglePart) { 103 super(alignment, alignmentLast, true, partOverflowRecovery, 0); 104 this.topLevelLM = topLevelLM; 105 this.pageProvider = pageProvider; 106 this.layoutListener = layoutListener; 107 best = new BestPageRecords(); 108 this.footnoteSeparatorLength = (MinOptMax) footnoteSeparatorLength.clone(); 109 if (footnoteSeparatorLength.min == footnoteSeparatorLength.max) { 111 footnoteSeparatorLength.max += 10000; 112 } 113 this.autoHeight = autoHeight; 114 this.favorSinglePart = favorSinglePart; 115 } 116 117 121 protected class KnuthPageNode extends KnuthNode { 122 123 124 public int totalFootnotes; 125 126 127 public int footnoteListIndex; 128 129 130 public int footnoteElementIndex; 131 132 public KnuthPageNode(int position, int line, int fitness, 133 int totalWidth, int totalStretch, int totalShrink, 134 int totalFootnotes, int footnoteListIndex, int footnoteElementIndex, 135 double adjustRatio, int availableShrink, int availableStretch, 136 int difference, double totalDemerits, KnuthNode previous) { 137 super(position, line, fitness, 138 totalWidth, totalStretch, totalShrink, 139 adjustRatio, availableShrink, availableStretch, 140 difference, totalDemerits, previous); 141 this.totalFootnotes = totalFootnotes; 142 this.footnoteListIndex = footnoteListIndex; 143 this.footnoteElementIndex = footnoteElementIndex; 144 } 145 146 } 147 148 152 protected class BestPageRecords extends BestRecords { 153 154 private int[] bestFootnotesLength = new int[4]; 155 private int[] bestFootnoteListIndex = new int[4]; 156 private int[] bestFootnoteElementIndex = new int[4]; 157 158 public void addRecord(double demerits, KnuthNode node, double adjust, 159 int availableShrink, int availableStretch, 160 int difference, int fitness) { 161 super.addRecord(demerits, node, adjust, 162 availableShrink, availableStretch, 163 difference, fitness); 164 bestFootnotesLength[fitness] = insertedFootnotesLength; 165 bestFootnoteListIndex[fitness] = footnoteListIndex; 166 bestFootnoteElementIndex[fitness] = footnoteElementIndex; 167 } 168 169 public int getFootnotesLength(int fitness) { 170 return bestFootnotesLength[fitness]; 171 } 172 173 public int getFootnoteListIndex(int fitness) { 174 return bestFootnoteListIndex[fitness]; 175 } 176 177 public int getFootnoteElementIndex(int fitness) { 178 return bestFootnoteElementIndex[fitness]; 179 } 180 } 181 182 protected void initialize() { 183 super.initialize(); 184 insertedFootnotesLength = 0; 185 footnoteListIndex = 0; 186 footnoteElementIndex = -1; 187 } 188 189 protected KnuthNode createNode(int position, int line, int fitness, 190 int totalWidth, int totalStretch, int totalShrink, 191 double adjustRatio, int availableShrink, int availableStretch, 192 int difference, double totalDemerits, KnuthNode previous) { 193 return new KnuthPageNode(position, line, fitness, 194 totalWidth, totalStretch, totalShrink, 195 insertedFootnotesLength, footnoteListIndex, footnoteElementIndex, 196 adjustRatio, availableShrink, availableStretch, 197 difference, totalDemerits, previous); 198 } 199 200 protected KnuthNode createNode(int position, int line, int fitness, 201 int totalWidth, int totalStretch, int totalShrink) { 202 return new KnuthPageNode(position, line, fitness, 203 totalWidth, totalStretch, totalShrink, 204 ((BestPageRecords) best).getFootnotesLength(fitness), 205 ((BestPageRecords) best).getFootnoteListIndex(fitness), 206 ((BestPageRecords) best).getFootnoteElementIndex(fitness), 207 best.getAdjust(fitness), best.getAvailableShrink(fitness), 208 best.getAvailableStretch(fitness), best.getDifference(fitness), 209 best.getDemerits(fitness), best.getNode(fitness)); 210 } 211 212 217 protected void handleBox(KnuthBox box) { 218 if (box instanceof KnuthBlockBox 219 && ((KnuthBlockBox) box).hasAnchors()) { 220 handleFootnotes(((KnuthBlockBox) box).getElementLists()); 221 if (!newFootnotes) { 222 newFootnotes = true; 223 firstNewFootnoteIndex = footnotesList.size() - 1; 224 } 225 } 226 } 227 228 234 private void handleFootnotes(LinkedList elementLists) { 235 if (!footnotesPending) { 237 footnotesPending = true; 238 footnotesList = new ArrayList (); 239 lengthList = new ArrayList (); 240 totalFootnotesLength = 0; 241 } 242 if (!newFootnotes) { 243 newFootnotes = true; 244 firstNewFootnoteIndex = footnotesList.size(); 245 } 246 247 ListIterator elementListsIterator = elementLists.listIterator(); 249 while (elementListsIterator.hasNext()) { 250 LinkedList noteList = (LinkedList ) elementListsIterator.next(); 251 252 SpaceResolver.resolveElementList(noteList); 255 256 int noteLength = 0; 257 footnotesList.add(noteList); 258 ListIterator noteListIterator = noteList.listIterator(); 259 while (noteListIterator.hasNext()) { 260 KnuthElement element = (KnuthElement) noteListIterator.next(); 261 if (element.isBox() || element.isGlue()) { 262 noteLength += element.getW(); 263 } 264 } 265 int prevLength = (lengthList.size() == 0 266 ? 0 267 : ((Integer ) lengthList.get(lengthList.size() - 1)).intValue()); 268 lengthList.add(new Integer (prevLength + noteLength)); 269 totalFootnotesLength += noteLength; 270 } 271 } 272 273 protected int restartFrom(KnuthNode restartingNode, int currentIndex) { 274 int returnValue = super.restartFrom(restartingNode, currentIndex); 275 newFootnotes = false; 276 if (footnotesPending) { 277 for (int j = currentIndex; j >= restartingNode.position; j--) { 280 KnuthElement resettedElement = getElement(j); 281 if (resettedElement instanceof KnuthBlockBox 282 && ((KnuthBlockBox) resettedElement).hasAnchors()) { 283 resetFootnotes(((KnuthBlockBox) resettedElement).getElementLists()); 284 } 285 } 286 } 287 return returnValue; 288 } 289 290 private void resetFootnotes(LinkedList elementLists) { 291 for (int i = 0; i < elementLists.size(); i++) { 292 LinkedList removedList = (LinkedList ) footnotesList.remove(footnotesList.size() - 1); 293 lengthList.remove(lengthList.size() - 1); 294 295 if (lengthList.size() > 0) { 297 totalFootnotesLength = ((Integer ) lengthList.get(lengthList.size() - 1)).intValue(); 298 } else { 299 totalFootnotesLength = 0; 300 } 301 } 302 if (footnotesList.size() == 0) { 304 footnotesPending = false; 305 } 306 } 307 308 protected void considerLegalBreak(KnuthElement element, int elementIdx) { 309 super.considerLegalBreak(element, elementIdx); 310 newFootnotes = false; 311 } 312 313 protected int computeDifference(KnuthNode activeNode, KnuthElement element, 314 int elementIndex) { 315 KnuthPageNode pageNode = (KnuthPageNode) activeNode; 316 int actualWidth = totalWidth - pageNode.totalWidth; 317 int footnoteSplit; 318 boolean canDeferOldFootnotes; 319 if (element.isPenalty()) { 320 actualWidth += element.getW(); 321 } 322 if (footnotesPending) { 323 int allFootnotes = totalFootnotesLength - pageNode.totalFootnotes; 325 if (allFootnotes > 0) { 326 actualWidth += footnoteSeparatorLength.opt; 329 if (actualWidth + allFootnotes <= getLineWidth()) { 330 actualWidth += allFootnotes; 333 insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes; 334 footnoteListIndex = footnotesList.size() - 1; 335 footnoteElementIndex = ((LinkedList ) footnotesList.get(footnoteListIndex)).size() - 1; 336 } else if (((canDeferOldFootnotes = checkCanDeferOldFootnotes(pageNode, elementIndex)) 337 || newFootnotes) 338 && (footnoteSplit = getFootnoteSplit(pageNode, getLineWidth() - actualWidth, 339 canDeferOldFootnotes)) > 0) { 340 actualWidth += footnoteSplit; 347 insertedFootnotesLength = pageNode.totalFootnotes + footnoteSplit; 348 } else { 351 actualWidth += allFootnotes; 357 insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes; 358 footnoteListIndex = footnotesList.size() - 1; 359 footnoteElementIndex = ((LinkedList ) footnotesList.get(footnoteListIndex)).size() - 1; 360 } 361 } else { 362 } 364 } else { 365 } 367 return getLineWidth(activeNode.line) - actualWidth; 368 } 369 370 376 private boolean checkCanDeferOldFootnotes(KnuthPageNode node, int contentElementIndex) { 377 return (noBreakBetween(node.position, contentElementIndex) 378 && deferredFootnotes(node.footnoteListIndex, node.footnoteElementIndex, node.totalFootnotes)); 379 } 380 381 388 private boolean noBreakBetween(int prevBreakIndex, int breakIndex) { 389 if (storedPrevBreakIndex != -1 396 && ((prevBreakIndex >= storedPrevBreakIndex 397 && breakIndex == storedBreakIndex 398 && storedValue) 399 || (prevBreakIndex <= storedPrevBreakIndex 400 && breakIndex >= storedBreakIndex 401 && !storedValue))) { 402 } else { 404 int index; 406 for (index = prevBreakIndex + 1; 408 !par.getElement(index).isBox(); 409 index++) { 410 } 412 for (; 414 index < breakIndex; 415 index++) { 416 if (par.getElement(index).isGlue() && par.getElement(index - 1).isBox() 417 || par.getElement(index).isPenalty() 418 && ((KnuthElement) par.getElement(index)).getP() < KnuthElement.INFINITE) { 419 break; 421 } 422 } 423 storedPrevBreakIndex = prevBreakIndex; 425 storedBreakIndex = breakIndex; 426 storedValue = (index == breakIndex); 427 } 428 return storedValue; 429 } 430 431 438 private boolean deferredFootnotes(int listIndex, int elementIndex, int length) { 439 return ((newFootnotes 440 && firstNewFootnoteIndex != 0 441 && (listIndex < firstNewFootnoteIndex - 1 442 || elementIndex < ((LinkedList ) footnotesList.get(listIndex)).size() - 1)) 443 || length < totalFootnotesLength); 444 } 445 446 452 private int getFootnoteSplit(KnuthPageNode activeNode, int availableLength, boolean canDeferOldFootnotes) { 453 return getFootnoteSplit(activeNode.footnoteListIndex, 454 activeNode.footnoteElementIndex, 455 activeNode.totalFootnotes, 456 availableLength, canDeferOldFootnotes); 457 } 458 459 467 private int getFootnoteSplit(int prevListIndex, int prevElementIndex, int prevLength, 468 int availableLength, boolean canDeferOldFootnotes) { 469 if (availableLength <= 0) { 470 return 0; 471 } else { 472 int splitLength = 0; 476 ListIterator noteListIterator = null; 477 KnuthElement element = null; 478 boolean somethingAdded = false; 479 480 int listIndex = prevListIndex; 483 int elementIndex = prevElementIndex; 484 if (elementIndex == ((LinkedList ) footnotesList.get(listIndex)).size() - 1) { 485 listIndex++; 486 elementIndex = 0; 487 } else { 488 elementIndex++; 489 } 490 491 if (footnotesList.size() - 1 > listIndex) { 493 if (!canDeferOldFootnotes 495 && newFootnotes 496 && firstNewFootnoteIndex > 0) { 497 splitLength = ((Integer ) lengthList.get(firstNewFootnoteIndex - 1)).intValue() 498 - prevLength; 499 listIndex = firstNewFootnoteIndex; 500 elementIndex = 0; 501 } 502 while (((Integer ) lengthList.get(listIndex)).intValue() - prevLength 504 <= availableLength) { 505 splitLength = ((Integer ) lengthList.get(listIndex)).intValue() 506 - prevLength; 507 somethingAdded = true; 508 listIndex++; 509 elementIndex = 0; 510 } 511 } 515 516 noteListIterator = ((LinkedList ) footnotesList.get(listIndex)).listIterator(elementIndex); 518 519 int prevSplitLength = 0; 520 int prevIndex = -1; 521 int index = -1; 522 523 while (!(somethingAdded && splitLength > availableLength)) { 524 if (!somethingAdded) { 525 somethingAdded = true; 526 } else { 527 prevSplitLength = splitLength; 528 prevIndex = index; 529 } 530 boolean bPrevIsBox = false; 532 while (noteListIterator.hasNext()) { 533 element = (KnuthElement) noteListIterator.next(); 538 if (element.isBox()) { 539 splitLength += element.getW(); 541 bPrevIsBox = true; 542 } else if (element.isGlue()) { 543 if (bPrevIsBox) { 545 index = noteListIterator.previousIndex(); 547 break; 548 } 549 bPrevIsBox = false; 550 splitLength += element.getW(); 551 } else { 552 if (element.getP() < KnuthElement.INFINITE) { 554 index = noteListIterator.previousIndex(); 556 break; 557 } 558 } 559 } 560 } 561 if (!somethingAdded) { 567 prevSplitLength = 0; 570 } else if (prevSplitLength > 0) { 571 footnoteListIndex = (prevIndex != -1) ? listIndex : listIndex - 1; 573 footnoteElementIndex = (prevIndex != -1) 574 ? prevIndex 575 : ((LinkedList ) footnotesList.get(footnoteListIndex)).size() - 1; 576 } 577 return prevSplitLength; 578 } 579 } 580 581 protected double computeAdjustmentRatio(KnuthNode activeNode, int difference) { 582 if (difference > 0) { 584 int maxAdjustment = totalStretch - activeNode.totalStretch; 585 if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) { 587 maxAdjustment += footnoteSeparatorLength.max - footnoteSeparatorLength.opt; 588 } 589 if (maxAdjustment > 0) { 590 return (double) difference / maxAdjustment; 591 } else { 592 return INFINITE_RATIO; 593 } 594 } else if (difference < 0) { 595 int maxAdjustment = totalShrink - activeNode.totalShrink; 596 if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) { 598 maxAdjustment += footnoteSeparatorLength.opt - footnoteSeparatorLength.min; 599 } 600 if (maxAdjustment > 0) { 601 return (double) difference / maxAdjustment; 602 } else { 603 return -INFINITE_RATIO; 604 } 605 } else { 606 return 0; 607 } 608 } 609 610 protected double computeDemerits(KnuthNode activeNode, KnuthElement element, 611 int fitnessClass, double r) { 612 double demerits = 0; 613 double f = Math.abs(r); 615 f = 1 + 100 * f * f * f; 616 if (element.isPenalty() && element.getP() >= 0) { 617 f += element.getP(); 618 demerits = f * f; 619 } else if (element.isPenalty() && !element.isForcedBreak()) { 620 double penalty = element.getP(); 621 demerits = f * f - penalty * penalty; 622 } else { 623 demerits = f * f; 624 } 625 626 if (element.isPenalty() && ((KnuthPenalty) element).isFlagged() 627 && getElement(activeNode.position).isPenalty() 628 && ((KnuthPenalty) getElement(activeNode.position)).isFlagged()) { 629 demerits += repeatedFlaggedDemerit; 631 } 632 if (Math.abs(fitnessClass - activeNode.fitness) > 1) { 633 demerits += incompatibleFitnessDemerit; 636 } 637 638 if (footnotesPending) { 639 if (footnoteListIndex < footnotesList.size() - 1) { 640 demerits += (footnotesList.size() - 1 - footnoteListIndex) 642 * deferredFootnoteDemerits; 643 } 644 if (footnoteElementIndex 645 < ((LinkedList ) footnotesList.get(footnoteListIndex)).size() - 1) { 646 demerits += splitFootnoteDemerits; 648 } 649 } 650 demerits += activeNode.totalDemerits; 651 return demerits; 652 } 653 654 protected void finish() { 655 for (int i = startLine; i < endLine; i++) { 656 for (KnuthPageNode node = (KnuthPageNode) getNode(i); 657 node != null; 658 node = (KnuthPageNode) node.next) { 659 if (node.totalFootnotes < totalFootnotesLength) { 660 createFootnotePages(node); 662 } 663 } 664 } 665 } 666 667 private void createFootnotePages(KnuthPageNode lastNode) { 668 insertedFootnotesLength = lastNode.totalFootnotes; 669 footnoteListIndex = lastNode.footnoteListIndex; 670 footnoteElementIndex = lastNode.footnoteElementIndex; 671 int availableBPD = getLineWidth(); 672 int split = 0; 673 KnuthPageNode prevNode = lastNode; 674 675 while (insertedFootnotesLength < totalFootnotesLength) { 677 if (((Integer ) lengthList.get(footnoteListIndex)).intValue() - insertedFootnotesLength 679 <= availableBPD) { 680 availableBPD -= ((Integer ) lengthList.get(footnoteListIndex)).intValue() 682 - insertedFootnotesLength; 683 insertedFootnotesLength = ((Integer )lengthList.get(footnoteListIndex)).intValue(); 684 footnoteElementIndex 685 = ((LinkedList )footnotesList.get(footnoteListIndex)).size() - 1; 686 } else if ((split = getFootnoteSplit(footnoteListIndex, footnoteElementIndex, 687 insertedFootnotesLength, availableBPD, true)) 688 > 0) { 689 availableBPD -= split; 691 insertedFootnotesLength += split; 692 } else { 695 KnuthPageNode node = (KnuthPageNode) 697 createNode(lastNode.position, prevNode.line + 1, 1, 698 insertedFootnotesLength - prevNode.totalFootnotes, 699 0, 0, 700 0, 0, 0, 701 0, 0, prevNode); 702 addNode(node.line, node); 703 removeNode(prevNode.line, prevNode); 704 705 prevNode = node; 706 availableBPD = getLineWidth(); 707 } 708 } 709 KnuthPageNode node = (KnuthPageNode) 711 createNode(lastNode.position, prevNode.line + 1, 1, 712 totalFootnotesLength - prevNode.totalFootnotes, 0, 0, 713 0, 0, 0, 714 0, 0, prevNode); 715 addNode(node.line, node); 716 removeNode(prevNode.line, prevNode); 717 } 718 719 722 public LinkedList getPageBreaks() { 723 return pageBreaks; 724 } 725 726 public void insertPageBreakAsFirst(PageBreakPosition pageBreak) { 727 if (pageBreaks == null) { 728 pageBreaks = new LinkedList (); 729 } 730 pageBreaks.addFirst(pageBreak); 731 } 732 733 private int getPartCount() { 734 if (pageBreaks == null) { 735 return 0; 736 } else { 737 return pageBreaks.size(); 738 } 739 } 740 741 public void updateData1(int total, double demerits) { 742 } 743 744 public void updateData2(KnuthNode bestActiveNode, 745 KnuthSequence sequence, 746 int total) { 747 int difference = bestActiveNode.difference; 750 if (difference + bestActiveNode.availableShrink < 0) { 751 if (!autoHeight) { 752 if (layoutListener != null) { 753 layoutListener.notifyOverflow(bestActiveNode.line - 1, getFObj()); 754 } else if (log.isWarnEnabled()) { 755 log.warn(FONode.decorateWithContextInfo( 756 "Part/page " + (bestActiveNode.line - 1) 757 + " overflows the available area in block-progression dimension.", 758 getFObj())); 759 } 760 } 761 } 762 boolean isNonLastPage = (bestActiveNode.line < total); 763 int blockAlignment = isNonLastPage ? alignment : alignmentLast; 764 double ratio = bestActiveNode.adjustRatio; 767 if (ratio < 0) { 768 difference = 0; 771 } else if (ratio <= 1 && isNonLastPage) { 772 difference = 0; 775 } else if (ratio > 1) { 776 ratio = 1; 779 difference -= bestActiveNode.availableStretch; 780 } else { 781 if (blockAlignment != Constants.EN_JUSTIFY) { 784 ratio = 0; 785 } else { 786 difference = 0; 788 } 789 } 790 int firstListIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteListIndex; 792 int firstElementIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteElementIndex; 793 if (footnotesList != null 794 && firstElementIndex == ((LinkedList ) footnotesList.get(firstListIndex)).size() - 1) { 795 firstListIndex++; 797 firstElementIndex = 0; 798 } else { 799 firstElementIndex++; 800 } 801 802 if (log.isDebugEnabled()) { 805 log.debug("BBA> difference=" + difference + " ratio=" + ratio 806 + " position=" + bestActiveNode.position); 807 } 808 insertPageBreakAsFirst(new PageBreakPosition(this.topLevelLM, 809 bestActiveNode.position, 810 firstListIndex, firstElementIndex, 811 ((KnuthPageNode) bestActiveNode).footnoteListIndex, 812 ((KnuthPageNode) bestActiveNode).footnoteElementIndex, 813 ratio, difference)); 814 } 815 816 protected int filterActiveNodes() { 817 KnuthNode bestActiveNode = null; 819 for (int i = startLine; i < endLine; i++) { 820 for (KnuthNode node = getNode(i); node != null; node = node.next) { 821 if (favorSinglePart 822 && node.line > 1 823 && bestActiveNode != null 824 && Math.abs(bestActiveNode.difference) < bestActiveNode.availableShrink) { 825 } else { 828 bestActiveNode = compareNodes(bestActiveNode, node); 829 } 830 if (node != bestActiveNode) { 831 removeNode(i, node); 832 } 833 } 834 } 835 return bestActiveNode.line; 836 } 837 838 public LinkedList getFootnoteList(int index) { 839 return (LinkedList ) footnotesList.get(index); 840 } 841 842 843 public FObj getFObj() { 844 return topLevelLM.getFObj(); 845 } 846 847 848 protected int getLineWidth(int line) { 849 int bpd; 850 if (pageProvider != null) { 851 bpd = pageProvider.getAvailableBPD(line); 852 } else { 853 bpd = super.getLineWidth(line); 854 } 855 if (log.isTraceEnabled()) { 856 log.trace("getLineWidth(" + line + ") -> " + bpd); 857 } 858 return bpd; 859 } 860 861 864 public interface PageBreakingLayoutListener { 865 866 871 void notifyOverflow(int part, FObj obj); 872 873 } 874 875 } 876 | Popular Tags |