1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351 |
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- /* $Id$ */
-
- package org.apache.fop.layoutmgr;
-
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
-
- import org.apache.fop.fo.Constants;
-
- /**
- * The set of nodes is sorted into lines indexed into activeLines.
- * The nodes in each line are linked together in a single linked list by the
- * {@link KnuthNode#next} field. The activeLines array contains a link to the head of
- * the linked list in index 'line*2' and a link to the tail at index 'line*2+1'.
- * <p>
- * The set of active nodes can be traversed by
- * <pre>
- * for (int line = startLine; line < endLine; line++) {
- * for (KnuthNode node = getNode(line); node != null; node = node.next) {
- * // Do something with 'node'
- * }
- * }
- * </pre>
- */
- public abstract class BreakingAlgorithm {
-
- /** the logger for the class */
- protected static Log log = LogFactory.getLog(BreakingAlgorithm.class);
-
- /** Maximum adjustment ration */
- protected static final int INFINITE_RATIO = 1000;
-
- private static final int MAX_RECOVERY_ATTEMPTS = 5;
-
- // constants identifying a subset of the feasible breaks
- /** All feasible breaks are ok. */
- public static final int ALL_BREAKS = 0;
- /** This forbids hyphenation. */
- public static final int NO_FLAGGED_PENALTIES = 1;
- /** wrap-option = "no-wrap". */
- public static final int ONLY_FORCED_BREAKS = 2;
-
- /** Holder for symbolic literals for the fitness classes */
- static final class FitnessClasses {
- static final int VERY_TIGHT = 0;
- static final int TIGHT = 1;
- static final int LOOSE = 2;
- static final int VERY_LOOSE = 3;
-
- static final String[] NAMES = { "VERY TIGHT", "TIGHT", "LOOSE", "VERY LOOSE" };
-
- /**
- * Figure out the fitness class of this line (tight, loose,
- * very tight or very loose).
- * See the section on "More Bells and Whistles" in Knuth's
- * "Breaking Paragraphs Into Lines".
- *
- * @param adjustRatio the adjustment ratio
- * @return the fitness class
- */
- static int computeFitness(double adjustRatio) {
- if (adjustRatio < -0.5) {
- return FitnessClasses.VERY_TIGHT;
- } else if (adjustRatio <= 0.5) {
- return FitnessClasses.TIGHT;
- } else if (adjustRatio <= 1.0) {
- return FitnessClasses.LOOSE;
- } else {
- return FitnessClasses.VERY_LOOSE;
- }
- }
- }
-
- // parameters of Knuth's algorithm:
- /** Demerit for consecutive lines ending at flagged penalties. */
- protected int repeatedFlaggedDemerit = KnuthPenalty.FLAGGED_PENALTY;
- /** Demerit for consecutive lines belonging to incompatible fitness classes . */
- protected int incompatibleFitnessDemerit = KnuthPenalty.FLAGGED_PENALTY;
- /** Maximum number of consecutive lines ending with a flagged penalty.
- * Only a value >= 1 is a significant limit.
- */
- protected int maxFlaggedPenaltiesCount;
-
- /**
- * The threshold for considering breaks to be acceptable. The adjustment ratio must be
- * inferior to this threshold.
- */
- private double threshold;
-
- /**
- * The paragraph of KnuthElements.
- */
- protected KnuthSequence par;
-
- /**
- * The width of a line (or height of a column in page-breaking mode).
- * -1 indicates that the line widths are different for each line.
- */
- protected int lineWidth = -1;
- /** Force the algorithm to find a set of breakpoints, even if no feasible breakpoints
- * exist.
- */
- private boolean force = false;
- /** If set to true, doesn't ignore break possibilities which are definitely too short. */
- protected boolean considerTooShort = false;
-
- /** When in forced mode, the best node leading to a too long line. The line will be
- * too long anyway, but this one will lead to a paragraph with fewest demerits.
- */
- private KnuthNode lastTooLong;
- /** When in forced mode, the best node leading to a too short line. The line will be
- * too short anyway, but this one will lead to a paragraph with fewest demerits.
- */
- private KnuthNode lastTooShort;
- /** The node to be reactivated if no set of feasible breakpoints can be found for this
- * paragraph.
- */
- private KnuthNode lastDeactivated;
-
- /** Alignment of the paragraph/page. One of EN_START, EN_JUSTIFY, etc. */
- protected int alignment;
- /** Alignment of the paragraph's last line. */
- protected int alignmentLast;
- /** Used to handle the text-indent property (indent the first line of a paragraph). */
- protected boolean indentFirstPart;
-
- /**
- * The set of active nodes in ascending line order. For each line l, activeLines[2l] contains a
- * link to l's first active node, and activeLines[2l+1] a link to l's last active node. The
- * line number l corresponds to the number of the line ending at the node's breakpoint.
- */
- protected KnuthNode[] activeLines;
-
- /**
- * The number of active nodes.
- */
- protected int activeNodeCount;
-
- /**
- * The lowest available line in the set of active nodes.
- */
- protected int startLine = 0;
-
- /**
- * The highest + 1 available line in the set of active nodes.
- */
- protected int endLine = 0;
-
- /**
- * The total width of all elements handled so far.
- */
- protected int totalWidth;
-
- /**
- * The total stretch of all elements handled so far.
- */
- protected int totalStretch = 0;
-
- /**
- * The total shrink of all elements handled so far.
- */
- protected int totalShrink = 0;
-
- protected BestRecords best;
-
- private boolean partOverflowRecoveryActivated = true;
- private KnuthNode lastRecovered;
-
- /**
- * Create a new instance.
- *
- * @param align alignment of the paragraph/page. One of {@link Constants#EN_START},
- * {@link Constants#EN_JUSTIFY}, {@link Constants#EN_CENTER},
- * {@link Constants#EN_END}.
- * For pages, {@link Constants#EN_BEFORE} and {@link Constants#EN_AFTER}
- * are mapped to the corresponding inline properties,
- * {@link Constants#EN_START} and {@link Constants#EN_END}.
- * @param alignLast alignment of the paragraph's last line
- * @param first for the text-indent property ({@code true} if the first line
- * of a paragraph should be indented)
- * @param partOverflowRecovery {@code true} if too long elements should be moved to
- * the next line/part
- * @param maxFlagCount maximum allowed number of consecutive lines ending at a flagged penalty
- * item
- */
- public BreakingAlgorithm(int align, int alignLast,
- boolean first, boolean partOverflowRecovery,
- int maxFlagCount) {
- this.alignment = align;
- this.alignmentLast = alignLast;
- this.indentFirstPart = first;
- this.partOverflowRecoveryActivated = partOverflowRecovery;
- this.best = new BestRecords();
- this.maxFlaggedPenaltiesCount = maxFlagCount;
- }
-
-
- /**
- * Class recording all the informations of a feasible breaking point.
- */
- public class KnuthNode {
- /** index of the breakpoint represented by this node */
- public final int position;
-
- /** number of the line ending at this breakpoint */
- public final int line;
-
- /** fitness class of the line ending at this breakpoint. One of 0, 1, 2, 3. */
- public final int fitness;
-
- /** accumulated width of the KnuthElements up to after this breakpoint. */
- public final int totalWidth;
-
- /** accumulated stretchability of the KnuthElements up to after this breakpoint. */
- public final int totalStretch;
-
- /** accumulated shrinkability of the KnuthElements up to after this breakpoint. */
- public final int totalShrink;
-
- /** adjustment ratio if the line ends at this breakpoint */
- public final double adjustRatio;
-
- /** available stretch of the line ending at this breakpoint */
- public final int availableShrink;
-
- /** available shrink of the line ending at this breakpoint */
- public final int availableStretch;
-
- /** difference between target and actual line width */
- public final int difference;
-
- /** minimum total demerits up to this breakpoint */
- public double totalDemerits;
-
- /** best node for the preceding breakpoint */
- public KnuthNode previous;
-
- /** next possible node in the same line */
- public KnuthNode next;
-
- /**
- * Holds the number of subsequent recovery attempty that are made to get content fit
- * into a line.
- */
- public int fitRecoveryCounter = 0;
-
- public KnuthNode(int position, int line, int fitness,
- int totalWidth, int totalStretch, int totalShrink,
- double adjustRatio, int availableShrink, int availableStretch,
- int difference, double totalDemerits, KnuthNode previous) {
- this.position = position;
- this.line = line;
- this.fitness = fitness;
- this.totalWidth = totalWidth;
- this.totalStretch = totalStretch;
- this.totalShrink = totalShrink;
- this.adjustRatio = adjustRatio;
- this.availableShrink = availableShrink;
- this.availableStretch = availableStretch;
- this.difference = difference;
- this.totalDemerits = totalDemerits;
- this.previous = previous;
- }
-
- public String toString() {
- return "<KnuthNode at " + position + " "
- + totalWidth + "+" + totalStretch + "-" + totalShrink
- + " line:" + line + " prev:" + (previous != null ? previous.position : -1)
- + " dem:" + totalDemerits
- + " fitness:" + FitnessClasses.NAMES[fitness] + ">";
- }
- }
-
- /** Class that stores, for each fitness class, the best active node that could start
- * a line of the corresponding fitness ending at the current element.
- */
- protected class BestRecords {
- private static final double INFINITE_DEMERITS = Double.POSITIVE_INFINITY;
-
- private double[] bestDemerits = new double[4];
- private KnuthNode[] bestNode = new KnuthNode[4];
- private double[] bestAdjust = new double[4];
- private int[] bestDifference = new int[4];
- private int[] bestAvailableShrink = new int[4];
- private int[] bestAvailableStretch = new int[4];
- /** Points to the fitness class which currently leads to the best demerits. */
- private int bestIndex = -1;
-
- public BestRecords() {
- reset();
- }
-
- /** Registers the new best active node for the given fitness class.
- * @param demerits the total demerits of the new optimal set of breakpoints
- * @param node the node starting the line ending at the current element
- * @param adjust adjustment ratio of the current line
- * @param availableShrink how much the current line can be shrinked
- * @param availableStretch how much the current line can be stretched
- * @param difference difference between the width of the considered line and the
- * width of the "real" line
- * @param fitness fitness class of the current line
- */
- public void addRecord(double demerits, KnuthNode node, double adjust,
- int availableShrink, int availableStretch,
- int difference, int fitness) {
- if (demerits > bestDemerits[fitness]) {
- log.error("New demerits value greater than the old one");
- }
- bestDemerits[fitness] = demerits;
- bestNode[fitness] = node;
- bestAdjust[fitness] = adjust;
- bestAvailableShrink[fitness] = availableShrink;
- bestAvailableStretch[fitness] = availableStretch;
- bestDifference[fitness] = difference;
- if (bestIndex == -1 || demerits < bestDemerits[bestIndex]) {
- bestIndex = fitness;
- }
- }
-
- public boolean hasRecords() {
- return (bestIndex != -1);
- }
-
- /**
- * @param fitness fitness class (0, 1, 2 or 3, i.e. "tight" to "very loose")
- * @return true if there is a set of feasible breakpoints registered for the
- * given fitness.
- */
- public boolean notInfiniteDemerits(int fitness) {
- return (bestDemerits[fitness] != INFINITE_DEMERITS);
- }
-
- public double getDemerits(int fitness) {
- return bestDemerits[fitness];
- }
-
- public KnuthNode getNode(int fitness) {
- return bestNode[fitness];
- }
-
- public double getAdjust(int fitness) {
- return bestAdjust[fitness];
- }
-
- public int getAvailableShrink(int fitness) {
- return bestAvailableShrink[fitness];
- }
-
- public int getAvailableStretch(int fitness) {
- return bestAvailableStretch[fitness];
- }
-
- public int getDifference(int fitness) {
- return bestDifference[fitness];
- }
-
- public double getMinDemerits() {
- if (bestIndex != -1) {
- return getDemerits(bestIndex);
- } else {
- // anyway, this should never happen
- return INFINITE_DEMERITS;
- }
- }
-
- /** Reset when a new breakpoint is being considered. */
- public void reset() {
- for (int i = 0; i < 4; i++) {
- bestDemerits[i] = INFINITE_DEMERITS;
- // there is no need to reset the other arrays
- }
- bestIndex = -1;
- }
- }
-
- /**
- * @return the number of times the algorithm should try to move overflowing content to the
- * next line/page.
- */
- protected int getMaxRecoveryAttempts() {
- return MAX_RECOVERY_ATTEMPTS;
- }
-
- /**
- * Controls the behaviour of the algorithm in cases where the first element of a part
- * overflows a line/page.
- * @return true if the algorithm should try to send the element to the next line/page.
- */
- protected boolean isPartOverflowRecoveryActivated() {
- return this.partOverflowRecoveryActivated;
- }
-
- /**
- * Empty method, hook for subclasses. Called before determining the optimal
- * breakpoints corresponding to a given active node.
- * @param total number of lines for the active node
- * @param demerits total demerits of the paragraph for the active node
- */
- public abstract void updateData1(int total, double demerits);
-
- /**
- * Empty method, hook for subclasses. Called when determining the optimal breakpoints
- * for a given active node.
- * @param bestActiveNode a node in the chain of best active nodes, corresponding to
- * one of the optimal breakpoints
- * @param sequence the corresponding paragraph
- * @param total the number of lines into which the paragraph will be broken
- */
- public abstract void updateData2(KnuthNode bestActiveNode,
- KnuthSequence sequence,
- int total);
-
- public void setConstantLineWidth(int lineWidth) {
- this.lineWidth = lineWidth;
- }
-
- /** @see #findBreakingPoints(KnuthSequence, int, double, boolean, int) */
- public int findBreakingPoints(KnuthSequence par,
- double threshold,
- boolean force,
- int allowedBreaks) {
- return findBreakingPoints(par, 0, threshold, force, allowedBreaks);
- }
-
- /**
- * Finds an optimal set of breakpoints for the given paragraph.
- *
- * @param par the paragraph to break
- * @param startIndex index of the Knuth element at which the breaking must start
- * @param threshold upper bound of the adjustment ratio
- * @param force {@code true} if a set of breakpoints must be found, even
- * if there are no feasible ones
- * @param allowedBreaks the type(s) of breaks allowed. One of {@link #ONLY_FORCED_BREAKS},
- * {@link #NO_FLAGGED_PENALTIES} or {@link #ALL_BREAKS}.
- *
- * @return the number of effective breaks
- */
- public int findBreakingPoints(KnuthSequence par, int startIndex,
- double threshold, boolean force,
- int allowedBreaks) {
- this.par = par;
- this.threshold = threshold;
- this.force = force;
-
- // initialize the algorithm
- initialize();
-
- // previous element in the paragraph is a KnuthBox?
- boolean previousIsBox = false;
-
- // index of the first KnuthBox in the sequence, in case of non-centered
- // alignment. For centered alignment, we need to take into account preceding
- // penalties+glues used for the filler spaces
- int firstBoxIndex = startIndex;
- if (alignment != Constants.EN_CENTER) {
- firstBoxIndex = par.getFirstBoxIndex(startIndex);
- }
- firstBoxIndex = (firstBoxIndex < 0) ? 0 : firstBoxIndex;
-
- // create an active node representing the starting point
- addNode(0, createNode(firstBoxIndex, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, null));
- KnuthNode lastForced = getNode(0);
-
-
- if (log.isTraceEnabled()) {
- log.trace("Looping over " + (par.size() - startIndex) + " elements");
- log.trace(par);
- }
-
- // main loop
- for (int elementIndex = startIndex; elementIndex < par.size(); elementIndex++) {
-
- previousIsBox = handleElementAt(
- elementIndex, previousIsBox, allowedBreaks).isBox();
-
- if (activeNodeCount == 0) {
- if (ipdChanged()) {
- return handleIpdChange();
- }
- if (!force) {
- log.debug("Could not find a set of breaking points " + threshold);
- return 0;
- }
-
- // lastDeactivated was a "good" break, while lastTooShort and lastTooLong
- // were "bad" breaks since the beginning;
- // if it is not the node we just restarted from, lastDeactivated can
- // replace either lastTooShort or lastTooLong
- if (lastDeactivated != null
- && lastDeactivated != lastForced) {
- replaceLastDeactivated();
- }
-
- if (lastTooShort == null
- || lastForced.position == lastTooShort.position) {
- lastForced = recoverFromOverflow();
- } else {
- lastForced = lastTooShort;
- this.lastRecovered = null;
- }
- elementIndex = restartFrom(lastForced, elementIndex);
- }
-
- }
-
- finish();
-
- // there is at least one set of breaking points
- // select one or more active nodes, removing the others from the list
- int line = filterActiveNodes();
-
- // for each active node, create a set of breaking points
- for (int i = startLine; i < endLine; i++) {
- for (KnuthNode node = getNode(i); node != null; node = node.next) {
- updateData1(node.line, node.totalDemerits);
- calculateBreakPoints(node, par, node.line);
- }
- }
-
- activeLines = null;
- return line;
- }
-
- protected boolean ipdChanged() {
- return false;
- }
-
- protected int handleIpdChange() {
- throw new IllegalStateException();
- }
-
- /**
- * Recover from a {@link KnuthNode} leading to a line that is too long.
- * The default implementation creates a new node corresponding to a break
- * point after the previous node that led to a line that was too short.
- *
- * @param lastTooLong the node that leads to a "too long" line
- * @return node corresponding to a breakpoint after the previous "too short" line
- */
- protected KnuthNode recoverFromTooLong(KnuthNode lastTooLong) {
- if (log.isDebugEnabled()) {
- log.debug("Recovering from too long: " + lastTooLong);
- }
-
- // content would overflow, insert empty line/page and try again
- return createNode(
- lastTooLong.previous.position, lastTooLong.previous.line + 1, 1,
- 0, 0, 0,
- 0, 0, 0,
- 0, 0, lastTooLong.previous);
- }
-
- /** Initializes the algorithm's variables. */
- protected void initialize() {
- this.totalWidth = 0;
- this.totalStretch = 0;
- this.totalShrink = 0;
- this.lastTooShort = this.lastTooLong = null;
- this.startLine = this.endLine = 0;
- this.activeLines = new KnuthNode[20];
- }
-
- /**
- * Creates a new active node for a feasible breakpoint at the given position. Only
- * called in forced mode.
- *
- * @param position index of the element in the Knuth sequence
- * @param line number of the line ending at the breakpoint
- * @param fitness fitness class of the line ending at the breakpoint. One of 0, 1, 2, 3.
- * @param totalWidth accumulated width of the KnuthElements up to after the breakpoint
- * @param totalStretch accumulated stretchability of the KnuthElements up to after the
- * breakpoint
- * @param totalShrink accumulated shrinkability of the KnuthElements up to after the
- * breakpoint
- * @param adjustRatio adjustment ratio if the line ends at this breakpoint
- * @param availableShrink available stretch of the line ending at this breakpoint
- * @param availableStretch available shrink of the line ending at this breakpoint
- * @param difference difference between target and actual line width
- * @param totalDemerits minimum total demerits up to the breakpoint
- * @param previous active node for the preceding breakpoint
- * @return a new node
- */
- protected KnuthNode createNode(int position, int line, int fitness,
- int totalWidth, int totalStretch, int totalShrink,
- double adjustRatio, int availableShrink, int availableStretch,
- int difference, double totalDemerits, KnuthNode previous) {
- return new KnuthNode(position, line, fitness,
- totalWidth, totalStretch, totalShrink,
- adjustRatio, availableShrink, availableStretch,
- difference, totalDemerits, previous);
- }
-
- /** Creates a new active node for a break from the best active node of the given
- * fitness class to the element at the given position.
- * @see #createNode(int, int, int, int, int, int, double, int, int, int, double, org.apache.fop.layoutmgr.BreakingAlgorithm.KnuthNode)
- * @see BreakingAlgorithm.BestRecords
- */
- protected KnuthNode createNode(int position, int line, int fitness,
- int totalWidth, int totalStretch, int totalShrink) {
- return new KnuthNode(position, line, fitness,
- totalWidth, totalStretch, totalShrink, best.getAdjust(fitness),
- best.getAvailableShrink(fitness), best.getAvailableStretch(fitness),
- best.getDifference(fitness), best.getDemerits(fitness),
- best.getNode(fitness));
- }
-
- /**
- * Return the last node that yielded a too short line.
- * @return the node corresponding to the last too short line
- */
- protected final KnuthNode getLastTooShort() {
- return this.lastTooShort;
- }
-
- /**
- * Generic handler for a {@link KnuthElement} at the given {@code position},
- * taking into account whether the preceding element was a box, and which
- * type(s) of breaks are allowed.
- * Non-overridable. This method simply serves to route the call to one of the
- * more specific handlers ({@link #handleBox(KnuthBox)},
- * {@link #handleGlueAt(KnuthGlue,int,boolean,int)} or
- * {@link #handlePenaltyAt(KnuthPenalty,int,int)}. The specialized handlers
- * can be overridden by subclasses to add to or modify the default behavior
- * for the different types of elements.
- *
- * @param position the position index of the element in the paragraph
- * @param previousIsBox {@code true} if the previous element is a box
- * @param allowedBreaks the type(s) of breaks allowed; should be one
- * of {@link #ALL_BREAKS}, {@link #NO_FLAGGED_PENALTIES}
- * or {@link #ONLY_FORCED_BREAKS}
- * @return the handled element
- */
- protected final KnuthElement handleElementAt(int position,
- boolean previousIsBox,
- int allowedBreaks) {
- KnuthElement element = getElement(position);
- if (element.isBox()) {
- handleBox((KnuthBox) element);
- } else if (element.isGlue()) {
- handleGlueAt((KnuthGlue) element, position, previousIsBox, allowedBreaks);
- } else if (element.isPenalty()){
- handlePenaltyAt((KnuthPenalty) element, position, allowedBreaks);
- } else {
- throw new IllegalArgumentException(
- "Unknown KnuthElement type: expecting KnuthBox, KnuthGlue or KnuthPenalty");
- }
- return element;
- }
-
- /**
- * Handle a {@link KnuthBox}.
- * <br/><em>Note: default implementation just adds the box's width
- * to the total content width. Subclasses that do not keep track
- * of this themselves, but override this method, should remember
- * to call {@code super.handleBox(box)} to avoid unwanted side-effects.</em>
- *
- * @param box the {@link KnuthBox} to handle
- */
- protected void handleBox(KnuthBox box) {
- // a KnuthBox object is not a legal line break,
- // just add the width to the total
- totalWidth += box.getW();
- }
-
- /**
- * Handle a {@link KnuthGlue} at the given position,
- * taking into account the additional parameters.
- *
- * @param glue the {@link KnuthGlue} to handle
- * @param position the position of the glue in the list
- * @param previousIsBox {@code true} if the preceding element is a box
- * @param allowedBreaks the type of breaks that are allowed
- */
- protected void handleGlueAt(KnuthGlue glue, int position,
- boolean previousIsBox, int allowedBreaks) {
- // a KnuthGlue object is a legal line break
- // only if the previous object is a KnuthBox
- // consider these glues according to the value of allowedBreaks
- if (previousIsBox
- && !(allowedBreaks == ONLY_FORCED_BREAKS)) {
- considerLegalBreak(glue, position);
- }
- totalWidth += glue.getW();
- totalStretch += glue.getY();
- totalShrink += glue.getZ();
- }
-
- /**
- * Handle a {@link KnuthPenalty} at the given position,
- * taking into account the type of breaks allowed.
- *
- * @param penalty the {@link KnuthPenalty} to handle
- * @param position the position of the penalty in the list
- * @param allowedBreaks the type of breaks that are allowed
- */
- protected void handlePenaltyAt(KnuthPenalty penalty, int position,
- int allowedBreaks) {
- // a KnuthPenalty is a legal line break
- // only if its penalty is not infinite;
- // consider all penalties, non-flagged penalties or non-forcing penalties
- // according to the value of allowedBreaks
- if (((penalty.getP() < KnuthElement.INFINITE)
- && (!(allowedBreaks == NO_FLAGGED_PENALTIES) || !penalty.isFlagged())
- && (!(allowedBreaks == ONLY_FORCED_BREAKS)
- || penalty.isForcedBreak()))) {
- considerLegalBreak(penalty, position);
- }
- }
-
- /**
- * Replace the last too-long or too-short node by the last deactivated
- * node, if applicable.
- */
- protected final void replaceLastDeactivated() {
- if (lastDeactivated.adjustRatio > 0) {
- //last deactivated was too short
- lastTooShort = lastDeactivated;
- } else {
- //last deactivated was too long or exactly the right width
- lastTooLong = lastDeactivated;
- }
- }
-
- /**
- * Recover from an overflow condition.
- *
- * @return the new {@code lastForced} node
- */
- protected KnuthNode recoverFromOverflow() {
- KnuthNode lastForced;
- if (isPartOverflowRecoveryActivated()) {
- if (lastRecovered == null) {
- lastRecovered = lastTooLong;
- if (log.isDebugEnabled()) {
- log.debug("Recovery point: " + lastRecovered);
- }
- }
- KnuthNode node = recoverFromTooLong(lastTooLong);
- lastForced = node;
- node.fitRecoveryCounter = lastTooLong.previous.fitRecoveryCounter + 1;
- if (log.isDebugEnabled()) {
- log.debug("first part doesn't fit into line, recovering: "
- + node.fitRecoveryCounter);
- }
- if (node.fitRecoveryCounter > getMaxRecoveryAttempts()) {
- while (lastForced.fitRecoveryCounter > 0
- && lastForced.previous != null) {
- lastForced = lastForced.previous;
- lastDeactivated = lastForced.previous;
- }
- lastForced = lastRecovered;
- lastRecovered = null;
- startLine = lastForced.line;
- endLine = lastForced.line;
- log.debug("rolled back...");
- }
- } else {
- lastForced = lastTooLong;
- }
- return lastForced;
- }
-
- /**
- * Restart from the given node at the given index.
- *
- * @param restartingNode the {@link KnuthNode} to restart from
- * @param currentIndex the current position index
- * @return the index of the restart point
- */
- protected int restartFrom(KnuthNode restartingNode, int currentIndex) {
- if (log.isDebugEnabled()) {
- log.debug("Restarting at node " + restartingNode);
- }
- restartingNode.totalDemerits = 0;
- addNode(restartingNode.line, restartingNode);
- startLine = restartingNode.line;
- endLine = startLine + 1;
- totalWidth = restartingNode.totalWidth;
- totalStretch = restartingNode.totalStretch;
- totalShrink = restartingNode.totalShrink;
- lastTooShort = null;
- lastTooLong = null;
- // the width, stretch and shrink already include the width,
- // stretch and shrink of the suppressed glues;
- // advance in the sequence in order to avoid taking into account
- // these elements twice
- int restartingIndex = restartingNode.position;
- while (restartingIndex + 1 < par.size()
- && !(getElement(restartingIndex + 1).isBox())) {
- restartingIndex++;
- }
- return restartingIndex;
- }
-
- /**
- * Determines if the given breakpoint is a feasible breakpoint. That is, if a decent
- * line may be built between one of the currently active nodes and this breakpoint.
- * @param element the paragraph's element to consider
- * @param elementIdx the element's index inside the paragraph
- */
- protected void considerLegalBreak(KnuthElement element, int elementIdx) {
-
- if (log.isTraceEnabled()) {
- log.trace("considerLegalBreak() at " + elementIdx
- + " (" + totalWidth + "+" + totalStretch + "-" + totalShrink
- + "), parts/lines: " + startLine + "-" + endLine);
- log.trace("\tCurrent active node list: " + activeNodeCount + " " + this.toString("\t"));
- }
-
- lastDeactivated = null;
- lastTooLong = null;
- for (int line = startLine; line < endLine; line++) {
- for (KnuthNode node = getNode(line); node != null; node = node.next) {
- if (node.position == elementIdx) {
- continue;
- }
- int difference = computeDifference(node, element, elementIdx);
- if (!elementCanEndLine(element, endLine, difference)) {
- log.trace("Skipping legal break");
- break;
- }
-
- double r = computeAdjustmentRatio(node, difference);
- int availableShrink = totalShrink - node.totalShrink;
- int availableStretch = totalStretch - node.totalStretch;
-
- if (log.isTraceEnabled()) {
- log.trace("\tr=" + r + " difference=" + difference);
- log.trace("\tline=" + line);
- }
-
- // The line would be too long.
- if (r < -1 || element.isForcedBreak()) {
- deactivateNode(node, line);
- }
-
- int fitnessClass = FitnessClasses.computeFitness(r);
- double demerits = computeDemerits(node, element, fitnessClass, r);
- // The line is within the available shrink and the threshold.
- if (r >= -1 && r <= threshold) {
- activateNode(node, difference, r,
- demerits, fitnessClass, availableShrink, availableStretch);
- }
-
- // The line is way too short/long, but we are in forcing mode, so a node is
- // calculated and stored in lastValidNode.
- if (force && (r <= -1 || r > threshold)) {
- forceNode(node, line, elementIdx, difference, r,
- demerits, fitnessClass, availableShrink, availableStretch);
- }
- }
- addBreaks(line, elementIdx);
- }
- }
-
- /**
- * Check if the given {@link KnuthElement} can end the line with the given
- * number.
- * @param element the element
- * @param line the line number
- * @param difference
- * @return {@code true} if the element can end the line
- */
- protected boolean elementCanEndLine(KnuthElement element, int line, int difference) {
- return (!element.isPenalty()
- || element.getP() < KnuthElement.INFINITE);
- }
-
- /**
- * Force the given {@link KnuthNode}, and register it.
- *
- * @param node the node
- * @param line the line number
- * @param elementIdx the position index of the element
- * @param difference the difference between content-length and avaialable width
- * @param r the adjustment ratio
- * @param demerits demerits produced by the node
- * @param fitnessClass the fitness class
- * @param availableShrink the available amount of shrink
- * @param availableStretch tha available amount of stretch
- */
- protected void forceNode(KnuthNode node,
- int line,
- int elementIdx,
- int difference,
- double r,
- double demerits,
- int fitnessClass,
- int availableShrink,
- int availableStretch) {
-
- int newWidth = totalWidth;
- int newStretch = totalStretch;
- int newShrink = totalShrink;
-
- // add the width, stretch and shrink of glue elements after
- // the break
- // this does not affect the dimension of the line / page, only
- // the values stored in the node; these would be as if the break
- // was just before the next box element, thus ignoring glues and
- // penalties between the "real" break and the following box
- for (int i = elementIdx; i < par.size(); i++) {
- KnuthElement tempElement = getElement(i);
- if (tempElement.isBox()) {
- break;
- } else if (tempElement.isGlue()) {
- newWidth += tempElement.getW();
- newStretch += tempElement.getY();
- newShrink += tempElement.getZ();
- } else if (tempElement.isForcedBreak() && i != elementIdx) {
- break;
- }
- }
-
- if (r <= -1) {
- log.debug("Considering tooLong, demerits=" + demerits);
- if (lastTooLong == null || demerits < lastTooLong.totalDemerits) {
- lastTooLong = createNode(elementIdx, line + 1, fitnessClass,
- newWidth, newStretch, newShrink,
- r, availableShrink, availableStretch,
- difference, demerits, node);
- if (log.isTraceEnabled()) {
- log.trace("Picking tooLong " + lastTooLong);
- }
- }
- } else {
- if (lastTooShort == null || demerits <= lastTooShort.totalDemerits) {
- if (considerTooShort) {
- //consider possibilities which are too short
- best.addRecord(demerits, node, r,
- availableShrink, availableStretch,
- difference, fitnessClass);
- }
- lastTooShort = createNode(elementIdx, line + 1, fitnessClass,
- newWidth, newStretch, newShrink,
- r, availableShrink, availableStretch,
- difference, demerits, node);
- if (log.isTraceEnabled()) {
- log.trace("Picking tooShort " + lastTooShort);
- }
- }
- }
- }
-
- /**
- * Activate the given node. Will result in the given {@link KnuthNode}
- * being registered as a feasible breakpoint, if the {@code demerits} are better
- * than that of the best node registered for the given {@code fitnessClass}.
- *
- * @param node the node
- * @param difference the difference between content-length and available width
- * @param r the adjustment ratio
- * @param demerits demerits produced by the node
- * @param fitnessClass the fitness class
- * @param availableShrink the available amount of shrink
- * @param availableStretch the available amount of stretch
- */
- protected void activateNode(KnuthNode node,
- int difference,
- double r,
- double demerits,
- int fitnessClass,
- int availableShrink,
- int availableStretch) {
-
- if (log.isTraceEnabled()) {
- log.trace("\tDemerits=" + demerits);
- log.trace("\tFitness class=" + FitnessClasses.NAMES[fitnessClass]);
- }
-
- if (demerits < best.getDemerits(fitnessClass)) {
- // updates best demerits data
- best.addRecord(demerits, node, r, availableShrink, availableStretch,
- difference, fitnessClass);
- lastTooShort = null;
- }
- }
-
- /**
- * Deactivate the given node
- *
- * @param node the node
- * @param line the line number
- */
- protected void deactivateNode(KnuthNode node, int line) {
- // Deactivate node...
- if (log.isTraceEnabled()) {
- log.trace("Removing " + node);
- }
- removeNode(line, node);
- // ... and remember it, if it was a good candidate
- lastDeactivated = compareNodes(lastDeactivated, node);
- }
-
- /**
- * Adds new active nodes for breaks at the given element.
- * @param line number of the previous line; this element will end line number (line+1)
- * @param elementIdx the element's index
- */
- private void addBreaks(int line, int elementIdx) {
- if (!best.hasRecords()) {
- return;
- }
-
- int newWidth = totalWidth;
- int newStretch = totalStretch;
- int newShrink = totalShrink;
-
- // add the width, stretch and shrink of glue elements after
- // the break
- // this does not affect the dimension of the line / page, only
- // the values stored in the node; these would be as if the break
- // was just before the next box element, thus ignoring glues and
- // penalties between the "real" break and the following box
- for (int i = elementIdx; i < par.size(); i++) {
- KnuthElement tempElement = getElement(i);
- if (tempElement.isBox()) {
- break;
- } else if (tempElement.isGlue()) {
- newWidth += tempElement.getW();
- newStretch += tempElement.getY();
- newShrink += tempElement.getZ();
- } else if (tempElement.isForcedBreak() && i != elementIdx) {
- break;
- }
- }
-
- // add nodes to the active nodes list
- double minimumDemerits = best.getMinDemerits() + incompatibleFitnessDemerit;
- for (int i = 0; i <= 3; i++) {
- if (best.notInfiniteDemerits(i) && best.getDemerits(i) <= minimumDemerits) {
- // the nodes in activeList must be ordered
- // by line number and position;
- if (log.isTraceEnabled()) {
- log.trace("\tInsert new break in list of " + activeNodeCount
- + " from fitness class " + FitnessClasses.NAMES[i]);
- }
- KnuthNode newNode = createNode(elementIdx, line + 1, i,
- newWidth, newStretch, newShrink);
- addNode(line + 1, newNode);
- }
- }
- best.reset();
- }
-
- /**
- * Return the difference between the natural width of a line that would be made
- * between the given active node and the given element, and the available width of the
- * real line.
- * @param activeNode node for the previous breakpoint
- * @param element currently considered breakpoint
- * @param elementIndex index of the element that is considered as a breakpoint
- * @return The difference in width. Positive numbers mean extra space in the line,
- * negative number that the line overflows.
- */
- protected int computeDifference(KnuthNode activeNode, KnuthElement element,
- int elementIndex) {
- // compute the adjustment ratio
- int actualWidth = totalWidth - activeNode.totalWidth;
- if (element.isPenalty()) {
- actualWidth += element.getW();
- }
- return getLineWidth() - actualWidth;
- }
-
- /**
- * Return the adjustment ratio needed to make up for the difference. A ratio of
- * <ul>
- * <li>0 means that the break has the exact right width</li>
- * <li>>= -1 && < 0 means that the break is wider than the line,
- * but within the minimim values of the glues.</li>
- * <li>>0 && < 1 means that the break is smaller than the line width,
- * but within the maximum values of the glues.</li>
- * <li>> 1 means that the break is too small to make up for the glues.</li>
- * </ul>
- * @param activeNode the currently active node
- * @param difference the difference between content-length and available width
- * @return The adjustment ratio.
- */
- protected double computeAdjustmentRatio(KnuthNode activeNode, int difference) {
- // compute the adjustment ratio
- if (difference > 0) {
- int maxAdjustment = totalStretch - activeNode.totalStretch;
- if (maxAdjustment > 0) {
- return (double) difference / maxAdjustment;
- } else {
- return INFINITE_RATIO;
- }
- } else if (difference < 0) {
- int maxAdjustment = totalShrink - activeNode.totalShrink;
- if (maxAdjustment > 0) {
- return (double) difference / maxAdjustment;
- } else {
- return -INFINITE_RATIO;
- }
- } else {
- return 0;
- }
- }
-
- /**
- * Computes the demerits of the current breaking (that is, up to the given element),
- * if the next-to-last chosen breakpoint is the given active node. This adds to the
- * total demerits of the given active node, the demerits of a line starting at this
- * node and ending at the given element.
- * @param activeNode considered preceding line break
- * @param element considered current line break
- * @param fitnessClass fitness of the current line
- * @param r adjustment ratio for the current line
- * @return the demerit of the current line
- */
- protected double computeDemerits(KnuthNode activeNode, KnuthElement element,
- int fitnessClass, double r) {
- double demerits = 0;
- // compute demerits
- double f = Math.abs(r);
- f = 1 + 100 * f * f * f;
- if (element.isPenalty()) {
- double penalty = element.getP();
- if (penalty >= 0) {
- f += penalty;
- demerits = f * f;
- } else if (!element.isForcedBreak()) {
- demerits = f * f - penalty * penalty;
- } else {
- demerits = f * f;
- }
- } else {
- demerits = f * f;
- }
-
- if (element.isPenalty() && ((KnuthPenalty) element).isFlagged()
- && getElement(activeNode.position).isPenalty()
- && ((KnuthPenalty) getElement(activeNode.position)).isFlagged()) {
- // add demerit for consecutive breaks at flagged penalties
- demerits += repeatedFlaggedDemerit;
- // there are at least two consecutive lines ending with a flagged penalty;
- // check if the previous line end with a flagged penalty too,
- // and if this situation is allowed
- int flaggedPenaltiesCount = 2;
- for (KnuthNode prevNode = activeNode.previous;
- prevNode != null && flaggedPenaltiesCount <= maxFlaggedPenaltiesCount;
- prevNode = prevNode.previous) {
- KnuthElement prevElement = getElement(prevNode.position);
- if (prevElement.isPenalty()
- && ((KnuthPenalty) prevElement).isFlagged()) {
- // the previous line ends with a flagged penalty too
- flaggedPenaltiesCount++;
- } else {
- // the previous line does not end with a flagged penalty,
- // exit the loop
- break;
- }
- }
- if (maxFlaggedPenaltiesCount >= 1
- && flaggedPenaltiesCount > maxFlaggedPenaltiesCount) {
- // add infinite demerits, so this break will not be chosen
- // unless there isn't any alternative break
- demerits += BestRecords.INFINITE_DEMERITS;
- }
- }
- if (Math.abs(fitnessClass - activeNode.fitness) > 1) {
- // add demerit for consecutive breaks
- // with very different fitness classes
- demerits += incompatibleFitnessDemerit;
- }
- demerits += activeNode.totalDemerits;
- return demerits;
- }
-
- /**
- * Hook for subclasses to trigger special behavior after ending the
- * main loop in {@link #findBreakingPoints(KnuthSequence,int,double,boolean,int)}
- */
- protected void finish() {
- if (log.isTraceEnabled()) {
- log.trace("Main loop completed " + activeNodeCount);
- log.trace("Active nodes=" + toString(""));
- }
- }
-
- /**
- * Return the element at index idx in the paragraph.
- * @param idx index of the element.
- * @return the element at index idx in the paragraph.
- */
- protected KnuthElement getElement(int idx) {
- return (KnuthElement) par.get(idx);
- }
-
- /**
- * Compare two KnuthNodes and return the node with the least demerit.
- * @param node1 The first knuth node.
- * @param node2 The other knuth node.
- * @return the node with the least demerit.
- */
- protected KnuthNode compareNodes(KnuthNode node1, KnuthNode node2) {
- if (node1 == null || node2.position > node1.position) {
- return node2;
- }
- if (node2.position == node1.position) {
- if (node2.totalDemerits < node1.totalDemerits) {
- return node2;
- }
- }
- return node1;
- }
-
- /**
- * Add a node at the end of the given line's existing active nodes.
- * If this is the first node in the line, adjust endLine accordingly.
- * @param line number of the line ending at the node's corresponding breakpoint
- * @param node the active node to add
- */
- protected void addNode(int line, KnuthNode node) {
- int headIdx = line * 2;
- if (headIdx >= activeLines.length) {
- KnuthNode[] oldList = activeLines;
- activeLines = new KnuthNode[headIdx + headIdx];
- System.arraycopy(oldList, 0, activeLines, 0, oldList.length);
- }
- node.next = null;
- if (activeLines[headIdx + 1] != null) {
- activeLines[headIdx + 1].next = node;
- } else {
- activeLines[headIdx] = node;
- endLine = line + 1;
- }
- activeLines[headIdx + 1] = node;
- activeNodeCount++;
- }
-
- /**
- * Remove the given active node registered for the given line. If there are no more active nodes
- * for this line, adjust the startLine accordingly.
- * @param line number of the line ending at the node's corresponding breakpoint
- * @param node the node to deactivate
- */
- protected void removeNode(int line, KnuthNode node) {
- int headIdx = line * 2;
- KnuthNode n = getNode(line);
- if (n != node) {
- // nodes could be rightly deactivated in a different order
- KnuthNode prevNode = null;
- while (n != node) {
- prevNode = n;
- n = n.next;
- }
- prevNode.next = n.next;
- if (prevNode.next == null) {
- activeLines[headIdx + 1] = prevNode;
- }
- } else {
- activeLines[headIdx] = node.next;
- if (node.next == null) {
- activeLines[headIdx + 1] = null;
- }
- while (startLine < endLine && getNode(startLine) == null) {
- startLine++;
- }
- }
- activeNodeCount--;
- }
-
- /**
- * Returns the first active node for the given line.
- * @param line the line/part number
- * @return the requested active node
- */
- protected KnuthNode getNode(int line) {
- return activeLines[line * 2];
- }
-
- /**
- * Returns the line/part width of a given line/part.
- * @param line the line/part number
- * @return the width/length in millipoints
- */
- protected int getLineWidth(int line) {
- assert lineWidth >= 0;
- return this.lineWidth;
- }
-
- /** @return the constant line/part width or -1 if there is no such value */
- protected int getLineWidth() {
- return this.lineWidth;
- }
-
- /**
- * Creates a string representation of the active nodes. Used for debugging.
- * @param prepend a string to prepend on each entry
- * @return the requested string
- */
- public String toString(String prepend) {
- StringBuffer sb = new StringBuffer();
- sb.append("[\n");
- for (int i = startLine; i < endLine; i++) {
- for (KnuthNode node = getNode(i); node != null; node = node.next) {
- sb.append(prepend).append('\t').append(node).append(",\n");
- }
- }
- sb.append(prepend).append("]");
- return sb.toString();
- }
-
- protected abstract int filterActiveNodes();
-
- /**
- * Determines the set of optimal breakpoints corresponding to the given active node.
- * @param node the active node
- * @param par the corresponding paragraph
- * @param total the number of lines into which the paragraph will be broken
- */
- protected void calculateBreakPoints(KnuthNode node, KnuthSequence par,
- int total) {
- KnuthNode bestActiveNode = node;
- // use bestActiveNode to determine the optimum breakpoints
- for (int i = node.line; i > 0; i--) {
- updateData2(bestActiveNode, par, total);
- bestActiveNode = bestActiveNode.previous;
- }
- }
-
- /** @return the alignment for normal lines/parts */
- public int getAlignment() {
- return this.alignment;
- }
-
- /** @return the alignment for the last line/part */
- public int getAlignmentLast() {
- return this.alignmentLast;
- }
-
- }
|