Browse Source

Added support for multiple multi-switch appearing on the same page.

Modified MultiSwitchLM to have its standard behaviour by default.
Moved BestFitLayoutUtils into MultiSwitchLM.
Code clean-up and javadoc.

Patch by Seifeddine Dridi, applied with some modifications.


git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_WhitespaceManagement@1585822 13f79535-47bb-0310-9956-ffa450edef68
tags/fop-2_0
Vincent Hennebert 10 years ago
parent
commit
8c3e6a5e15

+ 0
- 14
src/java/org/apache/fop/fo/flow/MultiSwitch.java View File

@@ -38,9 +38,7 @@ public class MultiSwitch extends FObj {
// private CommonAccessibility commonAccessibility;
// End of property values

private FONode currentlyVisibleMultiCase;
private String autoToggle;
private String fittingStrategy;

/**
* Base constructor
@@ -97,18 +95,6 @@ public class MultiSwitch extends FObj {
return FO_MULTI_SWITCH;
}

public void setCurrentlyVisibleNode(FONode node) {
currentlyVisibleMultiCase = node;
}

public FONode getCurrentlyVisibleNode() {
return currentlyVisibleMultiCase;
}

public String getFittingStrategy() {
return fittingStrategy;
}

public String getAutoToggle() {
return autoToggle;
}

+ 0
- 100
src/java/org/apache/fop/layoutmgr/BestFitLayoutUtils.java View File

@@ -1,100 +0,0 @@
/*
* 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.
*/

package org.apache.fop.layoutmgr;

import java.util.LinkedList;
import java.util.List;

import org.apache.fop.layoutmgr.BestFitPenalty.Variant;

/**
* Utility class used in {@link MultiSwitchLayoutManager}
* to handle the <i>best-fit</i> property value if specified in {@link org.apache.fop.fo.flow.MultiSwitch}
*/
public final class BestFitLayoutUtils {

private BestFitLayoutUtils() { }

static class BestFitPosition extends Position {

private List<ListElement> knuthList;

public BestFitPosition(LayoutManager lm) {
super(lm);
}

public List<Position> getPositionList() {
List<Position> positions = new LinkedList<Position>();
if (knuthList != null && !knuthList.isEmpty()) {
SpaceResolver.performConditionalsNotification(knuthList, 0, knuthList.size() - 1, -1);
for (ListElement el : knuthList) {
if (el.getPosition() != null) {
positions.add(el.getPosition());
}
}
}
return positions;
}

public void setKnuthList(List<ListElement> knuthList) {
this.knuthList = knuthList;
}

}

public static List<ListElement> getKnuthList(LayoutManager lm,
List<List<ListElement>> childrenLists) {

List<ListElement> knuthList = new LinkedList<ListElement>();

BestFitPenalty bestFitPenalty = new BestFitPenalty(new BestFitPosition(lm));
for (List<ListElement> childList : childrenLists) {
// TODO Doing space resolution here is not correct.
// Space elements will simply be nulled.
SpaceResolver.resolveElementList(childList);
int contentLength = ElementListUtils.calcContentLength(childList);
bestFitPenalty.addVariant(new Variant(childList, contentLength));
}
// TODO Adding two enclosing boxes is definitely a dirty hack.
// The first box forces the breaking algorithm to consider the penalty
// in case there are no elements preceding it
// and the last box prevents the glue and penalty from getting deleted
// when they are at the end of the Knuth list.
knuthList.add(new KnuthBox(0, new Position(lm), false));
knuthList.add(bestFitPenalty);
knuthList.add(new KnuthBox(0, new Position(lm), false));
return knuthList;
}

public static List<Position> getPositionList(LayoutManager lm, PositionIterator posIter) {

// "unwrap" the NonLeafPositions stored in parentIter
// and put them in a new list;
LinkedList<Position> positionList = new LinkedList<Position>();
while (posIter.hasNext()) {
Position pos = posIter.next();
if (pos instanceof BestFitPosition) {
positionList.addAll(((BestFitPosition) pos).getPositionList());
} else {
positionList.add(pos);
}
}
return positionList;
}

}

+ 2
- 12
src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java View File

@@ -451,18 +451,8 @@ public class LayoutManagerMapping implements LayoutManagerMaker {

@Override
public void make(FONode node, List lms) {
MultiSwitch multiSwitch = (MultiSwitch) node;
MultiSwitchLayoutManager mslm = new MultiSwitchLayoutManager(multiSwitch);
FONode multiCase = multiSwitch.getCurrentlyVisibleNode();
if (multiCase != null) {
FONodeIterator childIter = multiCase.getChildNodes();
while (childIter.hasNext()) {
FONode child = (FONode) childIter.next();
makeLayoutManagers(child, lms);
}
} else {
lms.add(mslm);
}
MultiSwitchLayoutManager mslm = new MultiSwitchLayoutManager((MultiSwitch) node);
lms.add(mslm);
}
}


+ 111
- 21
src/java/org/apache/fop/layoutmgr/MultiSwitchLayoutManager.java View File

@@ -17,38 +17,123 @@

package org.apache.fop.layoutmgr;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.fop.area.Area;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.flow.MultiSwitch;

public class MultiSwitchLayoutManager extends BlockStackingLayoutManager {

static class WhitespaceManagementPosition extends Position {

private List<ListElement> knuthList;

public WhitespaceManagementPosition(LayoutManager lm) {
super(lm);
}

public List<Position> getPositionList() {
List<Position> positions = new LinkedList<Position>();
if (knuthList != null && !knuthList.isEmpty()) {
SpaceResolver.performConditionalsNotification(knuthList, 0, knuthList.size() - 1, -1);
for (ListElement el : knuthList) {
if (el.getPosition() != null) {
positions.add(el.getPosition());
}
}
}
return positions;
}

public void setKnuthList(List<ListElement> knuthList) {
this.knuthList = knuthList;
}

public List<ListElement> getKnuthList() {
return knuthList;
}

}

private interface KnuthElementsGenerator {
List<ListElement> getKnuthElement(LayoutContext context, int alignment);
}

private class DefaultKnuthListGenerator implements KnuthElementsGenerator {

public List<ListElement> getKnuthElement(LayoutContext context, int alignment) {

List<ListElement> knuthList = new LinkedList<ListElement>();
LayoutManager childLM;
while ((childLM = getChildLM()) != null) {
if (!childLM.isFinished()) {
LayoutContext childLC = makeChildLayoutContext(context);
List childElements = childLM.getNextKnuthElements(childLC, alignment);
if (childElements != null) {
List<ListElement> newList = new LinkedList<ListElement>();
wrapPositionElements(childElements, newList);
knuthList.addAll(newList);
}
}
}
return knuthList;
}

}

private class WhitespaceManagement implements KnuthElementsGenerator {

public List<ListElement> getKnuthElement(LayoutContext context, int alignment) {

MultiSwitchLayoutManager mslm = MultiSwitchLayoutManager.this;
List<ListElement> knuthList = new LinkedList<ListElement>();
WhitespaceManagementPenalty penalty = new WhitespaceManagementPenalty(
new WhitespaceManagementPosition(mslm));
LayoutManager childLM;
while ((childLM = getChildLM()) != null) {
if (!childLM.isFinished()) {
LayoutContext childLC = makeChildLayoutContext(context);
List childElements = childLM.getNextKnuthElements(childLC, alignment);
if (childElements != null) {
List<ListElement> newList = new LinkedList<ListElement>();
wrapPositionElements(childElements, newList);
// TODO Doing space resolution here is wrong.
SpaceResolver.resolveElementList(newList);
int contentLength = ElementListUtils.calcContentLength(newList);
penalty.addVariant(penalty.new Variant(newList, contentLength));
}
}
}
// Prevent the penalty from being ignored if it is at the beginning of the content
knuthList.add(new KnuthBox(0, new Position(mslm), false));
knuthList.add(penalty);
// Prevent the penalty from being ignored if it is at the end of the content
knuthList.add(new KnuthBox(0, new Position(mslm), false));
return knuthList;
}

}

private KnuthElementsGenerator knuthGen;

public MultiSwitchLayoutManager(FObj node) {
super(node);
MultiSwitch multiSwitchNode = (MultiSwitch) node;
if (multiSwitchNode.getAutoToggle().equals("best-fit")) {
knuthGen = new WhitespaceManagement();
} else {
knuthGen = new DefaultKnuthListGenerator();
}
}

@Override
public List<ListElement> getNextKnuthElements(LayoutContext context, int alignment) {

referenceIPD = context.getRefIPD();
List<List<ListElement>> childrenLists = new ArrayList<List<ListElement>>();
LayoutManager childLM;
while ((childLM = getChildLM()) != null) {
if (!childLM.isFinished()) {
LayoutContext childLC = makeChildLayoutContext(context);
List childElements = childLM.getNextKnuthElements(childLC, alignment);
if (childElements != null) {
List<ListElement> newList = new LinkedList<ListElement>();
wrapPositionElements(childElements, newList);
childrenLists.add(newList);
}
}
}
List<ListElement> knuthList = knuthGen.getKnuthElement(context, alignment);
setFinished(true);
return BestFitLayoutUtils.getKnuthList(this, childrenLists);
return knuthList;
}

@Override
@@ -63,11 +148,16 @@ public class MultiSwitchLayoutManager extends BlockStackingLayoutManager {

@Override
public void addAreas(PositionIterator posIter, LayoutContext context) {

List<Position> positionList = BestFitLayoutUtils.getPositionList(this, posIter);
PositionIterator newPosIter = new PositionIterator(
positionList.listIterator());

LinkedList<Position> positionList = new LinkedList<Position>();
while (posIter.hasNext()) {
Position pos = posIter.next();
if (pos instanceof WhitespaceManagementPosition) {
positionList.addAll(((WhitespaceManagementPosition) pos).getPositionList());
} else {
positionList.add(pos);
}
}
PositionIterator newPosIter = new PositionIterator(positionList.listIterator());
AreaAdditionUtil.addAreas(this, newPosIter, context);
flush();
}

+ 38
- 31
src/java/org/apache/fop/layoutmgr/PageBreakingAlgorithm.java View File

@@ -30,7 +30,7 @@ import org.apache.commons.logging.LogFactory;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FObj;
import org.apache.fop.layoutmgr.AbstractBreaker.PageBreakPosition;
import org.apache.fop.layoutmgr.BestFitPenalty.Variant;
import org.apache.fop.layoutmgr.WhitespaceManagementPenalty.Variant;
import org.apache.fop.traits.MinOptMax;
import org.apache.fop.util.ListUtil;

@@ -153,10 +153,13 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
/** Index of the last inserted element of the last inserted footnote. */
public int footnoteElementIndex;

/** Current active variant attached to this node */
public final Variant variant;
/** Pending variant to be assigned to all descending nodes */
public Variant pendingVariant;
/**
* Pending variants of dynamic contents that were evaluated WRT this node.
* When computing page difference for a break element, the total width of these variants
* will be added to 'actualWidth'.
*/
private final List<Variant> pendingVariants = new ArrayList<Variant>();
private int totalVariantsWidth;

public KnuthPageNode(int position,
int line, int fitness,
@@ -164,7 +167,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
int insertedFootnotes, int totalFootnotes,
int footnoteListIndex, int footnoteElementIndex,
double adjustRatio, int availableShrink, int availableStretch,
int difference, double totalDemerits, KnuthNode previous, Variant variant) {
int difference, double totalDemerits, KnuthNode previous) {
super(position, line, fitness,
totalWidth, totalStretch, totalShrink,
adjustRatio, availableShrink, availableStretch,
@@ -173,7 +176,11 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
this.insertedFootnotes = insertedFootnotes;
this.footnoteListIndex = footnoteListIndex;
this.footnoteElementIndex = footnoteElementIndex;
this.variant = variant;
}

public void addVariant(Variant variant) {
pendingVariants.add(variant);
totalVariantsWidth += variant.width;
}

}
@@ -188,7 +195,6 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
private final int[] bestTotalFootnotesLength = new int[4];
private final int[] bestFootnoteListIndex = new int[4];
private final int[] bestFootnoteElementIndex = new int[4];
private final Variant[] bestVariant = new Variant[4];

@Override
public void addRecord(double demerits, KnuthNode node, double adjust,
@@ -201,7 +207,6 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
bestTotalFootnotesLength[fitness] = totalFootnotesLength;
bestFootnoteListIndex[fitness] = footnoteListIndex;
bestFootnoteElementIndex[fitness] = footnoteElementIndex;
bestVariant[fitness] = ((KnuthPageNode) node).pendingVariant;
}

public int getInsertedFootnotesLength(int fitness) {
@@ -220,9 +225,6 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
return bestFootnoteElementIndex[fitness];
}

public Variant getVariant(int fitness) {
return bestVariant[fitness];
}

}

@@ -316,8 +318,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
insertedFootnotesLength, totalFootnotesLength,
footnoteListIndex, footnoteElementIndex,
adjustRatio, availableShrink, availableStretch,
difference, totalDemerits, previous,
(previous != null) ? ((KnuthPageNode)previous).pendingVariant : null);
difference, totalDemerits, previous);
}

