org.apache.fop.layoutmgr.SpaceResolver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The newest version!
/*
* 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: SpaceResolver.java 1805173 2017-08-16 10:50:04Z ssteiner $ */
package org.apache.fop.layoutmgr;
import java.util.List;
import java.util.ListIterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.traits.MinOptMax;
/**
* This class resolves spaces and conditional borders and paddings by replacing the
* UnresolvedListElements descendants by the right combination of KnuthElements on an element
* list.
*/
public final class SpaceResolver {
/** Logger instance */
private static final Log LOG = LogFactory.getLog(SpaceResolver.class);
private UnresolvedListElementWithLength[] firstPart;
private BreakElement breakPoss;
private UnresolvedListElementWithLength[] secondPart;
private UnresolvedListElementWithLength[] noBreak;
private MinOptMax[] firstPartLengths;
private MinOptMax[] secondPartLengths;
private MinOptMax[] noBreakLengths;
private boolean isFirst;
private boolean isLast;
/**
* Main constructor.
* @param first Element list before a break (optional)
* @param breakPoss Break possibility (optional)
* @param second Element list after a break (or if no break possibility in vicinity)
* @param isFirst Resolution at the beginning of a (full) element list
* @param isLast Resolution at the end of a (full) element list
*/
private SpaceResolver(List first, BreakElement breakPoss, List second,
boolean isFirst, boolean isLast) {
this.isFirst = isFirst;
this.isLast = isLast;
//Create combined no-break list
int c = 0;
if (first != null) {
c += first.size();
}
if (second != null) {
c += second.size();
}
noBreak = new UnresolvedListElementWithLength[c];
noBreakLengths = new MinOptMax[c];
int i = 0;
ListIterator iter;
if (first != null) {
iter = first.listIterator();
while (iter.hasNext()) {
noBreak[i] = (UnresolvedListElementWithLength)iter.next();
noBreakLengths[i] = noBreak[i].getLength();
i++;
}
}
if (second != null) {
iter = second.listIterator();
while (iter.hasNext()) {
noBreak[i] = (UnresolvedListElementWithLength)iter.next();
noBreakLengths[i] = noBreak[i].getLength();
i++;
}
}
//Add pending elements from higher level FOs
if (breakPoss != null) {
if (breakPoss.getPendingAfterMarks() != null) {
if (LOG.isTraceEnabled()) {
LOG.trace(" adding pending before break: "
+ breakPoss.getPendingAfterMarks());
}
first.addAll(0, breakPoss.getPendingAfterMarks());
}
if (breakPoss.getPendingBeforeMarks() != null) {
if (LOG.isTraceEnabled()) {
LOG.trace(" adding pending after break: "
+ breakPoss.getPendingBeforeMarks());
}
second.addAll(0, breakPoss.getPendingBeforeMarks());
}
}
if (LOG.isTraceEnabled()) {
LOG.trace("before: " + first);
LOG.trace(" break: " + breakPoss);
LOG.trace("after: " + second);
LOG.trace("NO-BREAK: " + toString(noBreak, noBreakLengths));
}
if (first != null) {
firstPart = new UnresolvedListElementWithLength[first.size()];
firstPartLengths = new MinOptMax[firstPart.length];
first.toArray(firstPart);
for (i = 0; i < firstPart.length; i++) {
firstPartLengths[i] = firstPart[i].getLength();
}
}
this.breakPoss = breakPoss;
if (second != null) {
secondPart = new UnresolvedListElementWithLength[second.size()];
secondPartLengths = new MinOptMax[secondPart.length];
second.toArray(secondPart);
for (i = 0; i < secondPart.length; i++) {
secondPartLengths[i] = secondPart[i].getLength();
}
}
resolve();
}
private String toString(Object[] arr1, Object[] arr2) {
if (arr1.length != arr2.length) {
throw new IllegalArgumentException("The length of both arrays must be equal");
}
StringBuffer sb = new StringBuffer("[");
for (int i = 0; i < arr1.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(String.valueOf(arr1[i]));
sb.append("/");
sb.append(String.valueOf(arr2[i]));
}
sb.append("]");
return sb.toString();
}
private void removeConditionalBorderAndPadding(
UnresolvedListElement[] elems, MinOptMax[] lengths, boolean reverse) {
for (int i = 0; i < elems.length; i++) {
int effIndex;
if (reverse) {
effIndex = elems.length - 1 - i;
} else {
effIndex = i;
}
if (elems[effIndex] instanceof BorderOrPaddingElement) {
BorderOrPaddingElement bop = (BorderOrPaddingElement)elems[effIndex];
if (bop.isConditional() && !(bop.isFirst() || bop.isLast())) {
if (LOG.isDebugEnabled()) {
LOG.debug("Nulling conditional element: " + bop);
}
lengths[effIndex] = null;
}
}
}
if (LOG.isTraceEnabled() && elems.length > 0) {
LOG.trace("-->Resulting list: " + toString(elems, lengths));
}
}
private void performSpaceResolutionRule1(UnresolvedListElement[] elems, MinOptMax[] lengths,
boolean reverse) {
for (int i = 0; i < elems.length; i++) {
int effIndex;
if (reverse) {
effIndex = elems.length - 1 - i;
} else {
effIndex = i;
}
if (lengths[effIndex] == null) {
//Zeroed border or padding doesn't create a fence
continue;
} else if (elems[effIndex] instanceof BorderOrPaddingElement) {
//Border or padding form fences!
break;
} else if (!elems[effIndex].isConditional()) {
break;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Nulling conditional element using 4.3.1, rule 1: " + elems[effIndex]);
}
lengths[effIndex] = null;
}
if (LOG.isTraceEnabled() && elems.length > 0) {
LOG.trace("-->Resulting list: " + toString(elems, lengths));
}
}
private void performSpaceResolutionRules2to3(UnresolvedListElement[] elems,
MinOptMax[] lengths, int start, int end) {
if (LOG.isTraceEnabled()) {
LOG.trace("rule 2-3: " + start + "-" + end);
}
SpaceElement space;
int remaining;
//Rule 2 (4.3.1, XSL 1.0)
boolean hasForcing = false;
remaining = 0;
for (int i = start; i <= end; i++) {
if (lengths[i] == null) {
continue;
}
remaining++;
space = (SpaceElement)elems[i];
if (space.isForcing()) {
hasForcing = true;
break;
}
}
if (remaining == 0) {
return; //shortcut
}
if (hasForcing) {
for (int i = start; i <= end; i++) {
if (lengths[i] == null) {
continue;
}
space = (SpaceElement)elems[i];
if (!space.isForcing()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Nulling non-forcing space-specifier using 4.3.1, rule 2: "
+ elems[i]);
}
lengths[i] = null;
}
}
return; //If rule is triggered skip rule 3
}
//Rule 3 (4.3.1, XSL 1.0)
//Determine highes precedence
int highestPrecedence = Integer.MIN_VALUE;
for (int i = start; i <= end; i++) {
if (lengths[i] == null) {
continue;
}
space = (SpaceElement)elems[i];
highestPrecedence = Math.max(highestPrecedence, space.getPrecedence());
}
if (highestPrecedence != 0 && LOG.isDebugEnabled()) {
LOG.debug("Highest precedence is " + highestPrecedence);
}
//Suppress space-specifiers with lower precedence
remaining = 0;
int greatestOptimum = Integer.MIN_VALUE;
for (int i = start; i <= end; i++) {
if (lengths[i] == null) {
continue;
}
space = (SpaceElement)elems[i];
if (space.getPrecedence() != highestPrecedence) {
if (LOG.isDebugEnabled()) {
LOG.debug("Nulling space-specifier with precedence "
+ space.getPrecedence() + " using 4.3.1, rule 3: "
+ elems[i]);
}
lengths[i] = null;
} else {
greatestOptimum = Math.max(greatestOptimum, space.getLength().getOpt());
remaining++;
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Greatest optimum: " + greatestOptimum);
}
if (remaining <= 1) {
return;
}
//Suppress space-specifiers with smaller optimum length
remaining = 0;
for (int i = start; i <= end; i++) {
if (lengths[i] == null) {
continue;
}
space = (SpaceElement)elems[i];
if (space.getLength().getOpt() < greatestOptimum) {
if (LOG.isDebugEnabled()) {
LOG.debug("Nulling space-specifier with smaller optimum length "
+ "using 4.3.1, rule 3: "
+ elems[i]);
}
lengths[i] = null;
} else {
remaining++;
}
}
if (remaining <= 1) {
return;
}
//Construct resolved space-specifier from the remaining spaces
int min = Integer.MIN_VALUE;
int max = Integer.MAX_VALUE;
for (int i = start; i <= end; i++) {
if (lengths[i] == null) {
continue;
}
space = (SpaceElement)elems[i];
min = Math.max(min, space.getLength().getMin());
max = Math.min(max, space.getLength().getMax());
if (remaining > 1) {
if (LOG.isDebugEnabled()) {
LOG.debug("Nulling non-last space-specifier using 4.3.1, rule 3, second part: "
+ elems[i]);
}
lengths[i] = null;
remaining--;
} else {
lengths[i] = MinOptMax.getInstance(min, lengths[i].getOpt(), max);
}
}
if (LOG.isTraceEnabled() && elems.length > 0) {
LOG.trace("Remaining spaces: " + remaining);
LOG.trace("-->Resulting list: " + toString(elems, lengths));
}
}
private void performSpaceResolutionRules2to3(UnresolvedListElement[] elems,
MinOptMax[] lengths) {
int start = 0;
int i = start;
while (i < elems.length) {
if (elems[i] instanceof SpaceElement) {
while (i < elems.length) {
if (elems[i] == null || elems[i] instanceof SpaceElement) {
i++;
} else {
break;
}
}
performSpaceResolutionRules2to3(elems, lengths, start, i - 1);
}
i++;
start = i;
}
}
private boolean hasFirstPart() {
return firstPart != null && firstPart.length > 0;
}
private boolean hasSecondPart() {
return secondPart != null && secondPart.length > 0;
}
private void resolve() {
if (breakPoss != null) {
if (hasFirstPart()) {
removeConditionalBorderAndPadding(firstPart, firstPartLengths, true);
performSpaceResolutionRule1(firstPart, firstPartLengths, true);
performSpaceResolutionRules2to3(firstPart, firstPartLengths);
}
if (hasSecondPart()) {
removeConditionalBorderAndPadding(secondPart, secondPartLengths, false);
performSpaceResolutionRule1(secondPart, secondPartLengths, false);
performSpaceResolutionRules2to3(secondPart, secondPartLengths);
}
if (noBreak != null) {
performSpaceResolutionRules2to3(noBreak, noBreakLengths);
}
} else {
if (isFirst) {
removeConditionalBorderAndPadding(secondPart, secondPartLengths, false);
performSpaceResolutionRule1(secondPart, secondPartLengths, false);
}
if (isLast) {
removeConditionalBorderAndPadding(firstPart, firstPartLengths, true);
performSpaceResolutionRule1(firstPart, firstPartLengths, true);
}
if (hasFirstPart()) {
//Now that we've handled isFirst/isLast conditions, we need to look at the
//active part in its normal order so swap it back.
LOG.trace("Swapping first and second parts.");
UnresolvedListElementWithLength[] tempList;
MinOptMax[] tempLengths;
tempList = secondPart;
tempLengths = secondPartLengths;
secondPart = firstPart;
secondPartLengths = firstPartLengths;
firstPart = tempList;
firstPartLengths = tempLengths;
if (hasFirstPart()) {
throw new IllegalStateException("Didn't expect more than one parts in a"
+ "no-break condition.");
}
}
performSpaceResolutionRules2to3(secondPart, secondPartLengths);
}
}
private MinOptMax sum(MinOptMax[] lengths) {
MinOptMax sum = MinOptMax.ZERO;
for (MinOptMax length : lengths) {
if (length != null) {
sum = sum.plus(length);
}
}
return sum;
}
private void generate(ListIterator iter) {
MinOptMax spaceBeforeBreak = sum(firstPartLengths);
MinOptMax spaceAfterBreak = sum(secondPartLengths);
boolean hasPrecedingNonBlock = false;
if (breakPoss != null) {
if (spaceBeforeBreak.isNonZero()) {
iter.add(new KnuthPenalty(0, KnuthPenalty.INFINITE, false, null, true));
iter.add(new KnuthGlue(spaceBeforeBreak, null, true));
if (breakPoss.isForcedBreak()) {
//Otherwise, the preceding penalty and glue will be cut off
iter.add(new KnuthBox(0, null, true));
}
}
iter.add(new KnuthPenalty(breakPoss.getPenaltyWidth(), breakPoss.getPenaltyValue(),
false, breakPoss.getBreakClass(),
new SpaceHandlingBreakPosition(this, breakPoss), false));
if (breakPoss.getPenaltyValue() <= -KnuthPenalty.INFINITE) {
return; //return early. Not necessary (even wrong) to add additional elements
}
// No break
// TODO: We can't use a MinOptMax for glue2,
// because min <= opt <= max is not always true - why?
MinOptMax noBreakLength = sum(noBreakLengths);
MinOptMax spaceSum = spaceBeforeBreak.plus(spaceAfterBreak);
int glue2width = noBreakLength.getOpt() - spaceSum.getOpt();
int glue2stretch = noBreakLength.getStretch() - spaceSum.getStretch();
int glue2shrink = noBreakLength.getShrink() - spaceSum.getShrink();
if (glue2width != 0 || glue2stretch != 0 || glue2shrink != 0) {
iter.add(new KnuthGlue(glue2width, glue2stretch, glue2shrink, null, true));
}
} else {
if (spaceBeforeBreak.isNonZero()) {
throw new IllegalStateException("spaceBeforeBreak should be 0 in this case");
}
}
Position pos = null;
if (breakPoss == null) {
pos = new SpaceHandlingPosition(this);
}
if (spaceAfterBreak.isNonZero() || pos != null) {
iter.add(new KnuthBox(0, pos, true));
}
if (spaceAfterBreak.isNonZero()) {
iter.add(new KnuthPenalty(0, KnuthPenalty.INFINITE, false, null, true));
iter.add(new KnuthGlue(spaceAfterBreak, null, true));
hasPrecedingNonBlock = true;
}
if (isLast && hasPrecedingNonBlock) {
//Otherwise, the preceding penalty and glue will be cut off
iter.add(new KnuthBox(0, null, true));
}
}
/**
* Position class for break possibilities. It is used to notify layout manager about the
* effective spaces and conditional lengths.
*/
public static class SpaceHandlingBreakPosition extends Position {
private SpaceResolver resolver;
private Position originalPosition;
/**
* Main constructor.
* @param resolver the space resolver that provides the info about the actual situation
* @param breakPoss the original break possibility that creates this Position
*/
public SpaceHandlingBreakPosition(SpaceResolver resolver, BreakElement breakPoss) {
super(null);
this.resolver = resolver;
this.originalPosition = breakPoss.getPosition();
//Unpack since the SpaceHandlingBreakPosition is a non-wrapped Position, too
while (this.originalPosition instanceof NonLeafPosition) {
this.originalPosition = this.originalPosition.getPosition();
}
}
/** @return the space resolver */
public SpaceResolver getSpaceResolver() {
return this.resolver;
}
/**
* Notifies all affected layout managers about the current situation in the part to be
* handled for area generation.
* @param isBreakSituation true if this is a break situation.
* @param side defines to notify about the situation whether before or after the break.
* May be null if isBreakSituation is null.
*/
public void notifyBreakSituation(boolean isBreakSituation, RelSide side) {
if (isBreakSituation) {
if (RelSide.BEFORE == side) {
for (int i = 0; i < resolver.secondPart.length; i++) {
resolver.secondPart[i].notifyLayoutManager(resolver.secondPartLengths[i]);
}
} else {
for (int i = 0; i < resolver.firstPart.length; i++) {
resolver.firstPart[i].notifyLayoutManager(resolver.firstPartLengths[i]);
}
}
} else {
for (int i = 0; i < resolver.noBreak.length; i++) {
resolver.noBreak[i].notifyLayoutManager(resolver.noBreakLengths[i]);
}
}
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("SpaceHandlingBreakPosition(");
sb.append(this.originalPosition);
sb.append(")");
return sb.toString();
}
/**
* @return the original Position instance set at the BreakElement that this Position was
* created for.
*/
public Position getOriginalBreakPosition() {
return this.originalPosition;
}
/** {@inheritDoc} */
public Position getPosition() {
return originalPosition;
}
}
/**
* Position class for no-break situations. It is used to notify layout manager about the
* effective spaces and conditional lengths.
*/
public static class SpaceHandlingPosition extends Position {
private SpaceResolver resolver;
/**
* Main constructor.
* @param resolver the space resolver that provides the info about the actual situation
*/
public SpaceHandlingPosition(SpaceResolver resolver) {
super(null);
this.resolver = resolver;
}
/** @return the space resolver */
public SpaceResolver getSpaceResolver() {
return this.resolver;
}
/**
* Notifies all affected layout managers about the current situation in the part to be
* handled for area generation.
*/
public void notifySpaceSituation() {
if (resolver.breakPoss != null) {
throw new IllegalStateException("Only applicable to no-break situations");
}
for (int i = 0; i < resolver.secondPart.length; i++) {
resolver.secondPart[i].notifyLayoutManager(resolver.secondPartLengths[i]);
}
}
/** {@inheritDoc} */
public String toString() {
return "SpaceHandlingPosition";
}
}
/**
* Resolves unresolved elements applying the space resolution rules defined in 4.3.1.
* @param elems the element list
*/
public static void resolveElementList(List elems) {
if (LOG.isTraceEnabled()) {
LOG.trace(elems);
}
boolean first = true;
boolean last = false;
boolean skipNextElement = false;
List unresolvedFirst = new java.util.ArrayList();
List unresolvedSecond = new java.util.ArrayList();
List currentGroup;
ListIterator iter = elems.listIterator();
while (iter.hasNext()) {
ListElement el = (ListElement)iter.next();
if (el.isUnresolvedElement()) {
if (LOG.isTraceEnabled()) {
LOG.trace("unresolved found: " + el + " " + first + "/" + last);
}
BreakElement breakPoss = null;
//Clear temp lists
unresolvedFirst.clear();
unresolvedSecond.clear();
//Collect groups
if (el instanceof BreakElement) {
breakPoss = (BreakElement)el;
currentGroup = unresolvedSecond;
} else {
currentGroup = unresolvedFirst;
currentGroup.add(el);
}
iter.remove();
last = true;
skipNextElement = true;
while (iter.hasNext()) {
el = (ListElement)iter.next();
if (el instanceof BreakElement && breakPoss != null) {
skipNextElement = false;
last = false;
break;
} else if (currentGroup == unresolvedFirst && (el instanceof BreakElement)) {
breakPoss = (BreakElement)el;
iter.remove();
currentGroup = unresolvedSecond;
} else if (el.isUnresolvedElement()) {
currentGroup.add(el);
iter.remove();
} else {
last = false;
break;
}
}
//last = !iter.hasNext();
if (breakPoss == null && unresolvedSecond.isEmpty() && !last) {
LOG.trace("Swap first and second parts in no-break condition,"
+ " second part is empty.");
//The first list is reversed, so swap if this shouldn't happen
List swapList = unresolvedSecond;
unresolvedSecond = unresolvedFirst;
unresolvedFirst = swapList;
}
LOG.debug("----start space resolution (first=" + first + ", last=" + last + ")...");
SpaceResolver resolver = new SpaceResolver(
unresolvedFirst, breakPoss, unresolvedSecond, first, last);
if (!last) {
iter.previous();
}
resolver.generate(iter);
if (!last && skipNextElement) {
iter.next();
}
LOG.debug("----end space resolution.");
}
first = false;
}
}
/**
* Inspects an effective element list and notifies all layout managers about the state of
* the spaces and conditional lengths.
* @param effectiveList the effective element list
* @param startElementIndex index of the first element in the part to be processed
* @param endElementIndex index of the last element in the part to be processed
* @param prevBreak index of the the break possibility just before this part (used to
* identify a break condition, lastBreak <= 0 represents a no-break condition)
*/
public static void performConditionalsNotification(List effectiveList,
int startElementIndex, int endElementIndex, int prevBreak) {
KnuthElement el = null;
if (prevBreak > 0) {
el = (KnuthElement)effectiveList.get(prevBreak);
}
SpaceResolver.SpaceHandlingBreakPosition beforeBreak = null;
SpaceResolver.SpaceHandlingBreakPosition afterBreak = null;
if (el != null && el.isPenalty()) {
Position pos = el.getPosition();
if (pos instanceof SpaceResolver.SpaceHandlingBreakPosition) {
beforeBreak = (SpaceResolver.SpaceHandlingBreakPosition)pos;
beforeBreak.notifyBreakSituation(true, RelSide.BEFORE);
}
}
el = endElementIndex > -1 ? (KnuthElement) effectiveList.get(endElementIndex) : null;
if (el != null && el.isPenalty()) {
Position pos = el.getPosition();
if (pos instanceof SpaceResolver.SpaceHandlingBreakPosition) {
afterBreak = (SpaceResolver.SpaceHandlingBreakPosition)pos;
afterBreak.notifyBreakSituation(true, RelSide.AFTER);
}
}
for (int i = startElementIndex; i <= endElementIndex; i++) {
Position pos = ((KnuthElement)effectiveList.get(i)).getPosition();
if (pos instanceof SpaceResolver.SpaceHandlingPosition) {
((SpaceResolver.SpaceHandlingPosition)pos).notifySpaceSituation();
} else if (pos instanceof SpaceResolver.SpaceHandlingBreakPosition) {
SpaceResolver.SpaceHandlingBreakPosition noBreak;
noBreak = (SpaceResolver.SpaceHandlingBreakPosition)pos;
if (noBreak != beforeBreak && noBreak != afterBreak) {
noBreak.notifyBreakSituation(false, null);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy