/*-- $Id: PageSequence.java,v 1.25 2001/01/10 23:56:20 keiron Exp $ --
============================================================================
The Apache Software License, Version 1.1
============================================================================
Copyright (C) 1999 The Apache Software Foundation. All rights reserved.
Redistribution and use in source and binary forms, with or without modifica-
tion, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. The end-user documentation included with the redistribution, if any, must
include the following acknowledgment: "This product includes software
developed by the Apache Software Foundation (http://www.apache.org/)."
Alternately, this acknowledgment may appear in the software itself, if
and wherever such third-party acknowledgments normally appear.
4. The names "FOP" and "Apache Software Foundation" must not be used to
endorse or promote products derived from this software without prior
written permission. For written permission, please contact
apache@apache.org.
5. Products derived from this software may not be called "Apache", nor may
"Apache" appear in their name, without prior written permission of the
Apache Software Foundation.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This software consists of voluntary contributions made by many individuals
on behalf of the Apache Software Foundation and was originally created by
James Tauber <jtauber@jtauber.com>. For more information on the Apache
Software Foundation, please see <http://www.apache.org/>.
*/
package org.apache.fop.fo.pagination;
// FOP
import org.apache.fop.fo.*;
import org.apache.fop.messaging.MessageHandler;
import org.apache.fop.fo.properties.*;
import org.apache.fop.fo.flow.Flow;
import org.apache.fop.fo.flow.StaticContent;
import org.apache.fop.layout.Area;
import org.apache.fop.layout.AreaContainer;
import org.apache.fop.layout.BodyAreaContainer;
import org.apache.fop.layout.AreaTree;
import org.apache.fop.layout.Page;
import org.apache.fop.layout.PageMaster;
import org.apache.fop.apps.FOPException;
// Java
import java.util.*;
/**
* This provides pagination of flows onto pages. Much of the logic for paginating
* flows is contained in this class. The main entry point is the format method.
*/
public class PageSequence extends FObj
{
//
// Factory methods
//
public static class Maker extends FObj.Maker
{
public FObj make(FObj parent, PropertyList propertyList)
throws FOPException {
return new PageSequence(parent, propertyList);
}
}
public static FObj.Maker maker() {
return new PageSequence.Maker();
}
//
// intial-page-number types
//
private static final int EXPLICIT = 0;
private static final int AUTO = 1;
private static final int AUTO_EVEN = 2;
private static final int AUTO_ODD = 3;
//
// associations
//
/** The parent root object */
private Root root;
/** the set of layout masters (provided by the root object) */
private LayoutMasterSet layoutMasterSet;
// There doesn't seem to be anything in the spec requiring flows
// to be in the order given, only that they map to the regions
// defined in the page sequence, so all we need is this one hashtable
// the set of flows includes StaticContent flows also
/** Map of flows to their flow name (flow-name, Flow) */
private Hashtable _flowMap;
/** the "master-name" attribute */
private String masterName;
// according to communication from Paul Grosso (XSL-List,
// 001228, Number 406), confusion in spec section 6.4.5 about
// multiplicity of fo:flow in XSL 1.0 is cleared up - one (1)
// fo:flow per fo:page-sequence only.
private boolean isFlowSet = false;
//
// state attributes used during layout
//
private Page currentPage;
private int currentPageNumber = 0;
/** keeps count of page number from previous PageSequence */
private static int runningPageNumberCounter = 0;
/** specifies page numbering type (auto|auto-even|auto-odd|explicit) */
private int pageNumberType;
/** used to determine whether to calculate auto, auto-even, auto-odd */
private boolean thisIsFirstPage;
/** the current subsequence while formatting a given page sequence */
private SubSequenceSpecifier currentSubsequence;
/** the current index in the subsequence list */
private int currentSubsequenceNumber = -1; // starting case is -1 so that first getNext increments to 0
/** the name of the current page master */
private String currentPageMasterName;
protected PageSequence(FObj parent, PropertyList propertyList)
throws FOPException {
super(parent, propertyList);
this.name = "fo:page-sequence";
if ( parent.getName().equals("fo:root") )
{
this.runningPageNumberCounter=0; //else not initialized correctly
this.root = (Root) parent;
this.root.addPageSequence(this);
}
else
{
throw
new FOPException("page-sequence must be child of root, not "
+ parent.getName());
}
layoutMasterSet = root.getLayoutMasterSet();
// best time to run some checks on LayoutMasterSet
layoutMasterSet.checkRegionNames();
_flowMap = new Hashtable();
thisIsFirstPage=true; // we are now on the first page of the page sequence
String ipnValue= this.properties.get("initial-page-number").getString();
if ( ipnValue.equals("auto") )
{
pageNumberType=AUTO;
}
else if ( ipnValue.equals("auto-even") )
{
pageNumberType=AUTO_EVEN;
}
else if ( ipnValue.equals("auto-odd") )
{
pageNumberType=AUTO_ODD;
}
else
{
pageNumberType=EXPLICIT;
try
{
int pageStart = new Integer(ipnValue).intValue();
this.currentPageNumber = (pageStart > 0) ? pageStart - 1 : 0;
}
catch ( NumberFormatException nfe )
{
throw new FOPException("\""+ipnValue+"\" is not a valid value for initial-page-number");
}
}
masterName = this.properties.get("master-name").getString();
}
public void addFlow(Flow flow)
throws FOPException
{
if (_flowMap.containsKey(flow.getFlowName())) {
throw new FOPException("flow-names must be unique within an fo:page-sequence");
}
if (!this.layoutMasterSet.regionNameExists(flow.getFlowName())) {
MessageHandler.errorln("WARNING: region-name '"+flow.getFlowName()+"' doesn't exist in the layout-master-set.");
}
_flowMap.put(flow.getFlowName(), flow);
setIsFlowSet(true);
}
/**
* Runs the formatting of this page sequence into the given area tree
*/
public void format(AreaTree areaTree) throws FOPException {
Status status = new Status(Status.OK);
this.layoutMasterSet.resetPageMasters();
do
{
// makePage() moved to after the page-number computations,
// but store the page-number at this point for that method,
// since we want the 'current' current page-number...
int firstAvailPageNumber = this.runningPageNumberCounter;
boolean tempIsFirstPage = false;
if ( thisIsFirstPage )
{
tempIsFirstPage = thisIsFirstPage;
if ( pageNumberType==AUTO )
{
this.currentPageNumber=this.runningPageNumberCounter;
}
else if ( pageNumberType==AUTO_ODD )
{
this.currentPageNumber=this.runningPageNumberCounter;
if ( this.currentPageNumber % 2== 1 )
{
this.currentPageNumber++;
}
}
else if ( pageNumberType==AUTO_EVEN )
{
this.currentPageNumber=this.runningPageNumberCounter;
if ( this.currentPageNumber % 2 == 0 )
{
this.currentPageNumber++;
}
}
thisIsFirstPage=false;
}
this.currentPageNumber++;
// deliberately moved down here so page-number calculations
// are complete;
// compute flag for 'blank-or-not-blank'
boolean isEmptyPage = false;
if ( (status.getCode() == Status.FORCE_PAGE_BREAK_EVEN) &&
((currentPageNumber % 2) == 1) ) {
isEmptyPage = true;
}
else if ( (status.getCode() == Status.FORCE_PAGE_BREAK_ODD) &&
((currentPageNumber % 2) == 0) ) {
isEmptyPage = true;
}
else {
isEmptyPage = false;
}
currentPage = makePage(areaTree, firstAvailPageNumber,
tempIsFirstPage, isEmptyPage);
currentPage.setNumber(this.currentPageNumber);
this.runningPageNumberCounter=this.currentPageNumber;
MessageHandler.log(" [" + currentPageNumber);
formatStaticContent(areaTree);
if ( (status.getCode() == Status.FORCE_PAGE_BREAK_EVEN) &&
((currentPageNumber % 2) == 1) )
{
}
else if ( (status.getCode() == Status.FORCE_PAGE_BREAK_ODD) &&
((currentPageNumber % 2) == 0) )
{
}
else
{
BodyAreaContainer bodyArea = currentPage.getBody();
bodyArea.setIDReferences(areaTree.getIDReferences());
Flow flow = getCurrentFlow(RegionBody.REGION_CLASS);
if (null == flow) {
MessageHandler.errorln("No flow found for region-body "
+ "in page-master '" + currentPageMasterName + "'");
break;
}
else {
status = flow.layout(bodyArea);
}
}
MessageHandler.log("]");
areaTree.addPage(currentPage);
} while ( flowsAreIncomplete() );
MessageHandler.errorln("");
}
/**
* Creates a new page area for the given parameters
* @param areaTree the area tree the page should be contained in
* @param firstAvailPageNumber the page number for this page
* @param isFirstPage true when this is the first page in the sequence
* @param isEmptyPage true if this page will be empty (e.g. forced even or odd break)
* @return a Page layout object based on the page master selected from the params
*/
private Page makePage(AreaTree areaTree, int firstAvailPageNumber,
boolean isFirstPage, boolean isEmptyPage)
throws FOPException {
// layout this page sequence
// while there is still stuff in the flow, ask the
// layoutMasterSet for a new page
// page number is 0-indexed
PageMaster pageMaster = getNextPageMaster(masterName, firstAvailPageNumber,
isFirstPage, isEmptyPage );
// a legal alternative is to use the last sub-sequence
// specification which should be handled in getNextSubsequence. That's not done here.
if ( pageMaster == null )
{
throw new FOPException("page masters exhausted. Cannot recover.");
}
Page p = pageMaster.makePage(areaTree);
if(currentPage != null) {
Vector foots = currentPage.getPendingFootnotes();
p.setPendingFootnotes(foots);
}
return p;
}
/**
* Formats the static content of the current page
*/
private void formatStaticContent(AreaTree areaTree)
throws FOPException
{
SimplePageMaster simpleMaster = getCurrentSimplePageMaster();
if (simpleMaster.getRegion(RegionBefore.REGION_CLASS) != null && (currentPage.getBefore() != null)) {
Flow staticFlow = (Flow)_flowMap.get(simpleMaster.getRegion(RegionBefore.REGION_CLASS).getRegionName());
if (staticFlow != null) {
AreaContainer beforeArea = currentPage.getBefore();
beforeArea.setIDReferences(areaTree.getIDReferences());
layoutStaticContent(staticFlow, simpleMaster.getRegion(RegionBefore.REGION_CLASS),
beforeArea);
}
}
if (simpleMaster.getRegion(RegionAfter.REGION_CLASS) != null && (currentPage.getAfter() != null)) {
Flow staticFlow = (Flow)_flowMap.get(simpleMaster.getRegion(RegionAfter.REGION_CLASS).getRegionName());
if (staticFlow != null) {
AreaContainer afterArea = currentPage.getAfter();
afterArea.setIDReferences(areaTree.getIDReferences());
layoutStaticContent(staticFlow, simpleMaster.getRegion(RegionAfter.REGION_CLASS),
afterArea);
}
}
}
private void layoutStaticContent(Flow flow, Region region,
AreaContainer area)
throws FOPException
{
if (flow instanceof StaticContent) {
AreaContainer beforeArea = currentPage.getBefore();
((StaticContent)flow).layout(area, region);
}
else {
MessageHandler.errorln("WARNING: "+region.getName()+" only supports static-content flows currently. Cannot use flow named '"+flow.getFlowName()+"'");
}
}
/**
* Returns the next SubSequenceSpecifier for the given page sequence master. The result
* is bassed on the current state of this page sequence.
*/
// refactored from PageSequenceMaster
private SubSequenceSpecifier getNextSubsequence(PageSequenceMaster master)
{
if (master.getSubSequenceSpecifierCount() > currentSubsequenceNumber + 1) {
currentSubsequence = master.getSubSequenceSpecifier(currentSubsequenceNumber + 1);
currentSubsequenceNumber++;
return currentSubsequence;
}
else {
return null;
}
}
/**
* Returns the next simple page master for the given sequence master, page number and
* other state information
*/
private SimplePageMaster getNextSimplePageMaster(PageSequenceMaster sequenceMaster,
int currentPageNumber,
boolean thisIsFirstPage,
boolean isEmptyPage)
{
String nextPageMaster = getNextPageMasterName(sequenceMaster, currentPageNumber, thisIsFirstPage, isEmptyPage);
return this.layoutMasterSet.getSimplePageMaster( nextPageMaster );
}
private String getNextPageMasterName(PageSequenceMaster sequenceMaster,
int currentPageNumber,
boolean thisIsFirstPage,
boolean isEmptyPage)
{
if (null == currentSubsequence) {
currentSubsequence = getNextSubsequence(sequenceMaster);
}
String nextPageMaster =
currentSubsequence.getNextPageMaster( currentPageNumber, thisIsFirstPage, isEmptyPage );
if (null == nextPageMaster || isFlowForMasterNameDone(currentPageMasterName)) {
SubSequenceSpecifier nextSubsequence = getNextSubsequence(sequenceMaster);
if (nextSubsequence == null) {
MessageHandler.errorln("\nWARNING: Page subsequences exhausted. Using previous subsequence.");
thisIsFirstPage = true; // this becomes the first page in the new (old really) page master
currentSubsequence.reset();
// we leave currentSubsequence alone
}
else {
currentSubsequence = nextSubsequence;
}
nextPageMaster = currentSubsequence.getNextPageMaster( currentPageNumber,
thisIsFirstPage,
isEmptyPage);
}
currentPageMasterName = nextPageMaster;
return nextPageMaster;
}
private SimplePageMaster getCurrentSimplePageMaster()
{
return this.layoutMasterSet.getSimplePageMaster( currentPageMasterName );
}
private String getCurrentPageMasterName()
{
return currentPageMasterName;
}
// refactored from LayoutMasterSet
private PageMaster getNextPageMaster(String pageSequenceName,
int currentPageNumber,
boolean thisIsFirstPage,
boolean isEmptyPage )
throws FOPException
{
PageMaster pageMaster = null;
// see if there is a page master sequence for this master name
PageSequenceMaster sequenceMaster =
this.layoutMasterSet.getPageSequenceMaster( pageSequenceName );
if (sequenceMaster != null) {
pageMaster = getNextSimplePageMaster(sequenceMaster,
currentPageNumber,
thisIsFirstPage,
isEmptyPage).getPageMaster();
} else { // otherwise see if there's a simple master by the given name
SimplePageMaster simpleMaster =
this.layoutMasterSet.getSimplePageMaster( pageSequenceName );
if (simpleMaster == null) {
throw new FOPException( "'master-name' for 'fo:page-sequence'" +
"matches no 'simple-page-master' or 'page-sequence-master'" );
}
currentPageMasterName = pageSequenceName;
pageMaster = simpleMaster.getNextPageMaster();
}
return pageMaster;
}
/**
* Returns true when there is more flow elements left to lay out.
*/
private boolean flowsAreIncomplete()
{
boolean isIncomplete = false;
for (Enumeration e = _flowMap.elements(); e.hasMoreElements(); ) {
Flow flow = (Flow)e.nextElement();
if (flow instanceof StaticContent) {
continue;
}
Status status = flow.getStatus();
isIncomplete |= status.isIncomplete();
}
return isIncomplete;
}
/**
* Returns the flow that maps to the given region class for the current
* page master.
*/
private Flow getCurrentFlow(String regionClass)
{
Region region = getCurrentSimplePageMaster().getRegion(regionClass);
if (region != null) {
Flow flow = (Flow)_flowMap.get(region.getRegionName());
return flow;
}
else {
System.out.println("flow is null. regionClass = '"+regionClass+"' currentSPM = "+getCurrentSimplePageMaster());
return null;
}
}
private boolean isFlowForMasterNameDone( String masterName )
{
// parameter is master-name of PMR; we need to locate PM
// referenced by this, and determine whether flow(s) are OK
if (masterName != null) {
SimplePageMaster spm = this.layoutMasterSet.getSimplePageMaster( masterName );
Region region = spm.getRegion(RegionBody.REGION_CLASS);
Flow flow = (Flow)_flowMap.get( region.getRegionName() );
if ((null == flow) || flow.getStatus().isIncomplete())
return false;
else
return true;
}
return false;
}
public boolean isFlowSet()
{
return isFlowSet;
}
public void setIsFlowSet(boolean isFlowSet)
{
this.isFlowSet = isFlowSet;
}
}