/** {@inheritDoc} */
@@ -332,8 +333,7 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
((BestPageRecords) best).getFootnoteElementIndex(fitness),
best.getAdjust(fitness), best.getAvailableShrink(fitness),
best.getAvailableStretch(fitness), best.getDifference(fitness),
best.getDemerits(fitness), best.getNode(fitness),
((BestPageRecords) best).getVariant(fitness));
best.getDemerits(fitness), best.getNode(fitness));
}

/**
@@ -527,15 +527,12 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
int actualWidth = totalWidth - pageNode.totalWidth;
int footnoteSplit;
boolean canDeferOldFN;
if (element.isPenalty()) {
if (element instanceof BestFitPenalty) {
actualWidth += handleBestFitPenalty(pageNode, (BestFitPenalty) element, elementIndex);
} else {
actualWidth += element.getWidth();
if (pageNode.pendingVariant != null) {
actualWidth += ((KnuthPageNode) activeNode).pendingVariant.width;
}
}
actualWidth += pageNode.totalVariantsWidth;
if (element instanceof WhitespaceManagementPenalty) {
actualWidth += handleWhitespaceManagementPenalty(pageNode,
(WhitespaceManagementPenalty) element, elementIndex);
} else if (element.isPenalty()) {
actualWidth += element.getWidth();
}
if (footnotesPending) {
// compute the total length of the footnotes not yet inserted
@@ -595,13 +592,18 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
}
}

private int handleBestFitPenalty(KnuthPageNode activeNode, BestFitPenalty penalty, int elementIndex) {
/**
* Evaluates the variants corresponding to the given penalty until one that
* leads to an acceptable adjustment ratio is found. That variant will
* be added to the list of pending variants in the given active node.
*/
private int handleWhitespaceManagementPenalty(KnuthPageNode activeNode,
WhitespaceManagementPenalty penalty, int elementIndex) {
for (Variant var : penalty.getVariants()) {
int difference = computeDifference(activeNode, var.toPenalty(), elementIndex);
double r = computeAdjustmentRatio(activeNode, difference);
if (r >= -1.0) {
var.penaltyIndex = elementIndex;
activeNode.pendingVariant = var;
activeNode.addVariant(var);
return var.width;
}
}
@@ -1022,9 +1024,14 @@ class PageBreakingAlgorithm extends BreakingAlgorithm {
// ? bestActiveNode.difference : bestActiveNode.difference + fillerMinWidth;
// Check if the given node has an attached variant of a dynamic content
KnuthPageNode pageNode = (KnuthPageNode) bestActiveNode;
if (pageNode.variant != null) {
BestFitPenalty penalty = (BestFitPenalty) par.get(pageNode.variant.penaltyIndex);
penalty.setActiveVariant(pageNode.variant);
KnuthPageNode previousPageNode = ((KnuthPageNode) pageNode.previous);
for (Variant var : previousPageNode.pendingVariants) {
WhitespaceManagementPenalty penalty = var.getBestFitPenalty();
int penaltyIndex = this.par.indexOf(penalty);
// Make sure penalty is inside the range of the current page node
if (penaltyIndex <= pageNode.position) {
penalty.setActiveVariant(var);
}
}
int difference = bestActiveNode.difference;
if (difference + bestActiveNode.availableShrink < 0) {

src/java/org/apache/fop/layoutmgr/BestFitPenalty.java → src/java/org/apache/fop/layoutmgr/WhitespaceManagementPenalty.java View File

@@ -22,39 +22,42 @@ package org.apache.fop.layoutmgr;
import java.util.ArrayList;
import java.util.List;

import org.apache.fop.layoutmgr.BestFitLayoutUtils.BestFitPosition;
import org.apache.fop.layoutmgr.MultiSwitchLayoutManager.WhitespaceManagementPosition;

/**
* A type of penalty used to specify a set of alternatives for the layout engine
* to choose from. The chosen alternative must have an occupied size
* less than the remaining BPD on the active page.
* A special penalty used to specify content having multiple variants. At most
* only one variant will be inserted into the final document. If none of the
* variants fit into the remaining space on the current page, the dynamic
* content will be completely ignored.
*/
public class BestFitPenalty extends KnuthPenalty {
public class WhitespaceManagementPenalty extends KnuthPenalty {

public static class Variant {
public class Variant {

public final List<ListElement> knuthList;
public final int width;
public int penaltyIndex;

public Variant(List<ListElement> knuthList, int width) {
this.knuthList = knuthList;
this.width = width;
this.penaltyIndex = -1;
}

public KnuthElement toPenalty() {
return new KnuthPenalty(width, 0, false, null, false);
}

public WhitespaceManagementPenalty getBestFitPenalty() {
return WhitespaceManagementPenalty.this;
}

}

private final BestFitPosition bestFitPosition;
private final WhitespaceManagementPosition whitespaceManagementPosition;
private final List<Variant> variantList;

public BestFitPenalty(BestFitPosition pos) {
public WhitespaceManagementPenalty(WhitespaceManagementPosition pos) {
super(0, 0, false, pos, false);
this.bestFitPosition = pos;
this.whitespaceManagementPosition = pos;
variantList = new ArrayList<Variant>();
}

@@ -63,7 +66,11 @@ public class BestFitPenalty extends KnuthPenalty {
}

public void setActiveVariant(Variant bestVariant) {
bestFitPosition.setKnuthList(bestVariant.knuthList);
whitespaceManagementPosition.setKnuthList(bestVariant.knuthList);
}

public boolean isActivated() {
return whitespaceManagementPosition.getKnuthList() != null;
}

public List<Variant> getVariants() {

+ 98
- 0
test/layoutengine/standard-testcases/multi-switch_best-fit_multiple_dynamic_contents.xml View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<testcase>
<info>
<p>
Test that multiple whitespace managment elements on the same page are handled nicely.
</p>
</info>
<fo>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" font-size="8pt" line-height="10pt">
<fo:layout-master-set>
<fo:simple-page-master master-name="page"
page-height="70pt" page-width="220pt" margin="10pt">
<fo:region-body background-color="#F0F0F0"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="page">
<fo:flow flow-name="xsl-region-body">
<fo:multi-switch fox:auto-toggle="best-fit">
<fo:multi-case>
<fo:block>MS 1 Variant 1</fo:block>
</fo:multi-case>
</fo:multi-switch>
<fo:multi-switch fox:auto-toggle="best-fit">
<fo:multi-case>
<fo:block>MS 2 Variant 1</fo:block>
</fo:multi-case>
</fo:multi-switch>
<fo:multi-switch fox:auto-toggle="best-fit">
<fo:multi-case>
<fo:block>MS 3 Variant 1</fo:block>
</fo:multi-case>
</fo:multi-switch>
</fo:flow>
</fo:page-sequence>

<fo:page-sequence master-reference="page">
<fo:flow flow-name="xsl-region-body">
<fo:multi-switch fox:auto-toggle="best-fit">
<fo:multi-case>
<fo:block line-height="70pt">MS 1 Variant 1</fo:block>
</fo:multi-case>
</fo:multi-switch>
<fo:multi-switch fox:auto-toggle="best-fit">
<fo:multi-case>
<fo:block>MS 2 Variant 1</fo:block>
</fo:multi-case>
</fo:multi-switch>
<fo:multi-switch fox:auto-toggle="best-fit">
<fo:multi-case>
<fo:block line-height="50pt">MS 3 Variant 1</fo:block>
</fo:multi-case>
<fo:multi-case>
<fo:block>MS 3 Variant 2</fo:block>
</fo:multi-case>
</fo:multi-switch>
<fo:multi-switch fox:auto-toggle="best-fit">
<fo:multi-case>
<fo:block>MS 4 Variant 1</fo:block>
</fo:multi-case>
</fo:multi-switch>
</fo:flow>
</fo:page-sequence>

</fo:root>
</fo>
<checks>
<!-- 1. 3 multi-switch on the same page -->
<eval expected="1" xpath="count(//pageSequence[1]/pageViewport)"/>
<eval expected="3" xpath="count(//pageSequence[1]/pageViewport[1]//flow/block)"/>
<eval expected="MS 1 Variant 1" xpath="//pageSequence[1]/pageViewport[1]//flow/block[1]"/>
<eval expected="MS 2 Variant 1" xpath="//pageSequence[1]/pageViewport[1]//flow/block[2]"/>
<eval expected="MS 3 Variant 1" xpath="//pageSequence[1]/pageViewport[1]//flow/block[3]"/>

<!-- 2. 4 multi-switch, one cannot fit, one uses the 2nd variant -->
<eval expected="1" xpath="count(//pageSequence[2]/pageViewport)"/>
<eval expected="3" xpath="count(//pageSequence[2]/pageViewport[1]//flow/block)"/>
<eval expected="MS 2 Variant 1" xpath="//pageSequence[2]/pageViewport[1]//flow/block[1]"/>
<eval expected="MS 3 Variant 2" xpath="//pageSequence[2]/pageViewport[1]//flow/block[2]"/>
<eval expected="MS 4 Variant 1" xpath="//pageSequence[2]/pageViewport[1]//flow/block[3]"/>
</checks>
</testcase>

Loading…
Cancel
Save