blob: 56796f0da61a7e5171779195a1fe65e0b80eb8e9 [file] [log] [blame]
/*
* 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$ */
package org.apache.fop.fo.pagination;
// Java
import java.util.Map;
import org.xml.sax.Locator;
import org.apache.fop.apps.FOPException;
import org.apache.fop.datatypes.Numeric;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.ValidationException;
/**
* Implementation of the fo:page-sequence formatting object.
*/
public class PageSequence extends FObj {
// The value of properties relevant for fo:page-sequence.
private String country;
private String format;
private String language;
private int letterValue;
private char groupingSeparator;
private int groupingSize;
private String id;
private Numeric initialPageNumber;
private int forcePageCount;
private String masterReference;
// End of property values
/** The parent root object */
private Root root;
// 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 hashmap
// the set of flows includes StaticContent flows also
/** Map of flows to their flow name (flow-name, Flow) */
private Map flowMap;
private int startingPageNumber = 0;
private PageNumberGenerator pageNumberGenerator;
/**
* The currentSimplePageMaster is either the page master for the
* whole page sequence if master-reference refers to a simple-page-master,
* or the simple page master produced by the page sequence master otherwise.
* The pageSequenceMaster is null if master-reference refers to a
* simple-page-master.
*/
private SimplePageMaster simplePageMaster;
private PageSequenceMaster pageSequenceMaster;
/**
* The fo:title object for this page-sequence.
*/
private Title titleFO;
/**
* The fo:flow object for this page-sequence.
*/
private Flow mainFlow = null;
/**
* Create a page sequence FO node.
*
* @param parent the parent FO node
*/
public PageSequence(FONode parent) {
super(parent);
}
/**
* @see org.apache.fop.fo.FObj#bind(PropertyList)
*/
public void bind(PropertyList pList) throws FOPException {
country = pList.get(PR_COUNTRY).getString();
format = pList.get(PR_FORMAT).getString();
language = pList.get(PR_LANGUAGE).getString();
letterValue = pList.get(PR_LETTER_VALUE).getEnum();
groupingSeparator = pList.get(PR_GROUPING_SEPARATOR).getCharacter();
groupingSize = pList.get(PR_GROUPING_SIZE).getNumber().intValue();
id = pList.get(PR_ID).getString();
initialPageNumber = pList.get(PR_INITIAL_PAGE_NUMBER).getNumeric();
forcePageCount = pList.get(PR_FORCE_PAGE_COUNT).getEnum();
masterReference = pList.get(PR_MASTER_REFERENCE).getString();
if (masterReference == null || masterReference.equals("")) {
missingPropertyError("master-reference");
}
}
/**
* @see org.apache.fop.fo.FONode#startOfNode()
*/
protected void startOfNode() throws FOPException {
this.root = (Root) parent;
flowMap = new java.util.HashMap();
this.simplePageMaster = root.getLayoutMasterSet().getSimplePageMaster(masterReference);
if (this.simplePageMaster == null) {
this.pageSequenceMaster
= root.getLayoutMasterSet().getPageSequenceMaster(masterReference);
if (this.pageSequenceMaster == null) {
throw new ValidationException("master-reference '" + masterReference
+ "' for fo:page-sequence matches no"
+ " simple-page-master or page-sequence-master", locator);
} else {
pageSequenceMaster.reset();
}
}
this.pageNumberGenerator = new PageNumberGenerator(
format, groupingSeparator, groupingSize, letterValue);
checkId(id);
getFOEventHandler().startPageSequence(this);
}
/** @see org.apache.fop.fo.FONode#endOfNode() */
protected void endOfNode() throws FOPException {
if (mainFlow == null) {
missingChildElementError("(title?,static-content*,flow)");
}
getFOEventHandler().endPageSequence(this);
}
/**
* @see org.apache.fop.fo.FONode#validateChildNode(Locator, String, String)
XSL Content Model: (title?,static-content*,flow)
*/
protected void validateChildNode(Locator loc, String nsURI, String localName)
throws ValidationException {
if (FO_URI.equals(nsURI)) {
if (localName.equals("title")) {
if (titleFO != null) {
tooManyNodesError(loc, "fo:title");
} else if (flowMap.size() > 0) {
nodesOutOfOrderError(loc, "fo:title", "fo:static-content");
} else if (mainFlow != null) {
nodesOutOfOrderError(loc, "fo:title", "fo:flow");
}
} else if (localName.equals("static-content")) {
if (mainFlow != null) {
nodesOutOfOrderError(loc, "fo:static-content", "fo:flow");
}
} else if (localName.equals("flow")) {
if (mainFlow != null) {
tooManyNodesError(loc, "fo:flow");
}
} else {
invalidChildError(loc, nsURI, localName);
}
} else {
invalidChildError(loc, nsURI, localName);
}
}
/**
* @see org.apache.fop.fo.FONode#addChildNode(FONode)
* @todo see if addChildNode() should also be called for fo's other than
* fo:flow.
*/
public void addChildNode(FONode child) throws FOPException {
int childId = child.getNameId();
if (childId == FO_TITLE) {
this.titleFO = (Title) child;
} else if (childId == FO_FLOW) {
this.mainFlow = (Flow) child;
addFlow(mainFlow);
} else if (childId == FO_STATIC_CONTENT) {
addFlow((StaticContent) child);
String flowName = ((StaticContent) child).getFlowName();
flowMap.put(flowName, child);
}
}
/**
* Add a flow or static content, mapped by its flow-name.
* The flow-name is used to associate the flow with a region on a page,
* based on the region-names given to the regions in the page-master
* used to generate that page.
*/
private void addFlow(Flow flow) throws ValidationException {
String flowName = flow.getFlowName();
if (hasFlowName(flowName)) {
throw new ValidationException("duplicate flow-name \""
+ flowName
+ "\" found within fo:page-sequence", flow.getLocator());
}
if (!root.getLayoutMasterSet().regionNameExists(flowName)
&& !flowName.equals("xsl-before-float-separator")
&& !flowName.equals("xsl-footnote-separator")) {
throw new ValidationException("flow-name \""
+ flowName
+ "\" could not be mapped to a region-name in the"
+ " layout-master-set", flow.getLocator());
}
}
/**
* Initialize the current page number for the start of the page sequence.
*/
public void initPageNumber() {
int pageNumberType = 0;
if (initialPageNumber.getEnum() != 0) {
// auto | auto-odd | auto-even.
startingPageNumber = root.getEndingPageNumberOfPreviousSequence() + 1;
pageNumberType = initialPageNumber.getEnum();
if (pageNumberType == EN_AUTO_ODD) {
if (startingPageNumber % 2 == 0) {
startingPageNumber++;
}
} else if (pageNumberType == EN_AUTO_EVEN) {
if (startingPageNumber % 2 == 1) {
startingPageNumber++;
}
}
} else { // <integer> for explicit page number
int pageStart = initialPageNumber.getValue();
startingPageNumber = (pageStart > 0) ? pageStart : 1; // spec rule
}
}
// /**
// * Returns true when there is more flow elements left to lay out.
// */
// private boolean flowsAreIncomplete() {
// boolean isIncomplete = false;
// for (Iterator e = flowMap.values().iterator(); e.hasNext(); ) {
// Flow flow = (Flow)e.next();
// 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 {
// getLogger().error("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 (isForcing)
// return false;
// if (masterName != null) {
// SimplePageMaster spm =
// root.getLayoutMasterSet().getSimplePageMaster(masterName);
// Region region = spm.getRegion(FO_REGION_BODY);
// Flow flow = (Flow)flowMap.get(region.getRegionName());
// /*if ((null == flow) || flow.getStatus().isIncomplete())
// return false;
// else
// return true;*/
// }
// return false;
// }
/**
* Get the starting page number for this page sequence.
*
* @return the starting page number
*/
public int getStartingPageNumber() {
return startingPageNumber;
}
// private void forcePage(AreaTree areaTree, int firstAvailPageNumber) {
// boolean makePage = false;
// if (this.forcePageCount == ForcePageCount.AUTO) {
// PageSequence nextSequence =
// this.root.getSucceedingPageSequence(this);
// if (nextSequence != null) {
// if (nextSequence.getIpnValue().equals("auto")) {
// // do nothing special
// }
// else if (nextSequence.getIpnValue().equals("auto-odd")) {
// if (firstAvailPageNumber % 2 == 0) {
// makePage = true;
// }
// } else if (nextSequence.getIpnValue().equals("auto-even")) {
// if (firstAvailPageNumber % 2 != 0) {
// makePage = true;
// }
// } else {
// int nextSequenceStartPageNumber =
// nextSequence.getCurrentPageNumber();
// if ((nextSequenceStartPageNumber % 2 == 0)
// && (firstAvailPageNumber % 2 == 0)) {
// makePage = true;
// } else if ((nextSequenceStartPageNumber % 2 != 0)
// && (firstAvailPageNumber % 2 != 0)) {
// makePage = true;
// }
// }
// }
// } else if ((this.forcePageCount == ForcePageCount.EVEN)
// && (this.pageCount % 2 != 0)) {
// makePage = true;
// } else if ((this.forcePageCount == ForcePageCount.ODD)
// && (this.pageCount % 2 == 0)) {
// makePage = true;
// } else if ((this.forcePageCount == ForcePageCount.END_ON_EVEN)
// && (firstAvailPageNumber % 2 == 0)) {
// makePage = true;
// } else if ((this.forcePageCount == ForcePageCount.END_ON_ODD)
// && (firstAvailPageNumber % 2 != 0)) {
// makePage = true;
// } else if (this.forcePageCount == ForcePageCount.NO_FORCE) {
// // do nothing
// }
// if (makePage) {
// try {
// this.isForcing = true;
// this.currentPageNumber++;
// firstAvailPageNumber = this.currentPageNumber;
// currentPage = makePage(areaTree, firstAvailPageNumber, false,
// true);
// String formattedPageNumber =
// pageNumberGenerator.makeFormattedPageNumber(this.currentPageNumber);
// currentPage.setFormattedNumber(formattedPageNumber);
// currentPage.setPageSequence(this);
// formatStaticContent(areaTree);
// log.debug("[forced-" + firstAvailPageNumber + "]");
// areaTree.addPage(currentPage);
// this.root.setRunningPageNumberCounter(this.currentPageNumber);
// this.isForcing = false;
// } catch (FOPException fopex) {
// log.debug("'force-page-count' failure");
// }
// }
// }
/**
* Get the static content FO node from the flow map.
* This gets the static content flow for the given flow name.
*
* @param name the flow name to find
* @return the static content FO node
*/
public StaticContent getStaticContent(String name) {
return (StaticContent) flowMap.get(name);
}
/** @return the "id" property. */
public String getId() {
return id;
}
/**
* Accessor method for titleFO
* @return titleFO for this object
*/
public Title getTitleFO() {
return titleFO;
}
/**
* Public accessor for getting the MainFlow to which this PageSequence is
* attached.
* @return the MainFlow object to which this PageSequence is attached.
*/
public Flow getMainFlow() {
return mainFlow;
}
/**
* Determine if this PageSequence already has a flow with the given flow-name
* Used for validation of incoming fo:flow or fo:static-content objects
* @param flowName The flow-name to search for
* @return true if flow-name already defined within this page sequence,
* false otherwise
*/
public boolean hasFlowName(String flowName) {
return flowMap.containsKey(flowName);
}
/** @return the flow map for this page-sequence */
public Map getFlowMap() {
return this.flowMap;
}
/**
* Public accessor for determining the next page master to use within this page sequence.
* @param page the page number of the page to be created
* @param isFirstPage indicator whether this page is the first page of the
* page sequence
* @param isLastPage indicator whether this page is the last page of the
* page sequence
* @param isBlank indicator whether the page will be blank
* @return the SimplePageMaster to use for this page
* @throws FOPException if there's a problem determining the page master
*/
public SimplePageMaster getNextSimplePageMaster(int page,
boolean isFirstPage,
boolean isLastPage,
boolean isBlank) throws FOPException {
if (pageSequenceMaster == null) {
return simplePageMaster;
}
boolean isOddPage = ((page % 2) == 1);
if (getLogger().isDebugEnabled()) {
getLogger().debug("getNextSimplePageMaster(page=" + page
+ " isOdd=" + isOddPage
+ " isFirst=" + isFirstPage
+ " isLast=" + isLastPage
+ " isBlank=" + isBlank + ")");
}
return pageSequenceMaster.getNextSimplePageMaster(isOddPage,
isFirstPage, isLastPage, isBlank);
}
/**
* Used to set the "cursor position" for the page masters to the previous item.
* @return true if there is a previous item, false if the current one was the first one.
*/
public boolean goToPreviousSimplePageMaster() {
if (pageSequenceMaster == null) {
return true;
} else {
return pageSequenceMaster.goToPreviousSimplePageMaster();
}
}
/** @return true if the page-sequence has a page-master with page-position="last" */
public boolean hasPagePositionLast() {
if (pageSequenceMaster == null) {
return false;
} else {
return pageSequenceMaster.hasPagePositionLast();
}
}
/**
* Retrieves the string representation of a page number applicable
* for this page sequence
* @param pageNumber the page number
* @return string representation of the page number
*/
public String makeFormattedPageNumber(int pageNumber) {
return pageNumberGenerator.makeFormattedPageNumber(pageNumber);
}
/**
* Public accessor for the ancestor Root.
* @return the ancestor Root
*/
public Root getRoot() {
return root;
}
/** @return the "master-reference" property. */
public String getMasterReference() {
return masterReference;
}
/** @see org.apache.fop.fo.FONode#getLocalName() */
public String getLocalName() {
return "page-sequence";
}
/** @see org.apache.fop.fo.FObj#getNameId() */
public int getNameId() {
return FO_PAGE_SEQUENCE;
}
/** @return the force-page-count value */
public int getForcePageCount() {
return forcePageCount;
}
/** @return the initial-page-number property value */
public Numeric getInitialPageNumber() {
return initialPageNumber;
}
/** @return the country property value */
public String getCountry() {
return this.country;
}
/** @return the language property value */
public String getLanguage() {
return this.language;
}
}