/*
 * 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.
/** * 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
/** 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.
Params:
  • first – Element list before a break (optional)
  • breakPoss – Break possibility (optional)
  • second – Element list after a break (or if no break possibility in vicinity)
  • isFirst – Resolution at the beginning of a (full) element list
  • isLast – Resolution at the end of a (full) element list
/** * 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.
/** * 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.
Params:
  • resolver – the space resolver that provides the info about the actual situation
  • breakPoss – the original break possibility that creates this Position
/** * 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(); } }
Returns:the space 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.
Params:
  • isBreakSituation – true if this is a break situation.
  • side – defines to notify about the situation whether before or after the break. May be null if isBreakSituation is null.
/** * 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}
/** {@inheritDoc} */
public String toString() { StringBuffer sb = new StringBuffer(); sb.append("SpaceHandlingBreakPosition("); sb.append(this.originalPosition); sb.append(")"); return sb.toString(); }
Returns:the original Position instance set at the BreakElement that this Position was created for.
/** * @return the original Position instance set at the BreakElement that this Position was * created for. */
public Position getOriginalBreakPosition() { return this.originalPosition; }
{@inheritDoc}
/** {@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.
/** * 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.
Params:
  • resolver – the space resolver that provides the info about the actual situation
/** * Main constructor. * @param resolver the space resolver that provides the info about the actual situation */
public SpaceHandlingPosition(SpaceResolver resolver) { super(null); this.resolver = resolver; }
Returns:the space 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.
/** * 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}
/** {@inheritDoc} */
public String toString() { return "SpaceHandlingPosition"; } }
Resolves unresolved elements applying the space resolution rules defined in 4.3.1.
Params:
  • elems – the element list
/** * 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.
Params:
  • effectiveList – the effective element list
  • startElementIndex – index of the first element in the part to be processed
  • endElementIndex – index of the last element in the part to be processed
  • prevBreak – index of the the break possibility just before this part (used to identify a break condition, lastBreak <= 0 represents a no-break condition)
/** * 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 &lt;= 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); } } } } }