package org.apache.fop.layoutmgr.list;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.area.Area;
import org.apache.fop.area.Block;
import org.apache.fop.fo.flow.ListItem;
import org.apache.fop.fo.flow.ListItemBody;
import org.apache.fop.fo.flow.ListItemLabel;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.KeepProperty;
import org.apache.fop.layoutmgr.BlockLayoutManager;
import org.apache.fop.layoutmgr.BreakElement;
import org.apache.fop.layoutmgr.BreakOpportunity;
import org.apache.fop.layoutmgr.BreakOpportunityHelper;
import org.apache.fop.layoutmgr.ElementListObserver;
import org.apache.fop.layoutmgr.ElementListUtils;
import org.apache.fop.layoutmgr.FloatContentLayoutManager;
import org.apache.fop.layoutmgr.FootenoteUtil;
import org.apache.fop.layoutmgr.Keep;
import org.apache.fop.layoutmgr.KnuthBlockBox;
import org.apache.fop.layoutmgr.KnuthElement;
import org.apache.fop.layoutmgr.KnuthPenalty;
import org.apache.fop.layoutmgr.KnuthPossPosIter;
import org.apache.fop.layoutmgr.LayoutContext;
import org.apache.fop.layoutmgr.LayoutManager;
import org.apache.fop.layoutmgr.LeafPosition;
import org.apache.fop.layoutmgr.ListElement;
import org.apache.fop.layoutmgr.NonLeafPosition;
import org.apache.fop.layoutmgr.Position;
import org.apache.fop.layoutmgr.PositionIterator;
import org.apache.fop.layoutmgr.SpaceResolver;
import org.apache.fop.layoutmgr.SpacedBorderedPaddedBlockLayoutManager;
import org.apache.fop.layoutmgr.TraitSetter;
import org.apache.fop.traits.SpaceVal;
import org.apache.fop.util.BreakUtil;
public class ListItemLayoutManager extends SpacedBorderedPaddedBlockLayoutManager
implements BreakOpportunity {
private static Log log = LogFactory.getLog(ListItemLayoutManager.class);
private ListItemContentLayoutManager label;
private ListItemContentLayoutManager body;
private Block curBlockArea;
private List<ListElement> labelList;
private List<ListElement> bodyList;
private Keep keepWithNextPendingOnLabel;
private Keep keepWithNextPendingOnBody;
public class ListItemPosition extends Position {
private int labelFirstIndex;
private int labelLastIndex;
private int bodyFirstIndex;
private int bodyLastIndex;
private Position originalLabelPosition;
private Position originalBodyPosition;
public ListItemPosition(LayoutManager lm, int labelFirst, int labelLast,
int bodyFirst, int bodyLast) {
super(lm);
labelFirstIndex = labelFirst;
labelLastIndex = labelLast;
bodyFirstIndex = bodyFirst;
bodyLastIndex = bodyLast;
}
public int getLabelFirstIndex() {
return labelFirstIndex;
}
public int getLabelLastIndex() {
return labelLastIndex;
}
public int getBodyFirstIndex() {
return bodyFirstIndex;
}
public int getBodyLastIndex() {
return bodyLastIndex;
}
public boolean generatesAreas() {
return true;
}
public String toString() {
StringBuffer sb = new StringBuffer("ListItemPosition:");
sb.append(getIndex()).append("(");
sb.append("label:").append(labelFirstIndex).append("-").append(labelLastIndex);
sb.append(" body:").append(bodyFirstIndex).append("-").append(bodyLastIndex);
sb.append(")");
return sb.toString();
}
public Position getOriginalLabelPosition() {
return originalLabelPosition;
}
public void setOriginalLabelPosition(Position originalLabelPosition) {
this.originalLabelPosition = originalLabelPosition;
}
public Position getOriginalBodyPosition() {
return originalBodyPosition;
}
public void setOriginalBodyPosition(Position originalBodyPosition) {
this.originalBodyPosition = originalBodyPosition;
}
}
public ListItemLayoutManager(ListItem node) {
super(node);
setLabel(node.getLabel());
setBody(node.getBody());
}
@Override
protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() {
return getListItemFO().getCommonBorderPaddingBackground();
}
protected ListItem getListItemFO() {
return (ListItem)fobj;
}
public void setLabel(ListItemLabel node) {
label = new ListItemContentLayoutManager(node);
label.setParent(this);
}
public void setBody(ListItemBody node) {
body = new ListItemContentLayoutManager(node);
body.setParent(this);
}
@Override
public void initialize() {
foSpaceBefore = new SpaceVal(
getListItemFO().getCommonMarginBlock().spaceBefore, this).getSpace();
foSpaceAfter = new SpaceVal(
getListItemFO().getCommonMarginBlock().spaceAfter, this).getSpace();
startIndent = getListItemFO().getCommonMarginBlock().startIndent.getValue(this);
endIndent = getListItemFO().getCommonMarginBlock().endIndent.getValue(this);
}
private void resetSpaces() {
this.discardBorderBefore = false;
this.discardBorderAfter = false;
this.discardPaddingBefore = false;
this.discardPaddingAfter = false;
this.effSpaceBefore = null;
this.effSpaceAfter = null;
}
public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack,
Position restartPosition, LayoutManager restartAtLM) {
referenceIPD = context.getRefIPD();
LayoutContext childLC;
List<ListElement> returnList = new LinkedList<ListElement>();
if (!breakBeforeServed(context, returnList)) {
return returnList;
}
addFirstVisibleMarks(returnList, context, alignment);
childLC = makeChildLayoutContext(context);
childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE);
label.initialize();
boolean labelDone = false;
Stack labelLMStack = null;
Position labelRestartPosition = null;
LayoutManager labelRestartLM = null;
if (restartPosition != null && restartPosition instanceof ListItemPosition) {
ListItemPosition lip = (ListItemPosition) restartPosition;
if (lip.labelLastIndex <= lip.labelFirstIndex) {
labelDone = true;
} else {
labelRestartPosition = lip.getOriginalLabelPosition();
labelRestartLM = labelRestartPosition.getLM();
LayoutManager lm = labelRestartLM;
labelLMStack = new Stack();
while (lm != this) {
labelLMStack.push(lm);
lm = lm.getParent();
if (lm instanceof ListItemContentLayoutManager) {
lm = lm.getParent();
}
}
}
}
labelList = !labelDone ? label.getNextKnuthElements(childLC, alignment, labelLMStack,
labelRestartPosition, labelRestartLM) : (List) new LinkedList<KnuthElement>();
SpaceResolver.resolveElementList(labelList);
ElementListObserver.observe(labelList, "list-item-label", label.getPartFO().getId());
context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
this.keepWithNextPendingOnLabel = childLC.getKeepWithNextPending();
childLC = makeChildLayoutContext(context);
childLC.setFlags(LayoutContext.SUPPRESS_BREAK_BEFORE);
body.initialize();
boolean bodyDone = false;
Stack bodyLMStack = null;
Position bodyRestartPosition = null;
LayoutManager bodyRestartLM = null;
if (restartPosition != null && restartPosition instanceof ListItemPosition) {
ListItemPosition lip = (ListItemPosition) restartPosition;
if (lip.bodyLastIndex <= lip.bodyFirstIndex) {
bodyDone = true;
} else {
bodyRestartPosition = lip.getOriginalBodyPosition();
bodyRestartLM = bodyRestartPosition.getLM();
LayoutManager lm = bodyRestartLM;
bodyLMStack = new Stack();
while (lm != this) {
bodyLMStack.push(lm);
lm = lm.getParent();
if (lm instanceof ListItemContentLayoutManager) {
lm = lm.getParent();
}
}
}
}
bodyList = !bodyDone ? body.getNextKnuthElements(childLC, alignment, bodyLMStack,
bodyRestartPosition, bodyRestartLM) : (List) new LinkedList<KnuthElement>();
SpaceResolver.resolveElementList(bodyList);
ElementListObserver.observe(bodyList, "list-item-body", body.getPartFO().getId());
context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
this.keepWithNextPendingOnBody = childLC.getKeepWithNextPending();
List<ListElement> returnedList = new LinkedList<ListElement>();
if (!labelList.isEmpty() && labelList.get(0) instanceof KnuthBlockBox) {
KnuthBlockBox kbb = (KnuthBlockBox) labelList.get(0);
if (kbb.getWidth() == 0 && kbb.hasFloatAnchors()) {
List<FloatContentLayoutManager> floats = kbb.getFloatContentLMs();
returnedList.add(new KnuthBlockBox(0, Collections.emptyList(), null, false, floats));
Keep keep = getKeepTogether();
returnedList.add(new BreakElement(new LeafPosition(this, 0), keep.getPenalty(), keep
.getContext(), context));
labelList.remove(0);
labelList.remove(0);
}
}
if (!bodyList.isEmpty() && bodyList.get(0) instanceof KnuthBlockBox) {
KnuthBlockBox kbb = (KnuthBlockBox) bodyList.get(0);
if (kbb.getWidth() == 0 && kbb.hasFloatAnchors()) {
List<FloatContentLayoutManager> floats = kbb.getFloatContentLMs();
returnedList.add(new KnuthBlockBox(0, Collections.emptyList(), null, false, floats));
Keep keep = getKeepTogether();
returnedList.add(new BreakElement(new LeafPosition(this, 0), keep.getPenalty(), keep
.getContext(), context));
bodyList.remove(0);
bodyList.remove(0);
}
}
returnedList.addAll(getCombinedKnuthElementsForListItem(labelList, bodyList, context));
wrapPositionElements(returnedList, returnList, true);
addLastVisibleMarks(returnList, context, alignment);
addKnuthElementsForBreakAfter(returnList, context);
context.updateKeepWithNextPending(this.keepWithNextPendingOnLabel);
context.updateKeepWithNextPending(this.keepWithNextPendingOnBody);
context.updateKeepWithNextPending(getKeepWithNext());
context.updateKeepWithPreviousPending(getKeepWithPrevious());
setFinished(true);
resetSpaces();
return returnList;
}
@Override
protected void addFirstVisibleMarks(List<ListElement> elements,
LayoutContext context, int alignment) {
addKnuthElementsForSpaceBefore(elements, alignment);
addKnuthElementsForBorderPaddingBefore(elements, !firstVisibleMarkServed);
firstVisibleMarkServed = true;
addPendingMarks(context);
}
private List getCombinedKnuthElementsForListItem(List<ListElement> labelElements,
List<ListElement> bodyElements, LayoutContext context) {
List[] elementLists = {new ArrayList<ListElement>(labelElements),
new ArrayList<ListElement>(bodyElements)};
int[] fullHeights = {ElementListUtils.calcContentLength(elementLists[0]),
ElementListUtils.calcContentLength(elementLists[1])};
int[] partialHeights = {0, 0};
int[] start = {-1, -1};
int[] end = {-1, -1};
int totalHeight = Math.max(fullHeights[0], fullHeights[1]);
int step;
int addedBoxHeight = 0;
Keep keepWithNextActive = Keep.KEEP_AUTO;
LinkedList<ListElement> returnList = new LinkedList<ListElement>();
while ((step = getNextStep(elementLists, start, end, partialHeights)) > 0) {
if (end[0] + 1 == elementLists[0].size()) {
keepWithNextActive = keepWithNextActive.compare(keepWithNextPendingOnLabel);
}
if (end[1] + 1 == elementLists[1].size()) {
keepWithNextActive = keepWithNextActive.compare(keepWithNextPendingOnBody);
}
int penaltyHeight = step
+ getMaxRemainingHeight(fullHeights, partialHeights)
- totalHeight;
int additionalPenaltyHeight = 0;
int stepPenalty = 0;
int breakClass = EN_AUTO;
KnuthElement endEl = elementLists[0].size() > 0 ? (KnuthElement) elementLists[0].get(end[0])
: null;
Position originalLabelPosition =
(endEl != null && endEl.getPosition() != null) ? endEl.getPosition().getPosition() : null;
if (endEl instanceof KnuthPenalty) {
additionalPenaltyHeight = endEl.getWidth();
stepPenalty = endEl.getPenalty() == -KnuthElement.INFINITE ? -KnuthElement.INFINITE : Math
.max(stepPenalty, endEl.getPenalty());
breakClass = BreakUtil.compareBreakClasses(breakClass,
((KnuthPenalty) endEl).getBreakClass());
}
endEl = elementLists[1].size() > 0 ? (KnuthElement) elementLists[1].get(end[1]) : null;
Position originalBodyPosition =
(endEl != null && endEl.getPosition() != null) ? endEl.getPosition().getPosition() : null;
if (endEl instanceof KnuthPenalty) {
additionalPenaltyHeight = Math.max(
additionalPenaltyHeight, endEl.getWidth());
stepPenalty = endEl.getPenalty() == -KnuthElement.INFINITE ? -KnuthElement.INFINITE : Math
.max(stepPenalty, endEl.getPenalty());
breakClass = BreakUtil.compareBreakClasses(breakClass,
((KnuthPenalty) endEl).getBreakClass());
}
int boxHeight = step - addedBoxHeight - penaltyHeight;
penaltyHeight += additionalPenaltyHeight;
LinkedList<LayoutManager> footnoteList = new LinkedList<LayoutManager>();
for (int i = 0; i < elementLists.length; i++) {
footnoteList.addAll(FootenoteUtil.getFootnotes(elementLists[i], start[i], end[i]));
}
LinkedList<FloatContentLayoutManager> floats = new LinkedList<FloatContentLayoutManager>();
for (int i = 0; i < elementLists.length; i++) {
floats.addAll(FloatContentLayoutManager.checkForFloats(elementLists[i], start[i], end[i]));
}
addedBoxHeight += boxHeight;
ListItemPosition stepPosition = new ListItemPosition(this, start[0], end[0], start[1], end[1]);
stepPosition.setOriginalLabelPosition(originalLabelPosition);
stepPosition.setOriginalBodyPosition(originalBodyPosition);
if (floats.isEmpty()) {
returnList.add(new KnuthBlockBox(boxHeight, footnoteList, stepPosition, false));
} else {
returnList.add(new KnuthBlockBox(0, Collections.emptyList(), stepPosition, false, floats));
Keep keep = getKeepTogether();
returnList.add(new BreakElement(stepPosition, keep.getPenalty(), keep.getContext(), context));
returnList.add(new KnuthBlockBox(boxHeight, footnoteList, stepPosition, false));
}
if (originalBodyPosition != null) {
LayoutManager lm = originalBodyPosition.getLM();
if ((lm instanceof ListBlockLayoutManager || lm instanceof BlockLayoutManager)
&& getKeepWithPrevious().isAuto()) {
stepPenalty++;
}
}
if (addedBoxHeight < totalHeight) {
Keep keep = keepWithNextActive.compare(getKeepTogether());
int p = stepPenalty;
if (p > -KnuthElement.INFINITE) {
p = Math.max(p, keep.getPenalty());
breakClass = keep.getContext();
}
returnList.add(new BreakElement(stepPosition, penaltyHeight, p, breakClass, context));
}
}
return returnList;
}
private int getNextStep(List[] elementLists, int[] start, int[] end, int[] partialHeights) {
int[] backupHeights = {partialHeights[0], partialHeights[1]};
start[0] = end[0] + 1;
start[1] = end[1] + 1;
int seqCount = 0;
for (int i = 0; i < start.length; i++) {
while (end[i] + 1 < elementLists[i].size()) {
end[i]++;
KnuthElement el = (KnuthElement)elementLists[i].get(end[i]);
if (el.isPenalty()) {
if (el.getPenalty() < KnuthElement.INFINITE) {
break;
}
} else if (el.isGlue()) {
if (end[i] > 0) {
KnuthElement prev = (KnuthElement)elementLists[i].get(end[i] - 1);
if (prev.isBox()) {
break;
}
}
partialHeights[i] += el.getWidth();
} else {
partialHeights[i] += el.getWidth();
}
}
if (end[i] < start[i]) {
partialHeights[i] = backupHeights[i];
} else {
seqCount++;
}
}
if (seqCount == 0) {
return 0;
}
int step;
if (backupHeights[0] == 0 && backupHeights[1] == 0) {
step = Math.max((end[0] >= start[0] ? partialHeights[0] : Integer.MIN_VALUE),
(end[1] >= start[1] ? partialHeights[1] : Integer.MIN_VALUE));
} else {
step = Math.min((end[0] >= start[0] ? partialHeights[0] : Integer.MAX_VALUE),
(end[1] >= start[1] ? partialHeights[1] : Integer.MAX_VALUE));
}
for (int i = 0; i < partialHeights.length; i++) {
if (partialHeights[i] > step) {
partialHeights[i] = backupHeights[i];
end[i] = start[i] - 1;
}
}
return step;
}
private int getMaxRemainingHeight(int[] fullHeights, int[] partialHeights) {
return Math.max(fullHeights[0] - partialHeights[0],
fullHeights[1] - partialHeights[1]);
}
@Override
public List getChangedKnuthElements(List oldList, int alignment) {
labelList = label.getChangedKnuthElements(labelList, alignment);
ListIterator oldListIterator = oldList.listIterator();
KnuthElement oldElement;
while (oldListIterator.hasNext()) {
oldElement = (KnuthElement)oldListIterator.next();
Position innerPosition = oldElement.getPosition().getPosition();
if (innerPosition != null) {
oldElement.setPosition(innerPosition);
} else {
oldElement.setPosition(new Position(this));
}
}
List returnedList = body.getChangedKnuthElements(oldList, alignment);
List tempList = returnedList;
KnuthElement tempElement;
returnedList = new LinkedList();
for (Object aTempList : tempList) {
tempElement = (KnuthElement) aTempList;
tempElement.setPosition(new NonLeafPosition(this, tempElement.getPosition()));
returnedList.add(tempElement);
}
return returnedList;
}
@Override
public boolean hasLineAreaDescendant() {
return label.hasLineAreaDescendant() || body.hasLineAreaDescendant();
}
@Override
public int getBaselineOffset() {
if (label.hasLineAreaDescendant()) {
return label.getBaselineOffset();
} else if (body.hasLineAreaDescendant()) {
return body.getBaselineOffset();
} else {
throw newNoLineAreaDescendantException();
}
}
@Override
public void addAreas(PositionIterator parentIter,
LayoutContext layoutContext) {
getParentArea(null);
addId();
LayoutContext lc = LayoutContext.offspringOf(layoutContext);
Position firstPos = null;
Position lastPos = null;
LinkedList<Position> positionList = new LinkedList<Position>();
Position pos;
while (parentIter.hasNext()) {
pos = parentIter.next();
if (pos.getIndex() >= 0) {
if (firstPos == null) {
firstPos = pos;
}
lastPos = pos;
}
if (pos instanceof NonLeafPosition && pos.getPosition() != null) {
positionList.add(pos.getPosition());
}
}
if (positionList.isEmpty()) {
reset();
return;
}
registerMarkers(true, isFirst(firstPos), isLast(lastPos));
int labelFirstIndex = ((ListItemPosition) positionList.getFirst()).getLabelFirstIndex();
int labelLastIndex = ((ListItemPosition) positionList.getLast()).getLabelLastIndex();
int bodyFirstIndex = ((ListItemPosition) positionList.getFirst()).getBodyFirstIndex();
int bodyLastIndex = ((ListItemPosition) positionList.getLast()).getBodyLastIndex();
int previousBreak = ElementListUtils.determinePreviousBreak(labelList, labelFirstIndex);
SpaceResolver.performConditionalsNotification(labelList,
labelFirstIndex, labelLastIndex, previousBreak);
previousBreak = ElementListUtils.determinePreviousBreak(bodyList, bodyFirstIndex);
SpaceResolver.performConditionalsNotification(bodyList,
bodyFirstIndex, bodyLastIndex, previousBreak);
if (labelFirstIndex <= labelLastIndex) {
KnuthPossPosIter labelIter = new KnuthPossPosIter(labelList,
labelFirstIndex, labelLastIndex + 1);
lc.setFlags(LayoutContext.FIRST_AREA, layoutContext.isFirstArea());
lc.setFlags(LayoutContext.LAST_AREA, layoutContext.isLastArea());
lc.setSpaceAdjust(layoutContext.getSpaceAdjust());
lc.setStackLimitBP(layoutContext.getStackLimitBP());
label.addAreas(labelIter, lc);
}
if (bodyFirstIndex <= bodyLastIndex) {
KnuthPossPosIter bodyIter = new KnuthPossPosIter(bodyList,
bodyFirstIndex, bodyLastIndex + 1);
lc.setFlags(LayoutContext.FIRST_AREA, layoutContext.isFirstArea());
lc.setFlags(LayoutContext.LAST_AREA, layoutContext.isLastArea());
lc.setSpaceAdjust(layoutContext.getSpaceAdjust());
lc.setStackLimitBP(layoutContext.getStackLimitBP());
body.addAreas(bodyIter, lc);
}
int childCount = curBlockArea.getChildAreas().size();
assert childCount >= 1 && childCount <= 2;
int itemBPD = ((Block)curBlockArea.getChildAreas().get(0)).getAllocBPD();
if (childCount == 2) {
itemBPD = Math.max(itemBPD, ((Block)curBlockArea.getChildAreas().get(1)).getAllocBPD());
}
curBlockArea.setBPD(itemBPD);
registerMarkers(false, isFirst(firstPos), isLast(lastPos));
TraitSetter.addBackground(curBlockArea,
getListItemFO().getCommonBorderPaddingBackground(),
this);
TraitSetter.addSpaceBeforeAfter(curBlockArea, layoutContext.getSpaceAdjust(),
effSpaceBefore, effSpaceAfter);
flush();
curBlockArea = null;
resetSpaces();
checkEndOfLayout(lastPos);
}
@Override
public Area getParentArea(Area childArea) {
if (curBlockArea == null) {
curBlockArea = new Block();
parentLayoutManager.getParentArea(curBlockArea);
ListItem fo = getListItemFO();
TraitSetter.setProducerID(curBlockArea, fo.getId());
TraitSetter.addBorders(curBlockArea, fo.getCommonBorderPaddingBackground(),
discardBorderBefore, discardBorderAfter, false, false, this);
TraitSetter.addPadding(curBlockArea, fo.getCommonBorderPaddingBackground(),
discardPaddingBefore, discardPaddingAfter, false, false, this);
TraitSetter.addMargins(curBlockArea, fo.getCommonBorderPaddingBackground(),
fo.getCommonMarginBlock(), this);
TraitSetter.addBreaks(curBlockArea, fo.getBreakBefore(), fo.getBreakAfter());
int contentIPD = referenceIPD - getIPIndents();
curBlockArea.setIPD(contentIPD);
curBlockArea.setBidiLevel(fo.getBidiLevel());
setCurrentArea(curBlockArea);
}
return curBlockArea;
}
@Override
public void addChildArea(Area childArea) {
if (curBlockArea != null) {
curBlockArea.addBlock((Block) childArea);
}
}
@Override
public KeepProperty getKeepTogetherProperty() {
return getListItemFO().getKeepTogether();
}
@Override
public KeepProperty getKeepWithPreviousProperty() {
return getListItemFO().getKeepWithPrevious();
}
@Override
public KeepProperty getKeepWithNextProperty() {
return getListItemFO().getKeepWithNext();
}
@Override
public void reset() {
super.reset();
label.reset();
body.reset();
}
@Override
public int getBreakBefore() {
int breakBefore = BreakOpportunityHelper.getBreakBefore(this);
breakBefore = BreakUtil.compareBreakClasses(breakBefore, label.getBreakBefore());
breakBefore = BreakUtil.compareBreakClasses(breakBefore, body.getBreakBefore());
return breakBefore;
}
public boolean isRestartable() {
return true;
}
}