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.

PageBreakingAlgorithm.java 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. /*
  2. * Copyright 2004-2005 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /* $Id$ */
  17. package org.apache.fop.layoutmgr;
  18. import java.util.ArrayList;
  19. import java.util.LinkedList;
  20. import java.util.ListIterator;
  21. import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition;
  22. import org.apache.fop.traits.MinOptMax;
  23. class PageBreakingAlgorithm extends BreakingAlgorithm {
  24. private LayoutManager topLevelLM;
  25. private PageSequenceLayoutManager.PageViewportProvider pageViewportProvider;
  26. private LinkedList pageBreaks = null;
  27. private ArrayList footnotesList = null;
  28. private ArrayList lengthList = null;
  29. private int totalFootnotesLength = 0;
  30. private int insertedFootnotesLength = 0;
  31. private boolean footnotesPending = false;
  32. /**
  33. * newFootnotes is true if the elements met after the previous break point
  34. * contain footnote citations
  35. */
  36. private boolean newFootnotes = false;
  37. /**
  38. * firstNewFootnoteIndex is the index of the first footnote met after the
  39. * previous break point
  40. */
  41. private int firstNewFootnoteIndex = 0;
  42. private int footnoteListIndex = 0;
  43. private int footnoteElementIndex = -1;
  44. // demerits for a page break that splits a footnote
  45. private int splitFootnoteDemerits = 5000;
  46. // demerits for a page break that defers a whole footnote to the following page
  47. private int deferredFootnoteDemerits = 10000;
  48. private MinOptMax footnoteSeparatorLength = null;
  49. // the method noBreakBetween(int, int) uses thise variables
  50. // to store parameters and result of the last call, in order
  51. // to reuse them and take less time
  52. private int storedPrevBreakIndex = -1;
  53. private int storedBreakIndex = -1;
  54. private boolean storedValue = false;
  55. public PageBreakingAlgorithm(LayoutManager topLevelLM,
  56. PageSequenceLayoutManager.PageViewportProvider pageViewportProvider,
  57. int alignment, int alignmentLast,
  58. MinOptMax footnoteSeparatorLength,
  59. boolean partOverflowRecovery) {
  60. super(alignment, alignmentLast, true, partOverflowRecovery);
  61. this.topLevelLM = topLevelLM;
  62. this.pageViewportProvider = pageViewportProvider;
  63. best = new BestPageRecords();
  64. this.footnoteSeparatorLength = (MinOptMax) footnoteSeparatorLength.clone();
  65. // add some stretch, to avoid a restart for every page containing footnotes
  66. if (footnoteSeparatorLength.min == footnoteSeparatorLength.max) {
  67. footnoteSeparatorLength.max += 10000;
  68. }
  69. }
  70. /**
  71. * this class represent a feasible breaking point
  72. * with extra information about footnotes
  73. */
  74. protected class KnuthPageNode extends KnuthNode {
  75. // additional length due to footnotes
  76. public int totalFootnotes;
  77. // index of the last inserted footnote
  78. public int footnoteListIndex;
  79. // index of the last inserted element of the last inserted footnote
  80. public int footnoteElementIndex;
  81. public KnuthPageNode(int position, int line, int fitness,
  82. int totalWidth, int totalStretch, int totalShrink,
  83. int totalFootnotes, int footnoteListIndex, int footnoteElementIndex,
  84. double adjustRatio, int availableShrink, int availableStretch,
  85. int difference, double totalDemerits, KnuthNode previous) {
  86. super(position, line, fitness,
  87. totalWidth, totalStretch, totalShrink,
  88. adjustRatio, availableShrink, availableStretch,
  89. difference, totalDemerits, previous);
  90. this.totalFootnotes = totalFootnotes;
  91. this.footnoteListIndex = footnoteListIndex;
  92. this.footnoteElementIndex = footnoteElementIndex;
  93. }
  94. }
  95. /**
  96. * this class stores information about how the nodes
  97. * which could start a line ending at the current element
  98. */
  99. protected class BestPageRecords extends BestRecords {
  100. private int bestFootnotesLength[] = new int[4];
  101. private int bestFootnoteListIndex[] = new int[4];
  102. private int bestFootnoteElementIndex[] = new int[4];
  103. public void addRecord(double demerits, KnuthNode node, double adjust,
  104. int availableShrink, int availableStretch,
  105. int difference, int fitness) {
  106. super.addRecord(demerits, node, adjust,
  107. availableShrink, availableStretch,
  108. difference, fitness);
  109. bestFootnotesLength[fitness] = insertedFootnotesLength;
  110. bestFootnoteListIndex[fitness] = footnoteListIndex;
  111. bestFootnoteElementIndex[fitness] = footnoteElementIndex;
  112. }
  113. public int getFootnotesLength(int fitness) {
  114. return bestFootnotesLength[fitness];
  115. }
  116. public int getFootnoteListIndex(int fitness) {
  117. return bestFootnoteListIndex[fitness];
  118. }
  119. public int getFootnoteElementIndex(int fitness) {
  120. return bestFootnoteElementIndex[fitness];
  121. }
  122. }
  123. protected void initialize() {
  124. super.initialize();
  125. insertedFootnotesLength = 0;
  126. footnoteListIndex = 0;
  127. footnoteElementIndex = -1;
  128. }
  129. protected KnuthNode createNode(int position, int line, int fitness,
  130. int totalWidth, int totalStretch, int totalShrink,
  131. double adjustRatio, int availableShrink, int availableStretch,
  132. int difference, double totalDemerits, KnuthNode previous) {
  133. return new KnuthPageNode(position, line, fitness,
  134. totalWidth, totalStretch, totalShrink,
  135. insertedFootnotesLength, footnoteListIndex, footnoteElementIndex,
  136. adjustRatio, availableShrink, availableStretch,
  137. difference, totalDemerits, previous);
  138. }
  139. protected KnuthNode createNode(int position, int line, int fitness,
  140. int totalWidth, int totalStretch, int totalShrink) {
  141. return new KnuthPageNode(position, line, fitness,
  142. totalWidth, totalStretch, totalShrink,
  143. ((BestPageRecords) best).getFootnotesLength(fitness),
  144. ((BestPageRecords) best).getFootnoteListIndex(fitness),
  145. ((BestPageRecords) best).getFootnoteElementIndex(fitness),
  146. best.getAdjust(fitness), best.getAvailableShrink(fitness), best.getAvailableStretch(fitness),
  147. best.getDifference(fitness), best.getDemerits(fitness), best.getNode(fitness));
  148. }
  149. protected void handleBox(KnuthBox box) {
  150. if (box instanceof KnuthBlockBox
  151. && ((KnuthBlockBox) box).hasAnchors()) {
  152. handleFootnotes(((KnuthBlockBox) box).getElementLists());
  153. if (!newFootnotes) {
  154. newFootnotes = true;
  155. firstNewFootnoteIndex = footnotesList.size() - 1;
  156. }
  157. }
  158. }
  159. private void handleFootnotes(LinkedList elementLists) {
  160. // initialization
  161. if (!footnotesPending) {
  162. footnotesPending = true;
  163. footnotesList = new ArrayList();
  164. lengthList = new ArrayList();
  165. totalFootnotesLength = 0;
  166. }
  167. if (!newFootnotes) {
  168. newFootnotes = true;
  169. firstNewFootnoteIndex = footnotesList.size();
  170. }
  171. // compute the total length of the footnotes
  172. ListIterator elementListsIterator = elementLists.listIterator();
  173. while (elementListsIterator.hasNext()) {
  174. LinkedList noteList = (LinkedList) elementListsIterator.next();
  175. int noteLength = 0;
  176. footnotesList.add(noteList);
  177. ListIterator noteListIterator = noteList.listIterator();
  178. while (noteListIterator.hasNext()) {
  179. KnuthElement element = (KnuthElement) noteListIterator.next();
  180. if (element.isBox() || element.isGlue()) {
  181. noteLength += element.getW();
  182. }
  183. }
  184. int prevLength = (lengthList.size() == 0 ? 0 : ((Integer) lengthList.get(lengthList.size() - 1)).intValue());
  185. lengthList.add(new Integer(prevLength + noteLength));
  186. totalFootnotesLength += noteLength;
  187. }
  188. }
  189. protected void restartFrom(KnuthNode restartingNode, int currentIndex) {
  190. super.restartFrom(restartingNode, currentIndex);
  191. newFootnotes = false;
  192. if (footnotesPending) {
  193. // remove from footnotesList the note lists that will be met
  194. // after the restarting point
  195. for (int j = currentIndex; j >= restartingNode.position; j--) {
  196. KnuthElement resettedElement = getElement(j);
  197. if (resettedElement instanceof KnuthBlockBox
  198. && ((KnuthBlockBox) resettedElement).hasAnchors()) {
  199. resetFootnotes(((KnuthBlockBox) resettedElement).getElementLists());
  200. }
  201. }
  202. }
  203. }
  204. private void resetFootnotes(LinkedList elementLists) {
  205. for (int i = 0; i < elementLists.size(); i++) {
  206. LinkedList removedList = (LinkedList) footnotesList.remove(footnotesList.size() - 1);
  207. lengthList.remove(lengthList.size() - 1);
  208. // update totalFootnotesLength
  209. if (lengthList.size() > 0) {
  210. totalFootnotesLength = ((Integer) lengthList.get(lengthList.size() - 1)).intValue();
  211. } else {
  212. totalFootnotesLength = 0;
  213. }
  214. }
  215. // update footnotesPending;
  216. if (footnotesList.size() == 0) {
  217. footnotesPending = false;
  218. }
  219. }
  220. protected void considerLegalBreak(KnuthElement element, int elementIdx) {
  221. super.considerLegalBreak(element, elementIdx);
  222. newFootnotes = false;
  223. }
  224. /**
  225. * Return the difference between the line width and the width of the break that
  226. * ends in 'element'.
  227. * @param activeNode
  228. * @param element
  229. * @param elementIndex
  230. * @return The difference in width. Positive numbers mean extra space in the line,
  231. * negative number that the line overflows.
  232. */
  233. protected int computeDifference(KnuthNode activeNode, KnuthElement element,
  234. int elementIndex) {
  235. KnuthPageNode pageNode = (KnuthPageNode) activeNode;
  236. int actualWidth = totalWidth - pageNode.totalWidth;
  237. int footnoteSplit;
  238. boolean canDeferOldFootnotes;
  239. if (element.isPenalty()) {
  240. actualWidth += element.getW();
  241. }
  242. if (footnotesPending) {
  243. // compute the total length of the footnotes not yet inserted
  244. int allFootnotes = totalFootnotesLength - pageNode.totalFootnotes;
  245. if (allFootnotes > 0) {
  246. // this page contains some footnote citations
  247. // add the footnote separator width
  248. actualWidth += footnoteSeparatorLength.opt;
  249. if (actualWidth + allFootnotes <= getLineWidth()) {
  250. // there is enough space to insert all footnotes:
  251. // add the whole allFootnotes length
  252. actualWidth += allFootnotes;
  253. insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes;
  254. footnoteListIndex = footnotesList.size() - 1;
  255. footnoteElementIndex = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1;
  256. } else if (((canDeferOldFootnotes = checkCanDeferOldFootnotes(pageNode, elementIndex))
  257. || newFootnotes)
  258. && (footnoteSplit = getFootnoteSplit(pageNode, getLineWidth() - actualWidth,
  259. canDeferOldFootnotes)) > 0) {
  260. // it is allowed to break or even defer footnotes if either:
  261. // - there are new footnotes in the last piece of content, and
  262. // there is space to add at least a piece of the first one
  263. // - or the previous page break deferred some footnote lines, and
  264. // this is the first feasible break; in this case it is allowed
  265. // to break and defer, if necessary, old and new footnotes
  266. actualWidth += footnoteSplit;
  267. insertedFootnotesLength = pageNode.totalFootnotes + footnoteSplit;
  268. // footnoteListIndex has been set in getFootnoteSplit()
  269. // footnoteElementIndex has been set in getFootnoteSplit()
  270. } else {
  271. // there is no space to add the smallest piece of footnote,
  272. // or we are trying to add a piece of content with no footnotes and
  273. // it does not fit in the page, because of previous footnote bodies
  274. // that cannot be broken:
  275. // add the whole allFootnotes length, so this breakpoint will be discarded
  276. actualWidth += allFootnotes;
  277. insertedFootnotesLength = pageNode.totalFootnotes + allFootnotes;
  278. footnoteListIndex = footnotesList.size() - 1;
  279. footnoteElementIndex = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1;
  280. }
  281. } else {
  282. // all footnotes have already been placed on previous pages
  283. }
  284. } else {
  285. // there are no footnotes
  286. }
  287. return getLineWidth(activeNode.line) - actualWidth;
  288. }
  289. private boolean checkCanDeferOldFootnotes(KnuthPageNode node, int contentElementIndex) {
  290. return (noBreakBetween(node.position, contentElementIndex)
  291. && deferredFootnotes(node.footnoteListIndex, node.footnoteElementIndex, node.totalFootnotes));
  292. }
  293. private boolean noBreakBetween(int prevBreakIndex, int breakIndex) {
  294. // this method stores the parameters and the return value from previous calls
  295. // in order to avoid scanning the element list unnecessarily:
  296. // - if there is no break between element #i and element #j
  297. // there will not be a break between #(i+h) and #j too
  298. // - if there is a break between element #i and element #j
  299. // there will be a break between #(i-h) and #(j+k) too
  300. if (storedPrevBreakIndex != -1
  301. && ((prevBreakIndex >= storedPrevBreakIndex
  302. && breakIndex == storedBreakIndex
  303. && storedValue)
  304. || (prevBreakIndex <= storedPrevBreakIndex
  305. && breakIndex >= storedBreakIndex
  306. && !storedValue))) {
  307. // use the stored value, do nothing
  308. } else {
  309. // compute the new value
  310. int index;
  311. // ignore suppressed elements
  312. for (index = prevBreakIndex + 1;
  313. !par.getElement(index).isBox();
  314. index ++) {
  315. }
  316. // find the next break
  317. for (;
  318. index <= breakIndex;
  319. index ++) {
  320. if (par.getElement(index).isGlue() && par.getElement(index - 1).isBox()
  321. || par.getElement(index).isPenalty()
  322. && par.getElement(index).getP() < KnuthElement.INFINITE) {
  323. // break found
  324. break;
  325. }
  326. }
  327. // update stored parameters and value
  328. storedPrevBreakIndex = prevBreakIndex;
  329. storedBreakIndex = breakIndex;
  330. storedValue = (index == breakIndex);
  331. }
  332. return storedValue;
  333. }
  334. private boolean deferredFootnotes(int listIndex, int elementIndex, int length) {
  335. return ((newFootnotes
  336. && firstNewFootnoteIndex != 0
  337. && (listIndex < firstNewFootnoteIndex - 1
  338. || elementIndex < ((LinkedList) footnotesList.get(listIndex)).size() - 1))
  339. || length < totalFootnotesLength);
  340. }
  341. private int getFootnoteSplit(KnuthPageNode activeNode, int availableLength, boolean canDeferOldFootnotes) {
  342. return getFootnoteSplit(activeNode.footnoteListIndex,
  343. activeNode.footnoteElementIndex,
  344. activeNode.totalFootnotes,
  345. availableLength, canDeferOldFootnotes);
  346. }
  347. private int getFootnoteSplit(int prevListIndex, int prevElementIndex, int prevLength,
  348. int availableLength, boolean canDeferOldFootnotes) {
  349. if (availableLength <= 0) {
  350. return 0;
  351. } else {
  352. // the split should contain a piece of the last footnote
  353. // together with all previous, not yet inserted footnotes;
  354. // but if this is not possible, try adding as much content as possible
  355. int splitLength = 0;
  356. ListIterator noteListIterator = null;
  357. KnuthElement element = null;
  358. boolean somethingAdded = false;
  359. // prevListIndex and prevElementIndex points to the last footnote element
  360. // already placed in a page: advance to the next element
  361. int listIndex = prevListIndex;
  362. int elementIndex = prevElementIndex;
  363. if (elementIndex == ((LinkedList) footnotesList.get(listIndex)).size() - 1) {
  364. listIndex ++;
  365. elementIndex = 0;
  366. } else {
  367. elementIndex ++;
  368. }
  369. // try adding whole notes
  370. if (footnotesList.size() - 1 > listIndex) {
  371. // add the previous footnotes: these cannot be broken or deferred
  372. if (!canDeferOldFootnotes
  373. && newFootnotes
  374. && firstNewFootnoteIndex > 0) {
  375. splitLength = ((Integer) lengthList.get(firstNewFootnoteIndex - 1)).intValue()
  376. - prevLength;
  377. listIndex = firstNewFootnoteIndex;
  378. elementIndex = 0;
  379. }
  380. // try adding the new footnotes
  381. while (((Integer) lengthList.get(listIndex)).intValue() - prevLength
  382. <= availableLength) {
  383. splitLength = ((Integer) lengthList.get(listIndex)).intValue()
  384. - prevLength;
  385. somethingAdded = true;
  386. listIndex ++;
  387. elementIndex = 0;
  388. }
  389. // as this method is called only if it is not possible to insert
  390. // all footnotes, at this point listIndex and elementIndex points to
  391. // an existing element, the next one we will try to insert
  392. }
  393. // try adding a split of the next note
  394. noteListIterator = ((LinkedList) footnotesList.get(listIndex)).listIterator(elementIndex);
  395. int prevSplitLength = 0;
  396. int prevIndex = -1;
  397. int index = -1;
  398. while (!(somethingAdded && splitLength > availableLength)) {
  399. if (!somethingAdded) {
  400. somethingAdded = true;
  401. } else {
  402. prevSplitLength = splitLength;
  403. prevIndex = index;
  404. }
  405. // get a sub-sequence from the note element list
  406. boolean bPrevIsBox = false;
  407. while (noteListIterator.hasNext()) {
  408. // as this method is called only if it is not possible to insert
  409. // all footnotes, and we have already tried (and failed) to insert
  410. // this whole footnote, the while loop will never reach the end
  411. // of the note sequence
  412. element = (KnuthElement) noteListIterator.next();
  413. if (element.isBox()) {
  414. // element is a box
  415. splitLength += element.getW();
  416. bPrevIsBox = true;
  417. } else if (element.isGlue()) {
  418. // element is a glue
  419. if (bPrevIsBox) {
  420. // end of the sub-sequence
  421. index = noteListIterator.previousIndex();
  422. break;
  423. }
  424. bPrevIsBox = false;
  425. splitLength += element.getW();
  426. } else {
  427. // element is a penalty
  428. if (element.getP() < KnuthElement.INFINITE) {
  429. // end of the sub-sequence
  430. index = noteListIterator.previousIndex();
  431. break;
  432. }
  433. }
  434. }
  435. }
  436. // if prevSplitLength is 0, this means that the available length isn't enough
  437. // to insert even the smallest split of the last footnote, so we cannot end a
  438. // page here
  439. // if prevSplitLength is > 0 we can insert some footnote content in this page
  440. // and insert the remaining in the following one
  441. if (!somethingAdded) {
  442. // there was not enough space to add a piece of the first new footnote
  443. // this is not a good break
  444. prevSplitLength = 0;
  445. } else if (prevSplitLength > 0) {
  446. // prevIndex is -1 if we have added only some whole footnotes
  447. footnoteListIndex = (prevIndex != -1) ? listIndex : listIndex - 1;
  448. footnoteElementIndex = (prevIndex != -1) ?
  449. prevIndex :
  450. ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1;
  451. }
  452. return prevSplitLength;
  453. }
  454. }
  455. /**
  456. * Return the adjust ration needed to make up for the difference. A ratio of
  457. * <ul>
  458. * <li>0 means that the break has the exact right width</li>
  459. * <li>&gt;= -1 && &lt; 0 means that the break is to wider than the line,
  460. * but within the minimim values of the glues.</li>
  461. * <li>&gt;0 && &lt 1 means that the break is smaller than the line width,
  462. * but within the maximum values of the glues.</li>
  463. * <li>&gt 1 means that the break is too small to make up for the glues.</li>
  464. * </ul>
  465. * @param activeNode
  466. * @param difference
  467. * @return The ration.
  468. */
  469. protected double computeAdjustmentRatio(KnuthNode activeNode, int difference) {
  470. // compute the adjustment ratio
  471. if (difference > 0) {
  472. int maxAdjustment = totalStretch - activeNode.totalStretch;
  473. // add the footnote separator stretch if some footnote content will be added
  474. if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) {
  475. maxAdjustment += footnoteSeparatorLength.max - footnoteSeparatorLength.opt;
  476. }
  477. if (maxAdjustment > 0) {
  478. return (double) difference / maxAdjustment;
  479. } else {
  480. return INFINITE_RATIO;
  481. }
  482. } else if (difference < 0) {
  483. int maxAdjustment = totalShrink - activeNode.totalShrink;
  484. // add the footnote separator shrink if some footnote content will be added
  485. if (((KnuthPageNode) activeNode).totalFootnotes < totalFootnotesLength) {
  486. maxAdjustment += footnoteSeparatorLength.opt - footnoteSeparatorLength.min;
  487. }
  488. if (maxAdjustment > 0) {
  489. return (double) difference / maxAdjustment;
  490. } else {
  491. return -INFINITE_RATIO;
  492. }
  493. } else {
  494. return 0;
  495. }
  496. }
  497. protected double computeDemerits(KnuthNode activeNode, KnuthElement element,
  498. int fitnessClass, double r) {
  499. double demerits = 0;
  500. // compute demerits
  501. double f = Math.abs(r);
  502. f = 1 + 100 * f * f * f;
  503. if (element.isPenalty() && element.getP() >= 0) {
  504. f += element.getP();
  505. demerits = f * f;
  506. } else if (element.isPenalty() && !element.isForcedBreak()) {
  507. double penalty = element.getP();
  508. demerits = f * f - penalty * penalty;
  509. } else {
  510. demerits = f * f;
  511. }
  512. if (element.isPenalty() && ((KnuthPenalty) element).isFlagged()
  513. && getElement(activeNode.position).isPenalty()
  514. && ((KnuthPenalty) getElement(activeNode.position)).isFlagged()) {
  515. // add demerit for consecutive breaks at flagged penalties
  516. demerits += repeatedFlaggedDemerit;
  517. }
  518. if (Math.abs(fitnessClass - activeNode.fitness) > 1) {
  519. // add demerit for consecutive breaks
  520. // with very different fitness classes
  521. demerits += incompatibleFitnessDemerit;
  522. }
  523. if (footnotesPending) {
  524. if (footnoteListIndex < footnotesList.size() - 1) {
  525. // add demerits for the deferred footnotes
  526. demerits += (footnotesList.size() - 1 - footnoteListIndex) * deferredFootnoteDemerits;
  527. }
  528. if (footnoteElementIndex < ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1) {
  529. // add demerits for the footnote split between pages
  530. demerits += splitFootnoteDemerits;
  531. }
  532. }
  533. demerits += activeNode.totalDemerits;
  534. return demerits;
  535. }
  536. protected void finish() {
  537. for (int i = startLine; i < endLine; i++) {
  538. for (KnuthPageNode node = (KnuthPageNode) getNode(i);
  539. node != null;
  540. node = (KnuthPageNode) node.next) {
  541. if (node.totalFootnotes < totalFootnotesLength) {
  542. // layout remaining footnote bodies
  543. createFootnotePages(node);
  544. }
  545. }
  546. }
  547. }
  548. private void createFootnotePages(KnuthPageNode lastNode) {
  549. insertedFootnotesLength = lastNode.totalFootnotes;
  550. footnoteListIndex = lastNode.footnoteListIndex;
  551. footnoteElementIndex = lastNode.footnoteElementIndex;
  552. int availableBPD = getLineWidth();
  553. int split = 0;
  554. KnuthPageNode prevNode = lastNode;
  555. // create pages containing the remaining footnote bodies
  556. while (insertedFootnotesLength < totalFootnotesLength) {
  557. // try adding some more content
  558. if (((Integer) lengthList.get(footnoteListIndex)).intValue() - insertedFootnotesLength
  559. <= availableBPD) {
  560. // add a whole footnote
  561. availableBPD -= ((Integer) lengthList.get(footnoteListIndex)).intValue()
  562. - insertedFootnotesLength;
  563. insertedFootnotesLength = ((Integer) lengthList.get(footnoteListIndex)).intValue();
  564. footnoteElementIndex = ((LinkedList) footnotesList.get(footnoteListIndex)).size() - 1;
  565. } else if ((split = getFootnoteSplit(footnoteListIndex, footnoteElementIndex,
  566. insertedFootnotesLength, availableBPD, true))
  567. > 0) {
  568. // add a piece of a footnote
  569. availableBPD -= split;
  570. insertedFootnotesLength += split;
  571. // footnoteListIndex has already been set in getFootnoteSplit()
  572. // footnoteElementIndex has already been set in getFootnoteSplit()
  573. } else {
  574. // cannot add any content: create a new node and start again
  575. KnuthPageNode node = (KnuthPageNode)
  576. createNode(lastNode.position, prevNode.line + 1, 1,
  577. insertedFootnotesLength - prevNode.totalFootnotes, 0, 0,
  578. 0, 0, 0,
  579. 0, 0, prevNode);
  580. addNode(node.line, node);
  581. removeNode(prevNode.line, prevNode);
  582. prevNode = node;
  583. availableBPD = getLineWidth();
  584. }
  585. }
  586. // create the last node
  587. KnuthPageNode node = (KnuthPageNode)
  588. createNode(lastNode.position, prevNode.line + 1, 1,
  589. totalFootnotesLength - prevNode.totalFootnotes, 0, 0,
  590. 0, 0, 0,
  591. 0, 0, prevNode);
  592. addNode(node.line, node);
  593. removeNode(prevNode.line, prevNode);
  594. }
  595. /**
  596. * Remove the first node in line 'line'. If the line then becomes empty, adjust the
  597. * startLine accordingly.
  598. * @param line
  599. * @param node
  600. */
  601. protected void removeNode(int line, KnuthNode node) {
  602. KnuthNode n = getNode(line);
  603. if (n != node) {
  604. if (footnotesPending) {
  605. // nodes could be rightly deactivated in a different order
  606. KnuthNode prevNode = null;
  607. while (n != node) {
  608. prevNode = n;
  609. n = n.next;
  610. }
  611. prevNode.next = n.next;
  612. if (prevNode.next == null) {
  613. activeLines[line*2+1] = prevNode;
  614. }
  615. } else {
  616. log.error("Should be first");
  617. }
  618. } else {
  619. activeLines[line*2] = node.next;
  620. if (node.next == null) {
  621. activeLines[line*2+1] = null;
  622. }
  623. while (startLine < endLine && getNode(startLine) == null) {
  624. startLine++;
  625. }
  626. }
  627. activeNodeCount--;
  628. }
  629. public LinkedList getPageBreaks() {
  630. return pageBreaks;
  631. }
  632. public void insertPageBreakAsFirst(PageBreakPosition pageBreak) {
  633. if (pageBreaks == null) {
  634. pageBreaks = new LinkedList();
  635. }
  636. pageBreaks.addFirst(pageBreak);
  637. }
  638. public void updateData1(int total, double demerits) {
  639. }
  640. public void updateData2(KnuthNode bestActiveNode,
  641. KnuthSequence sequence,
  642. int total) {
  643. //int difference = (bestActiveNode.line < total) ? bestActiveNode.difference : bestActiveNode.difference + fillerMinWidth;
  644. int difference = bestActiveNode.difference;
  645. int blockAlignment = (bestActiveNode.line < total) ? alignment : alignmentLast;
  646. // it is always allowed to adjust space, so the ratio must be set regardless of
  647. // the value of the property display-align; the ratio must be <= 1
  648. double ratio = bestActiveNode.adjustRatio;
  649. if (ratio < 0) {
  650. // page break with a negative difference:
  651. // spaces always have enough shrink
  652. difference = 0;
  653. } else if (ratio <= 1 && bestActiveNode.line < total) {
  654. // not-last page break with a positive difference smaller than the available stretch:
  655. // spaces can stretch to fill the whole difference
  656. difference = 0;
  657. } else if (ratio > 1) {
  658. // not-last page with a positive difference greater than the available stretch
  659. // spaces can stretch to fill the difference only partially
  660. ratio = 1;
  661. difference -= bestActiveNode.availableStretch;
  662. } else {
  663. // last page with a positive difference:
  664. // spaces do not need to stretch
  665. ratio = 0;
  666. }
  667. // compute the indexes of the first footnote list and the first element in that list
  668. int firstListIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteListIndex;
  669. int firstElementIndex = ((KnuthPageNode) bestActiveNode.previous).footnoteElementIndex;
  670. if (footnotesList != null
  671. && firstElementIndex == ((LinkedList) footnotesList.get(firstListIndex)).size() - 1) {
  672. // advance to the next list
  673. firstListIndex ++;
  674. firstElementIndex = 0;
  675. } else {
  676. firstElementIndex ++;
  677. }
  678. // add nodes at the beginning of the list, as they are found
  679. // backwards, from the last one to the first one
  680. if (log.isDebugEnabled()) {
  681. log.debug("BBA> difference=" + difference + " ratio=" + ratio
  682. + " position=" + bestActiveNode.position);
  683. }
  684. insertPageBreakAsFirst(new PageBreakPosition(this.topLevelLM,
  685. bestActiveNode.position,
  686. firstListIndex, firstElementIndex,
  687. ((KnuthPageNode) bestActiveNode).footnoteListIndex,
  688. ((KnuthPageNode) bestActiveNode).footnoteElementIndex,
  689. ratio, difference));
  690. }
  691. protected int filterActiveNodes() {
  692. // leave only the active node with fewest total demerits
  693. KnuthNode bestActiveNode = null;
  694. for (int i = startLine; i < endLine; i++) {
  695. for (KnuthNode node = getNode(i); node != null; node = node.next) {
  696. bestActiveNode = compareNodes(bestActiveNode, node);
  697. if (node != bestActiveNode) {
  698. removeNode(i, node);
  699. }
  700. }
  701. }
  702. return bestActiveNode.line;
  703. }
  704. public LinkedList getFootnoteList(int index) {
  705. return (LinkedList) footnotesList.get(index);
  706. }
  707. /** @see org.apache.fop.layoutmgr.BreakingAlgorithm#getLineWidth(int) */
  708. protected int getLineWidth(int line) {
  709. int bpd;
  710. if (pageViewportProvider != null) {
  711. bpd = pageViewportProvider.getAvailableBPD(line);
  712. } else {
  713. bpd = super.getLineWidth(line);
  714. }
  715. if (log.isTraceEnabled()) {
  716. log.trace("getLineWidth(" + line + ") -> " + bpd);
  717. }
  718. return bpd;
  719. }
  720. }