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

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