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.

BreakingAlgorithm.java 57KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.layoutmgr;
  19. import org.apache.commons.logging.Log;
  20. import org.apache.commons.logging.LogFactory;
  21. import org.apache.fop.fo.Constants;
  22. /**
  23. * The set of nodes is sorted into lines indexed into activeLines.
  24. * The nodes in each line are linked together in a single linked list by the
  25. * {@link KnuthNode#next} field. The activeLines array contains a link to the head of
  26. * the linked list in index 'line*2' and a link to the tail at index 'line*2+1'.
  27. * <p>
  28. * The set of active nodes can be traversed by
  29. * <pre>
  30. * for (int line = startLine; line &lt; endLine; line++) {
  31. * for (KnuthNode node = getNode(line); node != null; node = node.next) {
  32. * // Do something with 'node'
  33. * }
  34. * }
  35. * </pre>
  36. */
  37. public abstract class BreakingAlgorithm {
  38. /** the logger for the class */
  39. protected static final Log log = LogFactory.getLog(BreakingAlgorithm.class);
  40. /** Maximum adjustment ration */
  41. protected static final int INFINITE_RATIO = 1000;
  42. private static final int MAX_RECOVERY_ATTEMPTS = 5;
  43. // constants identifying a subset of the feasible breaks
  44. /** All feasible breaks are ok. */
  45. public static final int ALL_BREAKS = 0;
  46. /** This forbids hyphenation. */
  47. public static final int NO_FLAGGED_PENALTIES = 1;
  48. /** wrap-option = "no-wrap". */
  49. public static final int ONLY_FORCED_BREAKS = 2;
  50. /** Holder for symbolic literals for the fitness classes */
  51. static final class FitnessClasses {
  52. private FitnessClasses() {
  53. }
  54. static final int VERY_TIGHT = 0;
  55. static final int TIGHT = 1;
  56. static final int LOOSE = 2;
  57. static final int VERY_LOOSE = 3;
  58. static final String[] NAMES = {
  59. "VERY TIGHT", "TIGHT", "LOOSE", "VERY LOOSE"
  60. };
  61. /**
  62. * Figure out the fitness class of this line (tight, loose,
  63. * very tight or very loose).
  64. * See the section on "More Bells and Whistles" in Knuth's
  65. * "Breaking Paragraphs Into Lines".
  66. *
  67. * @param adjustRatio the adjustment ratio
  68. * @return the fitness class
  69. */
  70. static int computeFitness(double adjustRatio) {
  71. if (adjustRatio < -0.5) {
  72. return FitnessClasses.VERY_TIGHT;
  73. } else if (adjustRatio <= 0.5) {
  74. return FitnessClasses.TIGHT;
  75. } else if (adjustRatio <= 1.0) {
  76. return FitnessClasses.LOOSE;
  77. } else {
  78. return FitnessClasses.VERY_LOOSE;
  79. }
  80. }
  81. }
  82. // parameters of Knuth's algorithm:
  83. /** Demerit for consecutive lines ending at flagged penalties. */
  84. protected int repeatedFlaggedDemerit = KnuthPenalty.FLAGGED_PENALTY;
  85. /** Demerit for consecutive lines belonging to incompatible fitness classes . */
  86. protected int incompatibleFitnessDemerit = KnuthPenalty.FLAGGED_PENALTY;
  87. /** Maximum number of consecutive lines ending with a flagged penalty.
  88. * Only a value &gt;= 1 is a significant limit.
  89. */
  90. protected int maxFlaggedPenaltiesCount;
  91. /**
  92. * The threshold for considering breaks to be acceptable. The adjustment ratio must be
  93. * inferior to this threshold.
  94. */
  95. private double threshold;
  96. /**
  97. * The paragraph of KnuthElements.
  98. */
  99. protected KnuthSequence par;
  100. /**
  101. * The width of a line (or height of a column in page-breaking mode).
  102. * -1 indicates that the line widths are different for each line.
  103. */
  104. protected int lineWidth = -1;
  105. /** Force the algorithm to find a set of breakpoints, even if no feasible breakpoints
  106. * exist.
  107. */
  108. private boolean force;
  109. /** If set to true, doesn't ignore break possibilities which are definitely too short. */
  110. protected boolean considerTooShort;
  111. /** When in forced mode, the best node leading to a too long line. The line will be
  112. * too long anyway, but this one will lead to a paragraph with fewest demerits.
  113. */
  114. private KnuthNode lastTooLong;
  115. /** When in forced mode, the best node leading to a too short line. The line will be
  116. * too short anyway, but this one will lead to a paragraph with fewest demerits.
  117. */
  118. private KnuthNode lastTooShort;
  119. /** The node to be reactivated if no set of feasible breakpoints can be found for this
  120. * paragraph.
  121. */
  122. private KnuthNode lastDeactivated;
  123. /** Alignment of the paragraph/page. One of EN_START, EN_JUSTIFY, etc. */
  124. protected int alignment;
  125. /** Alignment of the paragraph's last line. */
  126. protected int alignmentLast;
  127. /** Used to handle the text-indent property (indent the first line of a paragraph). */
  128. protected boolean indentFirstPart;
  129. /**
  130. * The set of active nodes in ascending line order. For each line l, activeLines[2l] contains a
  131. * link to l's first active node, and activeLines[2l+1] a link to l's last active node. The
  132. * line number l corresponds to the number of the line ending at the node's breakpoint.
  133. */
  134. protected KnuthNode[] activeLines;
  135. /**
  136. * The number of active nodes.
  137. */
  138. protected int activeNodeCount;
  139. /**
  140. * The lowest available line in the set of active nodes.
  141. */
  142. protected int startLine;
  143. /**
  144. * The highest + 1 available line in the set of active nodes.
  145. */
  146. protected int endLine;
  147. /**
  148. * The total width of all elements handled so far.
  149. */
  150. protected int totalWidth;
  151. /**
  152. * The total stretch of all elements handled so far.
  153. */
  154. protected int totalStretch;
  155. /**
  156. * The total shrink of all elements handled so far.
  157. */
  158. protected int totalShrink;
  159. /**
  160. * Best records.
  161. */
  162. protected BestRecords best;
  163. private boolean partOverflowRecoveryActivated = true;
  164. private KnuthNode lastRecovered;
  165. /**
  166. * Create a new instance.
  167. *
  168. * @param align alignment of the paragraph/page. One of {@link Constants#EN_START},
  169. * {@link Constants#EN_JUSTIFY}, {@link Constants#EN_CENTER},
  170. * {@link Constants#EN_END}.
  171. * For pages, {@link Constants#EN_BEFORE} and {@link Constants#EN_AFTER}
  172. * are mapped to the corresponding inline properties,
  173. * {@link Constants#EN_START} and {@link Constants#EN_END}.
  174. * @param alignLast alignment of the paragraph's last line
  175. * @param first for the text-indent property ({@code true} if the first line
  176. * of a paragraph should be indented)
  177. * @param partOverflowRecovery {@code true} if too long elements should be moved to
  178. * the next line/part
  179. * @param maxFlagCount maximum allowed number of consecutive lines ending at a flagged penalty
  180. * item
  181. */
  182. public BreakingAlgorithm(int align, int alignLast,
  183. boolean first, boolean partOverflowRecovery,
  184. int maxFlagCount) {
  185. this.alignment = align;
  186. this.alignmentLast = alignLast;
  187. this.indentFirstPart = first;
  188. this.partOverflowRecoveryActivated = partOverflowRecovery;
  189. this.best = new BestRecords();
  190. this.maxFlaggedPenaltiesCount = maxFlagCount;
  191. }
  192. /**
  193. * Class recording all the informations of a feasible breaking point.
  194. */
  195. public class KnuthNode {
  196. /** index of the breakpoint represented by this node */
  197. public final int position;
  198. /** number of the line ending at this breakpoint */
  199. public final int line;
  200. /** fitness class of the line ending at this breakpoint. One of 0, 1, 2, 3. */
  201. public final int fitness;
  202. /** accumulated width of the KnuthElements up to after this breakpoint. */
  203. public final int totalWidth;
  204. /** accumulated stretchability of the KnuthElements up to after this breakpoint. */
  205. public final int totalStretch;
  206. /** accumulated shrinkability of the KnuthElements up to after this breakpoint. */
  207. public final int totalShrink;
  208. /** adjustment ratio if the line ends at this breakpoint */
  209. public final double adjustRatio;
  210. /** available stretch of the line ending at this breakpoint */
  211. public final int availableShrink;
  212. /** available shrink of the line ending at this breakpoint */
  213. public final int availableStretch;
  214. /** difference between target and actual line width */
  215. public final int difference;
  216. /** minimum total demerits up to this breakpoint */
  217. public double totalDemerits;
  218. /** best node for the preceding breakpoint */
  219. public KnuthNode previous;
  220. /** next possible node in the same line */
  221. public KnuthNode next;
  222. /**
  223. * Holds the number of subsequent recovery attempty that are made to get content fit
  224. * into a line.
  225. */
  226. public int fitRecoveryCounter;
  227. /**
  228. * Construct node.
  229. * @param position an integer
  230. * @param line an integer
  231. * @param fitness an integer
  232. * @param totalWidth an integer
  233. * @param totalStretch an integer
  234. * @param totalShrink an integer
  235. * @param adjustRatio a real number
  236. * @param availableShrink an integer
  237. * @param availableStretch an integer
  238. * @param difference an integer
  239. * @param totalDemerits a real number
  240. * @param previous a node
  241. */
  242. public KnuthNode(int position, int line, int fitness,
  243. int totalWidth, int totalStretch, int totalShrink,
  244. double adjustRatio, int availableShrink, int availableStretch,
  245. int difference, double totalDemerits, KnuthNode previous) {
  246. this.position = position;
  247. this.line = line;
  248. this.fitness = fitness;
  249. this.totalWidth = totalWidth;
  250. this.totalStretch = totalStretch;
  251. this.totalShrink = totalShrink;
  252. this.adjustRatio = adjustRatio;
  253. this.availableShrink = availableShrink;
  254. this.availableStretch = availableStretch;
  255. this.difference = difference;
  256. this.totalDemerits = totalDemerits;
  257. this.previous = previous;
  258. }
  259. /** {@inheritDoc} */
  260. public String toString() {
  261. return "<KnuthNode at " + position + " "
  262. + totalWidth + "+" + totalStretch + "-" + totalShrink
  263. + " line:" + line + " prev:" + (previous != null ? previous.position : -1)
  264. + " dem:" + totalDemerits
  265. + " fitness:" + FitnessClasses.NAMES[fitness] + ">";
  266. }
  267. }
  268. /** Class that stores, for each fitness class, the best active node that could start
  269. * a line of the corresponding fitness ending at the current element.
  270. */
  271. protected class BestRecords {
  272. private static final double INFINITE_DEMERITS = Double.POSITIVE_INFINITY;
  273. private final double[] bestDemerits = new double[4];
  274. private final KnuthNode[] bestNode = new KnuthNode[4];
  275. private final double[] bestAdjust = new double[4];
  276. private final int[] bestDifference = new int[4];
  277. private final int[] bestAvailableShrink = new int[4];
  278. private final int[] bestAvailableStretch = new int[4];
  279. /** Points to the fitness class which currently leads to the best demerits. */
  280. private int bestIndex = -1;
  281. /** default constructor */
  282. public BestRecords() {
  283. reset();
  284. }
  285. /** Registers the new best active node for the given fitness class.
  286. * @param demerits the total demerits of the new optimal set of breakpoints
  287. * @param node the node starting the line ending at the current element
  288. * @param adjust adjustment ratio of the current line
  289. * @param availableShrink how much the current line can be shrinked
  290. * @param availableStretch how much the current line can be stretched
  291. * @param difference difference between the width of the considered line and the
  292. * width of the "real" line
  293. * @param fitness fitness class of the current line
  294. */
  295. public void addRecord(double demerits, KnuthNode node, double adjust,
  296. int availableShrink, int availableStretch,
  297. int difference, int fitness) {
  298. if (demerits > bestDemerits[fitness]) {
  299. log.error("New demerits value greater than the old one");
  300. }
  301. bestDemerits[fitness] = demerits;
  302. bestNode[fitness] = node;
  303. bestAdjust[fitness] = adjust;
  304. bestAvailableShrink[fitness] = availableShrink;
  305. bestAvailableStretch[fitness] = availableStretch;
  306. bestDifference[fitness] = difference;
  307. if (bestIndex == -1 || demerits < bestDemerits[bestIndex]) {
  308. bestIndex = fitness;
  309. }
  310. }
  311. /** @return true if has records (best index not -1) */
  312. public boolean hasRecords() {
  313. return (bestIndex != -1);
  314. }
  315. /**
  316. * @param fitness fitness class (0, 1, 2 or 3, i.e. "tight" to "very loose")
  317. * @return true if there is a set of feasible breakpoints registered for the
  318. * given fitness.
  319. */
  320. public boolean notInfiniteDemerits(int fitness) {
  321. return (bestDemerits[fitness] != INFINITE_DEMERITS);
  322. }
  323. /**
  324. * @param fitness to use
  325. * @return best demerits
  326. */
  327. public double getDemerits(int fitness) {
  328. return bestDemerits[fitness];
  329. }
  330. /**
  331. * @param fitness to use
  332. * @return best node
  333. */
  334. public KnuthNode getNode(int fitness) {
  335. return bestNode[fitness];
  336. }
  337. /**
  338. * @param fitness to use
  339. * @return adjustment
  340. */
  341. public double getAdjust(int fitness) {
  342. return bestAdjust[fitness];
  343. }
  344. /**
  345. * @param fitness to use
  346. * @return available shrink
  347. */
  348. public int getAvailableShrink(int fitness) {
  349. return bestAvailableShrink[fitness];
  350. }
  351. /**
  352. * @param fitness to use
  353. * @return available stretch
  354. */
  355. public int getAvailableStretch(int fitness) {
  356. return bestAvailableStretch[fitness];
  357. }
  358. /**
  359. * @param fitness to use
  360. * @return difference
  361. */
  362. public int getDifference(int fitness) {
  363. return bestDifference[fitness];
  364. }
  365. /** @return minimum demerits */
  366. public double getMinDemerits() {
  367. if (bestIndex != -1) {
  368. return getDemerits(bestIndex);
  369. } else {
  370. // anyway, this should never happen
  371. return INFINITE_DEMERITS;
  372. }
  373. }
  374. /** Reset when a new breakpoint is being considered. */
  375. public void reset() {
  376. for (int i = 0; i < 4; i++) {
  377. bestDemerits[i] = INFINITE_DEMERITS;
  378. // there is no need to reset the other arrays
  379. }
  380. bestIndex = -1;
  381. }
  382. }
  383. /**
  384. * @return the number of times the algorithm should try to move overflowing content to the
  385. * next line/page.
  386. */
  387. protected int getMaxRecoveryAttempts() {
  388. return MAX_RECOVERY_ATTEMPTS;
  389. }
  390. /**
  391. * Controls the behaviour of the algorithm in cases where the first element of a part
  392. * overflows a line/page.
  393. * @return true if the algorithm should try to send the element to the next line/page.
  394. */
  395. protected boolean isPartOverflowRecoveryActivated() {
  396. return this.partOverflowRecoveryActivated;
  397. }
  398. protected KnuthNode getLastTooLong() {
  399. return lastTooLong;
  400. }
  401. /**
  402. * Empty method, hook for subclasses. Called before determining the optimal
  403. * breakpoints corresponding to a given active node.
  404. * @param total number of lines for the active node
  405. * @param demerits total demerits of the paragraph for the active node
  406. */
  407. public abstract void updateData1(int total, double demerits);
  408. /**
  409. * Empty method, hook for subclasses. Called when determining the optimal breakpoints
  410. * for a given active node.
  411. * @param bestActiveNode a node in the chain of best active nodes, corresponding to
  412. * one of the optimal breakpoints
  413. * @param sequence the corresponding paragraph
  414. * @param total the number of lines into which the paragraph will be broken
  415. */
  416. public abstract void updateData2(KnuthNode bestActiveNode,
  417. KnuthSequence sequence,
  418. int total);
  419. /** @param lineWidth the line width */
  420. public void setConstantLineWidth(int lineWidth) {
  421. this.lineWidth = lineWidth;
  422. }
  423. /**
  424. * @param par the paragraph to break
  425. * @param threshold upper bound of the adjustment ratio
  426. * @param force {@code true} if a set of breakpoints must be found, even
  427. * if there are no feasible ones
  428. * @param allowedBreaks the type(s) of breaks allowed. One of {@link #ONLY_FORCED_BREAKS},
  429. * {@link #NO_FLAGGED_PENALTIES} or {@link #ALL_BREAKS}.
  430. *
  431. * @return the number of effective breaks
  432. * @see #findBreakingPoints(KnuthSequence, int, double, boolean, int)
  433. */
  434. public int findBreakingPoints(KnuthSequence par,
  435. double threshold,
  436. boolean force,
  437. int allowedBreaks) {
  438. return findBreakingPoints(par, 0, threshold, force, allowedBreaks);
  439. }
  440. /**
  441. * Finds an optimal set of breakpoints for the given paragraph.
  442. *
  443. * @param par the paragraph to break
  444. * @param startIndex index of the Knuth element at which the breaking must start
  445. * @param threshold upper bound of the adjustment ratio
  446. * @param force {@code true} if a set of breakpoints must be found, even
  447. * if there are no feasible ones
  448. * @param allowedBreaks the type(s) of breaks allowed. One of {@link #ONLY_FORCED_BREAKS},
  449. * {@link #NO_FLAGGED_PENALTIES} or {@link #ALL_BREAKS}.
  450. *
  451. * @return the number of effective breaks
  452. */
  453. public int findBreakingPoints(KnuthSequence par, int startIndex,
  454. double threshold, boolean force,
  455. int allowedBreaks) {
  456. this.par = par;
  457. this.threshold = threshold;
  458. this.force = force;
  459. // initialize the algorithm
  460. initialize();
  461. // previous element in the paragraph is a KnuthBox?
  462. boolean previousIsBox = false;
  463. // index of the first KnuthBox in the sequence, in case of non-centered
  464. // alignment. For centered alignment, we need to take into account preceding
  465. // penalties+glues used for the filler spaces
  466. int previousPosition = startIndex;
  467. if (alignment != Constants.EN_CENTER) {
  468. int firstBoxIndex = par.getFirstBoxIndex(startIndex);
  469. previousPosition = (firstBoxIndex >= par.size()) ? startIndex : firstBoxIndex - 1;
  470. }
  471. previousPosition = (previousPosition < 0) ? 0 : previousPosition;
  472. // create an active node representing the starting point
  473. addNode(0, createNode(previousPosition, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, null));
  474. KnuthNode lastForced = getNode(0);
  475. if (log.isTraceEnabled()) {
  476. log.trace("Looping over " + (par.size() - startIndex) + " elements");
  477. log.trace(par);
  478. }
  479. // main loop
  480. for (int elementIndex = startIndex; elementIndex < par.size(); elementIndex++) {
  481. previousIsBox = handleElementAt(
  482. elementIndex, previousIsBox, allowedBreaks).isBox();
  483. if (activeNodeCount == 0) {
  484. if (handlingFloat()) {
  485. return handleFloat();
  486. }
  487. if (getIPDdifference() != 0) {
  488. return handleIpdChange();
  489. }
  490. if (!force) {
  491. log.debug("Could not find a set of breaking points " + threshold);
  492. return 0;
  493. }
  494. // lastDeactivated was a "good" break, while lastTooShort and lastTooLong
  495. // were "bad" breaks since the beginning;
  496. // if it is not the node we just restarted from, lastDeactivated can
  497. // replace either lastTooShort or lastTooLong
  498. if (lastDeactivated != null
  499. && lastDeactivated != lastForced) {
  500. replaceLastDeactivated();
  501. }
  502. if (lastTooShort == null
  503. || lastForced.position == lastTooShort.position) {
  504. lastForced = recoverFromOverflow();
  505. } else {
  506. lastForced = lastTooShort;
  507. this.lastRecovered = null;
  508. }
  509. elementIndex = restartFrom(lastForced, elementIndex);
  510. }
  511. }
  512. finish();
  513. // there is at least one set of breaking points
  514. // select one or more active nodes, removing the others from the list
  515. int line = filterActiveNodes();
  516. // for each active node, create a set of breaking points
  517. for (int i = startLine; i < endLine; i++) {
  518. for (KnuthNode node = getNode(i); node != null; node = node.next) {
  519. updateData1(node.line, node.totalDemerits);
  520. calculateBreakPoints(node, par, node.line);
  521. }
  522. }
  523. activeLines = null;
  524. return line;
  525. }
  526. /**
  527. * obtain ipd difference
  528. * @return an integer
  529. */
  530. protected int getIPDdifference() {
  531. return 0;
  532. }
  533. /**
  534. * handle ipd change
  535. * @return an integer
  536. */
  537. protected int handleIpdChange() {
  538. throw new IllegalStateException();
  539. }
  540. /**
  541. * Recover from a {@link KnuthNode} leading to a line that is too long.
  542. * The default implementation creates a new node corresponding to a break
  543. * point after the previous node that led to a line that was too short.
  544. *
  545. * @param lastTooLong the node that leads to a "too long" line
  546. * @return node corresponding to a breakpoint after the previous "too short" line
  547. */
  548. protected KnuthNode recoverFromTooLong(KnuthNode lastTooLong) {
  549. if (log.isDebugEnabled()) {
  550. log.debug("Recovering from too long: " + lastTooLong);
  551. }
  552. // if lastTooLong would be the very first break in the blockList, and
  553. // the first element in the paragraph is not a penalty, add an auxiliary
  554. // penalty now to make it possible to create a genuine 'empty' node that
  555. // represents a break before the first box/glue
  556. if (lastTooLong.previous.previous == null) {
  557. ListElement el = (ListElement)this.par.get(0);
  558. if (!el.isPenalty()) {
  559. this.par.add(0, KnuthPenalty.DUMMY_ZERO_PENALTY);
  560. }
  561. }
  562. // content would overflow, insert empty line/page and try again
  563. return createNode(
  564. lastTooLong.previous.position, lastTooLong.previous.line + 1, 1,
  565. 0, 0, 0,
  566. 0, 0, 0,
  567. 0, 0, lastTooLong.previous);
  568. }
  569. /** Initializes the algorithm's variables. */
  570. protected void initialize() {
  571. this.totalWidth = 0;
  572. this.totalStretch = 0;
  573. this.totalShrink = 0;
  574. this.lastTooShort = null;
  575. this.lastTooLong = null;
  576. this.startLine = 0;
  577. this.endLine = 0;
  578. this.activeLines = new KnuthNode[20];
  579. }
  580. /**
  581. * Creates a new active node for a feasible breakpoint at the given position. Only
  582. * called in forced mode.
  583. *
  584. * @param position index of the element in the Knuth sequence
  585. * @param line number of the line ending at the breakpoint
  586. * @param fitness fitness class of the line ending at the breakpoint. One of 0, 1, 2, 3.
  587. * @param totalWidth accumulated width of the KnuthElements up to after the breakpoint
  588. * @param totalStretch accumulated stretchability of the KnuthElements up to after the
  589. * breakpoint
  590. * @param totalShrink accumulated shrinkability of the KnuthElements up to after the
  591. * breakpoint
  592. * @param adjustRatio adjustment ratio if the line ends at this breakpoint
  593. * @param availableShrink available stretch of the line ending at this breakpoint
  594. * @param availableStretch available shrink of the line ending at this breakpoint
  595. * @param difference difference between target and actual line width
  596. * @param totalDemerits minimum total demerits up to the breakpoint
  597. * @param previous active node for the preceding breakpoint
  598. * @return a new node
  599. */
  600. protected KnuthNode createNode(int position, int line, int fitness,
  601. int totalWidth, int totalStretch, int totalShrink,
  602. double adjustRatio, int availableShrink, int availableStretch,
  603. int difference, double totalDemerits, KnuthNode previous) {
  604. return new KnuthNode(position, line, fitness,
  605. totalWidth, totalStretch, totalShrink,
  606. adjustRatio, availableShrink, availableStretch,
  607. difference, totalDemerits, previous);
  608. }
  609. /** Creates a new active node for a break from the best active node of the given
  610. * fitness class to the element at the given position.
  611. * @param position index of the element in the Knuth sequence
  612. * @param line number of the line ending at the breakpoint
  613. * @param fitness fitness class of the line ending at the breakpoint. One of 0, 1, 2, 3.
  614. * @param totalWidth accumulated width of the KnuthElements up to after the breakpoint
  615. * @param totalStretch accumulated stretchability of the KnuthElements up to after the
  616. * breakpoint
  617. * @param totalShrink accumulated shrinkability of the KnuthElements up to after the
  618. * breakpoint
  619. * @return a new node
  620. * @see #createNode(int, int, int, int, int, int, double, int, int, int, double,
  621. * org.apache.fop.layoutmgr.BreakingAlgorithm.KnuthNode)
  622. * @see BreakingAlgorithm.BestRecords
  623. */
  624. protected KnuthNode createNode(int position, int line, int fitness,
  625. int totalWidth, int totalStretch, int totalShrink) {
  626. return new KnuthNode(position, line, fitness,
  627. totalWidth, totalStretch, totalShrink, best.getAdjust(fitness),
  628. best.getAvailableShrink(fitness), best.getAvailableStretch(fitness),
  629. best.getDifference(fitness), best.getDemerits(fitness),
  630. best.getNode(fitness));
  631. }
  632. /**
  633. * Return the last node that yielded a too short line.
  634. * @return the node corresponding to the last too short line
  635. */
  636. protected final KnuthNode getLastTooShort() {
  637. return this.lastTooShort;
  638. }
  639. /**
  640. * Generic handler for a {@link KnuthElement} at the given {@code position},
  641. * taking into account whether the preceding element was a box, and which
  642. * type(s) of breaks are allowed.
  643. * Non-overridable. This method simply serves to route the call to one of the
  644. * more specific handlers ({@link #handleBox(KnuthBox)},
  645. * {@link #handleGlueAt(KnuthGlue,int,boolean,int)} or
  646. * {@link #handlePenaltyAt(KnuthPenalty,int,int)}. The specialized handlers
  647. * can be overridden by subclasses to add to or modify the default behavior
  648. * for the different types of elements.
  649. *
  650. * @param position the position index of the element in the paragraph
  651. * @param previousIsBox {@code true} if the previous element is a box
  652. * @param allowedBreaks the type(s) of breaks allowed; should be one
  653. * of {@link #ALL_BREAKS}, {@link #NO_FLAGGED_PENALTIES}
  654. * or {@link #ONLY_FORCED_BREAKS}
  655. * @return the handled element
  656. */
  657. protected final KnuthElement handleElementAt(int position,
  658. boolean previousIsBox,
  659. int allowedBreaks) {
  660. KnuthElement element = getElement(position);
  661. if (element.isBox()) {
  662. handleBox((KnuthBox) element);
  663. } else if (element.isGlue()) {
  664. handleGlueAt((KnuthGlue) element, position, previousIsBox, allowedBreaks);
  665. } else if (element.isPenalty()) {
  666. handlePenaltyAt((KnuthPenalty) element, position, allowedBreaks);
  667. } else {
  668. throw new IllegalArgumentException(
  669. "Unknown KnuthElement type: expecting KnuthBox, KnuthGlue or KnuthPenalty");
  670. }
  671. return element;
  672. }
  673. /**
  674. * Handle a {@link KnuthBox}.
  675. * <br><em>Note: default implementation just adds the box's width
  676. * to the total content width. Subclasses that do not keep track
  677. * of this themselves, but override this method, should remember
  678. * to call {@code super.handleBox(box)} to avoid unwanted side-effects.</em>
  679. *
  680. * @param box the {@link KnuthBox} to handle
  681. */
  682. protected void handleBox(KnuthBox box) {
  683. // a KnuthBox object is not a legal line break,
  684. // just add the width to the total
  685. totalWidth += box.getWidth();
  686. }
  687. /**
  688. * Handle a {@link KnuthGlue} at the given position,
  689. * taking into account the additional parameters.
  690. *
  691. * @param glue the {@link KnuthGlue} to handle
  692. * @param position the position of the glue in the list
  693. * @param previousIsBox {@code true} if the preceding element is a box
  694. * @param allowedBreaks the type of breaks that are allowed
  695. */
  696. protected void handleGlueAt(KnuthGlue glue, int position,
  697. boolean previousIsBox, int allowedBreaks) {
  698. // a KnuthGlue object is a legal line break
  699. // only if the previous object is a KnuthBox
  700. // consider these glues according to the value of allowedBreaks
  701. if (previousIsBox
  702. && !(allowedBreaks == ONLY_FORCED_BREAKS)) {
  703. considerLegalBreak(glue, position);
  704. }
  705. totalWidth += glue.getWidth();
  706. totalStretch += glue.getStretch();
  707. totalShrink += glue.getShrink();
  708. }
  709. /**
  710. * Handle a {@link KnuthPenalty} at the given position,
  711. * taking into account the type of breaks allowed.
  712. *
  713. * @param penalty the {@link KnuthPenalty} to handle
  714. * @param position the position of the penalty in the list
  715. * @param allowedBreaks the type of breaks that are allowed
  716. */
  717. protected void handlePenaltyAt(KnuthPenalty penalty, int position,
  718. int allowedBreaks) {
  719. // a KnuthPenalty is a legal line break
  720. // only if its penalty is not infinite;
  721. // consider all penalties, non-flagged penalties or non-forcing penalties
  722. // according to the value of allowedBreaks
  723. if (((penalty.getPenalty() < KnuthElement.INFINITE)
  724. && (!(allowedBreaks == NO_FLAGGED_PENALTIES) || !penalty.isPenaltyFlagged())
  725. && (!(allowedBreaks == ONLY_FORCED_BREAKS)
  726. || penalty.isForcedBreak()))) {
  727. considerLegalBreak(penalty, position);
  728. }
  729. }
  730. /**
  731. * Replace the last too-long or too-short node by the last deactivated
  732. * node, if applicable.
  733. */
  734. protected final void replaceLastDeactivated() {
  735. if (lastDeactivated.adjustRatio > 0) {
  736. //last deactivated was too short
  737. lastTooShort = lastDeactivated;
  738. } else {
  739. //last deactivated was too long or exactly the right width
  740. lastTooLong = lastDeactivated;
  741. }
  742. }
  743. /**
  744. * Recover from an overflow condition.
  745. *
  746. * @return the new {@code lastForced} node
  747. */
  748. protected KnuthNode recoverFromOverflow() {
  749. KnuthNode lastForced;
  750. if (isPartOverflowRecoveryActivated()) {
  751. if (lastRecovered == null) {
  752. lastRecovered = lastTooLong;
  753. if (log.isDebugEnabled()) {
  754. log.debug("Recovery point: " + lastRecovered);
  755. }
  756. }
  757. KnuthNode node = recoverFromTooLong(lastTooLong);
  758. lastForced = node;
  759. node.fitRecoveryCounter = lastTooLong.previous.fitRecoveryCounter + 1;
  760. if (log.isDebugEnabled()) {
  761. log.debug("first part doesn't fit into line, recovering: "
  762. + node.fitRecoveryCounter);
  763. }
  764. if (node.fitRecoveryCounter > getMaxRecoveryAttempts()) {
  765. while (lastForced.fitRecoveryCounter > 0
  766. && lastForced.previous != null) {
  767. lastForced = lastForced.previous;
  768. lastDeactivated = lastForced.previous;
  769. }
  770. lastForced = lastRecovered;
  771. lastRecovered = null;
  772. startLine = lastForced.line;
  773. endLine = lastForced.line;
  774. log.debug("rolled back...");
  775. }
  776. } else {
  777. lastForced = lastTooLong;
  778. }
  779. return lastForced;
  780. }
  781. /**
  782. * Restart from the given node at the given index.
  783. *
  784. * @param restartingNode the {@link KnuthNode} to restart from
  785. * @param currentIndex the current position index
  786. * @return the index of the restart point
  787. */
  788. protected int restartFrom(KnuthNode restartingNode, int currentIndex) {
  789. if (log.isDebugEnabled()) {
  790. log.debug("Restarting at node " + restartingNode);
  791. }
  792. restartingNode.totalDemerits = 0;
  793. addNode(restartingNode.line, restartingNode);
  794. startLine = restartingNode.line;
  795. endLine = startLine + 1;
  796. totalWidth = restartingNode.totalWidth;
  797. totalStretch = restartingNode.totalStretch;
  798. totalShrink = restartingNode.totalShrink;
  799. lastTooShort = null;
  800. lastTooLong = null;
  801. // the width, stretch and shrink already include the width,
  802. // stretch and shrink of the suppressed glues;
  803. // advance in the sequence in order to avoid taking into account
  804. // these elements twice
  805. int restartingIndex = restartingNode.position;
  806. while (restartingIndex + 1 < par.size()
  807. && !(getElement(restartingIndex + 1).isBox())) {
  808. restartingIndex++;
  809. }
  810. return restartingIndex;
  811. }
  812. /**
  813. * Determines if the given breakpoint is a feasible breakpoint. That is, if a decent
  814. * line may be built between one of the currently active nodes and this breakpoint.
  815. * @param element the paragraph's element to consider
  816. * @param elementIdx the element's index inside the paragraph
  817. */
  818. protected void considerLegalBreak(KnuthElement element, int elementIdx) {
  819. if (log.isTraceEnabled()) {
  820. log.trace("considerLegalBreak() at " + elementIdx
  821. + " (" + totalWidth + "+" + totalStretch + "-" + totalShrink
  822. + "), parts/lines: " + startLine + "-" + endLine);
  823. log.trace("\tCurrent active node list: " + activeNodeCount + " " + this.toString("\t"));
  824. }
  825. lastDeactivated = null;
  826. lastTooLong = null;
  827. for (int line = startLine; line < endLine; line++) {
  828. for (KnuthNode node = getNode(line); node != null; node = node.next) {
  829. if (node.position == elementIdx) {
  830. continue;
  831. }
  832. int difference = computeDifference(node, element, elementIdx);
  833. if (!elementCanEndLine(element, endLine, difference)) {
  834. log.trace("Skipping legal break");
  835. break;
  836. }
  837. double r = computeAdjustmentRatio(node, difference);
  838. int availableShrink = totalShrink - node.totalShrink;
  839. int availableStretch = totalStretch - node.totalStretch;
  840. if (log.isTraceEnabled()) {
  841. log.trace("\tr=" + r + " difference=" + difference);
  842. log.trace("\tline=" + line);
  843. }
  844. if (element.isForcedBreak() && handlingFloat()) {
  845. disableFloatHandling(); // so that we do not create a float edge position later
  846. }
  847. // The line would be too long.
  848. if (r < -1 || element.isForcedBreak() || handlingFloat()) {
  849. deactivateNode(node, line);
  850. }
  851. int fitnessClass = FitnessClasses.computeFitness(r);
  852. double demerits = computeDemerits(node, element, fitnessClass, r);
  853. // The line is within the available shrink and the threshold.
  854. if (r >= -1 && r <= threshold) {
  855. activateNode(node, difference, r,
  856. demerits, fitnessClass, availableShrink, availableStretch);
  857. }
  858. // The line is way too short/long, but we are in forcing mode, so a node is
  859. // calculated and stored in lastValidNode.
  860. if (force && (r <= -1 || r > threshold)) {
  861. forceNode(node, line, elementIdx, difference, r,
  862. demerits, fitnessClass, availableShrink, availableStretch);
  863. }
  864. }
  865. addBreaks(line, elementIdx);
  866. }
  867. }
  868. /**
  869. * Check if the given {@link KnuthElement} can end the line with the given
  870. * number.
  871. * @param element the element
  872. * @param line the line number
  873. * @param difference an integer
  874. * @return {@code true} if the element can end the line
  875. */
  876. protected boolean elementCanEndLine(KnuthElement element, int line, int difference) {
  877. return (!element.isPenalty()
  878. || element.getPenalty() < KnuthElement.INFINITE);
  879. }
  880. /**
  881. * Force the given {@link KnuthNode}, and register it.
  882. *
  883. * @param node the node
  884. * @param line the line number
  885. * @param elementIdx the position index of the element
  886. * @param difference the difference between content-length and available width
  887. * @param r the adjustment ratio
  888. * @param demerits demerits produced by the node
  889. * @param fitnessClass the fitness class
  890. * @param availableShrink the available amount of shrink
  891. * @param availableStretch tha available amount of stretch
  892. */
  893. protected void forceNode(KnuthNode node,
  894. int line,
  895. int elementIdx,
  896. int difference,
  897. double r,
  898. double demerits,
  899. int fitnessClass,
  900. int availableShrink,
  901. int availableStretch) {
  902. int newWidth = totalWidth;
  903. int newStretch = totalStretch;
  904. int newShrink = totalShrink;
  905. // add the width, stretch and shrink of glue elements after
  906. // the break
  907. // this does not affect the dimension of the line / page, only
  908. // the values stored in the node; these would be as if the break
  909. // was just before the next box element, thus ignoring glues and
  910. // penalties between the "real" break and the following box
  911. for (int i = elementIdx; i < par.size(); i++) {
  912. KnuthElement tempElement = getElement(i);
  913. if (tempElement.isBox()) {
  914. break;
  915. } else if (tempElement.isGlue()) {
  916. newWidth += tempElement.getWidth();
  917. newStretch += tempElement.getStretch();
  918. newShrink += tempElement.getShrink();
  919. } else if (tempElement.isForcedBreak() && i != elementIdx) {
  920. break;
  921. }
  922. }
  923. createForcedNodes(node, line, elementIdx, difference, r, demerits, fitnessClass, availableShrink,
  924. availableStretch, newWidth, newStretch, newShrink);
  925. }
  926. protected void createForcedNodes(KnuthNode node, int line, int elementIdx, int difference, double r,
  927. double demerits, int fitnessClass, int availableShrink, int availableStretch, int newWidth,
  928. int newStretch, int newShrink) {
  929. if (r <= -1) {
  930. log.debug("Considering tooLong, demerits=" + demerits);
  931. if (lastTooLong == null || demerits < lastTooLong.totalDemerits) {
  932. lastTooLong = createNode(elementIdx, line + 1, fitnessClass,
  933. newWidth, newStretch, newShrink,
  934. r, availableShrink, availableStretch,
  935. difference, demerits, node);
  936. if (log.isTraceEnabled()) {
  937. log.trace("Picking tooLong " + lastTooLong);
  938. }
  939. }
  940. } else {
  941. if (lastTooShort == null || demerits <= lastTooShort.totalDemerits) {
  942. if (considerTooShort) {
  943. // consider possibilities which are too short
  944. best.addRecord(demerits, node, r, availableShrink, availableStretch, difference,
  945. fitnessClass);
  946. }
  947. lastTooShort = createNode(elementIdx, line + 1, fitnessClass,
  948. newWidth, newStretch, newShrink,
  949. r, availableShrink, availableStretch,
  950. difference, demerits, node);
  951. if (log.isTraceEnabled()) {
  952. log.trace("Picking tooShort " + lastTooShort);
  953. }
  954. }
  955. }
  956. }
  957. /**
  958. * Activate the given node. Will result in the given {@link KnuthNode}
  959. * being registered as a feasible breakpoint, if the {@code demerits} are better
  960. * than that of the best node registered for the given {@code fitnessClass}.
  961. *
  962. * @param node the node
  963. * @param difference the difference between content-length and available width
  964. * @param r the adjustment ratio
  965. * @param demerits demerits produced by the node
  966. * @param fitnessClass the fitness class
  967. * @param availableShrink the available amount of shrink
  968. * @param availableStretch the available amount of stretch
  969. */
  970. protected void activateNode(KnuthNode node,
  971. int difference,
  972. double r,
  973. double demerits,
  974. int fitnessClass,
  975. int availableShrink,
  976. int availableStretch) {
  977. if (log.isTraceEnabled()) {
  978. log.trace("\tDemerits=" + demerits);
  979. log.trace("\tFitness class=" + FitnessClasses.NAMES[fitnessClass]);
  980. }
  981. if (demerits < best.getDemerits(fitnessClass)) {
  982. // updates best demerits data
  983. best.addRecord(demerits, node, r, availableShrink, availableStretch,
  984. difference, fitnessClass);
  985. lastTooShort = null;
  986. }
  987. }
  988. /**
  989. * Deactivate the given node
  990. *
  991. * @param node the node
  992. * @param line the line number
  993. */
  994. protected void deactivateNode(KnuthNode node, int line) {
  995. // Deactivate node...
  996. if (log.isTraceEnabled()) {
  997. log.trace("Removing " + node);
  998. }
  999. removeNode(line, node);
  1000. // ... and remember it, if it was a good candidate
  1001. lastDeactivated = compareNodes(lastDeactivated, node);
  1002. }
  1003. /**
  1004. * Adds new active nodes for breaks at the given element.
  1005. * @param line number of the previous line; this element will end line number (line+1)
  1006. * @param elementIdx the element's index
  1007. */
  1008. private void addBreaks(int line, int elementIdx) {
  1009. if (!best.hasRecords()) {
  1010. return;
  1011. }
  1012. int newWidth = totalWidth;
  1013. int newStretch = totalStretch;
  1014. int newShrink = totalShrink;
  1015. // add the width, stretch and shrink of glue elements after
  1016. // the break
  1017. // this does not affect the dimension of the line / page, only
  1018. // the values stored in the node; these would be as if the break
  1019. // was just before the next box element, thus ignoring glues and
  1020. // penalties between the "real" break and the following box
  1021. for (int i = elementIdx; i < par.size(); i++) {
  1022. KnuthElement tempElement = getElement(i);
  1023. if (tempElement.isBox()) {
  1024. break;
  1025. } else if (tempElement.isGlue()) {
  1026. newWidth += tempElement.getWidth();
  1027. newStretch += tempElement.getStretch();
  1028. newShrink += tempElement.getShrink();
  1029. } else if (tempElement.isForcedBreak() && i != elementIdx) {
  1030. break;
  1031. }
  1032. }
  1033. // add nodes to the active nodes list
  1034. double minimumDemerits = best.getMinDemerits() + incompatibleFitnessDemerit;
  1035. for (int i = 0; i <= 3; i++) {
  1036. if (best.notInfiniteDemerits(i) && best.getDemerits(i) <= minimumDemerits) {
  1037. // the nodes in activeList must be ordered
  1038. // by line number and position;
  1039. if (log.isTraceEnabled()) {
  1040. log.trace("\tInsert new break in list of " + activeNodeCount
  1041. + " from fitness class " + FitnessClasses.NAMES[i]);
  1042. }
  1043. KnuthNode newNode = createNode(elementIdx, line + 1, i,
  1044. newWidth, newStretch, newShrink);
  1045. addNode(line + 1, newNode);
  1046. }
  1047. }
  1048. best.reset();
  1049. }
  1050. /**
  1051. * Return the difference between the natural width of a line that would be made
  1052. * between the given active node and the given element, and the available width of the
  1053. * real line.
  1054. * @param activeNode node for the previous breakpoint
  1055. * @param element currently considered breakpoint
  1056. * @param elementIndex index of the element that is considered as a breakpoint
  1057. * @return The difference in width. Positive numbers mean extra space in the line,
  1058. * negative number that the line overflows.
  1059. */
  1060. protected int computeDifference(KnuthNode activeNode, KnuthElement element,
  1061. int elementIndex) {
  1062. // compute the adjustment ratio
  1063. int actualWidth = totalWidth - activeNode.totalWidth;
  1064. if (element.isPenalty()) {
  1065. actualWidth += element.getWidth();
  1066. }
  1067. return getLineWidth() - actualWidth;
  1068. }
  1069. /**
  1070. * Return the adjustment ratio needed to make up for the difference. A ratio of
  1071. * <ul>
  1072. * <li>0 means that the break has the exact right width</li>
  1073. * <li>&gt;= -1 &amp;&amp; &lt; 0 means that the break is wider than the line,
  1074. * but within the minimim values of the glues.</li>
  1075. * <li>&gt;0 &amp;&amp; &lt; 1 means that the break is smaller than the line width,
  1076. * but within the maximum values of the glues.</li>
  1077. * <li>&gt; 1 means that the break is too small to make up for the glues.</li>
  1078. * </ul>
  1079. * @param activeNode the currently active node
  1080. * @param difference the difference between content-length and available width
  1081. * @return The adjustment ratio.
  1082. */
  1083. protected double computeAdjustmentRatio(KnuthNode activeNode, int difference) {
  1084. // compute the adjustment ratio
  1085. if (difference > 0) {
  1086. int maxAdjustment = totalStretch - activeNode.totalStretch;
  1087. if (maxAdjustment > 0) {
  1088. return (double) difference / maxAdjustment;
  1089. } else {
  1090. return INFINITE_RATIO;
  1091. }
  1092. } else if (difference < 0) {
  1093. int maxAdjustment = totalShrink - activeNode.totalShrink;
  1094. if (maxAdjustment > 0) {
  1095. return (double) difference / maxAdjustment;
  1096. } else {
  1097. return -INFINITE_RATIO;
  1098. }
  1099. } else {
  1100. return 0;
  1101. }
  1102. }
  1103. /**
  1104. * Computes the demerits of the current breaking (that is, up to the given element),
  1105. * if the next-to-last chosen breakpoint is the given active node. This adds to the
  1106. * total demerits of the given active node, the demerits of a line starting at this
  1107. * node and ending at the given element.
  1108. * @param activeNode considered preceding line break
  1109. * @param element considered current line break
  1110. * @param fitnessClass fitness of the current line
  1111. * @param r adjustment ratio for the current line
  1112. * @return the demerit of the current line
  1113. */
  1114. protected double computeDemerits(KnuthNode activeNode, KnuthElement element,
  1115. int fitnessClass, double r) {
  1116. double demerits = 0;
  1117. // compute demerits
  1118. double f = Math.abs(r);
  1119. f = 1 + 100 * f * f * f;
  1120. if (element.isPenalty()) {
  1121. double penalty = element.getPenalty();
  1122. if (penalty >= 0) {
  1123. f += penalty;
  1124. demerits = f * f;
  1125. } else if (!element.isForcedBreak()) {
  1126. demerits = f * f - penalty * penalty;
  1127. } else {
  1128. demerits = f * f;
  1129. }
  1130. } else {
  1131. demerits = f * f;
  1132. }
  1133. if (element.isPenalty() && ((KnuthPenalty) element).isPenaltyFlagged()
  1134. && getElement(activeNode.position).isPenalty()
  1135. && ((KnuthPenalty) getElement(activeNode.position)).isPenaltyFlagged()) {
  1136. // add demerit for consecutive breaks at flagged penalties
  1137. demerits += repeatedFlaggedDemerit;
  1138. // there are at least two consecutive lines ending with a flagged penalty;
  1139. // check if the previous line end with a flagged penalty too,
  1140. // and if this situation is allowed
  1141. int flaggedPenaltiesCount = 2;
  1142. for (KnuthNode prevNode = activeNode.previous;
  1143. prevNode != null && flaggedPenaltiesCount <= maxFlaggedPenaltiesCount;
  1144. prevNode = prevNode.previous) {
  1145. KnuthElement prevElement = getElement(prevNode.position);
  1146. if (prevElement.isPenalty()
  1147. && ((KnuthPenalty) prevElement).isPenaltyFlagged()) {
  1148. // the previous line ends with a flagged penalty too
  1149. flaggedPenaltiesCount++;
  1150. } else {
  1151. // the previous line does not end with a flagged penalty,
  1152. // exit the loop
  1153. break;
  1154. }
  1155. }
  1156. if (maxFlaggedPenaltiesCount >= 1
  1157. && flaggedPenaltiesCount > maxFlaggedPenaltiesCount) {
  1158. // add infinite demerits, so this break will not be chosen
  1159. // unless there isn't any alternative break
  1160. demerits += BestRecords.INFINITE_DEMERITS;
  1161. }
  1162. }
  1163. if (Math.abs(fitnessClass - activeNode.fitness) > 1) {
  1164. // add demerit for consecutive breaks
  1165. // with very different fitness classes
  1166. demerits += incompatibleFitnessDemerit;
  1167. }
  1168. demerits += activeNode.totalDemerits;
  1169. return demerits;
  1170. }
  1171. /**
  1172. * Hook for subclasses to trigger special behavior after ending the
  1173. * main loop in {@link #findBreakingPoints(KnuthSequence,int,double,boolean,int)}
  1174. */
  1175. protected void finish() {
  1176. if (log.isTraceEnabled()) {
  1177. log.trace("Main loop completed " + activeNodeCount);
  1178. log.trace("Active nodes=" + toString(""));
  1179. }
  1180. }
  1181. /**
  1182. * Return the element at index idx in the paragraph.
  1183. * @param idx index of the element.
  1184. * @return the element at index idx in the paragraph.
  1185. */
  1186. protected KnuthElement getElement(int idx) {
  1187. return (KnuthElement) par.get(idx);
  1188. }
  1189. /**
  1190. * Compare two KnuthNodes and return the node with the least demerit.
  1191. * @param node1 The first knuth node.
  1192. * @param node2 The other knuth node.
  1193. * @return the node with the least demerit.
  1194. */
  1195. protected KnuthNode compareNodes(KnuthNode node1, KnuthNode node2) {
  1196. if (node1 == null || node2.position > node1.position) {
  1197. return node2;
  1198. }
  1199. if (node2.position == node1.position) {
  1200. if (node2.totalDemerits < node1.totalDemerits) {
  1201. return node2;
  1202. }
  1203. }
  1204. return node1;
  1205. }
  1206. /**
  1207. * Add a node at the end of the given line's existing active nodes.
  1208. * If this is the first node in the line, adjust endLine accordingly.
  1209. * @param line number of the line ending at the node's corresponding breakpoint
  1210. * @param node the active node to add
  1211. */
  1212. protected void addNode(int line, KnuthNode node) {
  1213. int headIdx = line * 2;
  1214. if (headIdx >= activeLines.length) {
  1215. KnuthNode[] oldList = activeLines;
  1216. activeLines = new KnuthNode[headIdx + headIdx];
  1217. System.arraycopy(oldList, 0, activeLines, 0, oldList.length);
  1218. }
  1219. node.next = null;
  1220. if (activeLines[headIdx + 1] != null) {
  1221. activeLines[headIdx + 1].next = node;
  1222. } else {
  1223. activeLines[headIdx] = node;
  1224. endLine = line + 1;
  1225. }
  1226. activeLines[headIdx + 1] = node;
  1227. activeNodeCount++;
  1228. }
  1229. /**
  1230. * Remove the given active node registered for the given line. If there are no more active nodes
  1231. * for this line, adjust the startLine accordingly.
  1232. * @param line number of the line ending at the node's corresponding breakpoint
  1233. * @param node the node to deactivate
  1234. */
  1235. protected void removeNode(int line, KnuthNode node) {
  1236. int headIdx = line * 2;
  1237. KnuthNode n = getNode(line);
  1238. if (n != node) {
  1239. // nodes could be rightly deactivated in a different order
  1240. KnuthNode prevNode = null;
  1241. while (n != node) {
  1242. prevNode = n;
  1243. n = n.next;
  1244. }
  1245. prevNode.next = n.next;
  1246. if (prevNode.next == null) {
  1247. activeLines[headIdx + 1] = prevNode;
  1248. }
  1249. } else {
  1250. activeLines[headIdx] = node.next;
  1251. if (node.next == null) {
  1252. activeLines[headIdx + 1] = null;
  1253. }
  1254. while (startLine < endLine && getNode(startLine) == null) {
  1255. startLine++;
  1256. }
  1257. }
  1258. activeNodeCount--;
  1259. }
  1260. /**
  1261. * Returns the first active node for the given line.
  1262. * @param line the line/part number
  1263. * @return the requested active node
  1264. */
  1265. protected KnuthNode getNode(int line) {
  1266. return activeLines[line * 2];
  1267. }
  1268. /**
  1269. * Returns the line/part width of a given line/part.
  1270. * @param line the line/part number
  1271. * @return the width/length in millipoints
  1272. */
  1273. protected int getLineWidth(int line) {
  1274. assert lineWidth >= 0;
  1275. return this.lineWidth;
  1276. }
  1277. /** @return the constant line/part width or -1 if there is no such value */
  1278. protected int getLineWidth() {
  1279. return this.lineWidth;
  1280. }
  1281. /**
  1282. * Creates a string representation of the active nodes. Used for debugging.
  1283. * @param prepend a string to prepend on each entry
  1284. * @return the requested string
  1285. */
  1286. public String toString(String prepend) {
  1287. StringBuffer sb = new StringBuffer();
  1288. sb.append("[\n");
  1289. for (int i = startLine; i < endLine; i++) {
  1290. for (KnuthNode node = getNode(i); node != null; node = node.next) {
  1291. sb.append(prepend).append('\t').append(node).append(",\n");
  1292. }
  1293. }
  1294. sb.append(prepend).append("]");
  1295. return sb.toString();
  1296. }
  1297. /**
  1298. * Filter active nodes.
  1299. * @return an integer
  1300. */
  1301. protected abstract int filterActiveNodes();
  1302. /**
  1303. * Determines the set of optimal breakpoints corresponding to the given active node.
  1304. * @param node the active node
  1305. * @param par the corresponding paragraph
  1306. * @param total the number of lines into which the paragraph will be broken
  1307. */
  1308. protected void calculateBreakPoints(KnuthNode node, KnuthSequence par,
  1309. int total) {
  1310. KnuthNode bestActiveNode = node;
  1311. // use bestActiveNode to determine the optimum breakpoints
  1312. for (int i = node.line; i > 0; i--) {
  1313. updateData2(bestActiveNode, par, total);
  1314. bestActiveNode = bestActiveNode.previous;
  1315. }
  1316. }
  1317. /** @return the alignment for normal lines/parts */
  1318. public int getAlignment() {
  1319. return this.alignment;
  1320. }
  1321. /** @return the alignment for the last line/part */
  1322. public int getAlignmentLast() {
  1323. return this.alignmentLast;
  1324. }
  1325. protected boolean handlingFloat() {
  1326. return false;
  1327. }
  1328. protected int handleFloat() {
  1329. throw new IllegalStateException();
  1330. }
  1331. protected void disableFloatHandling() {
  1332. throw new IllegalStateException();
  1333. }
  1334. }