blob: 79955ff74e4dd57ab3998aef0ef370090059148f [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;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import org.xml.sax.Locator;
import org.apache.fop.apps.FOPException;
import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
import org.apache.fop.datatypes.Numeric;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.ValidationException;
import org.apache.fop.fo.flow.ChangeBar;
import org.apache.fop.fo.properties.CommonHyphenation;
import org.apache.fop.traits.Direction;
import org.apache.fop.traits.WritingMode;
import org.apache.fop.traits.WritingModeTraits;
import org.apache.fop.traits.WritingModeTraitsGetter;
/**
* Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_page-sequence">
* <code>fo:page-sequence</code></a> object.
*/
public class PageSequence extends AbstractPageSequence implements WritingModeTraitsGetter {
private String masterReference;
private Numeric referenceOrientation;
private WritingModeTraits writingModeTraits;
private Locale locale;
// 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<String, FONode> flowMap;
/**
* 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;
/**
* Active change bars
*/
private final List<ChangeBar> changeBarList = new LinkedList<ChangeBar>();
/**
* Create a PageSequence instance that is a child of the
* given {@link FONode}.
*
* @param parent the parent {@link FONode}
*/
public PageSequence(FONode parent) {
super(parent);
}
/** {@inheritDoc} */
public void bind(PropertyList pList) throws FOPException {
super.bind(pList);
String country = pList.get(PR_COUNTRY).getString();
String language = pList.get(PR_LANGUAGE).getString();
locale = CommonHyphenation.toLocale(language, country);
masterReference = pList.get(PR_MASTER_REFERENCE).getString();
referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
writingModeTraits = new WritingModeTraits(
WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()),
pList.getExplicit(PR_WRITING_MODE) != null);
if (masterReference == null || masterReference.equals("")) {
missingPropertyError("master-reference");
}
}
/** {@inheritDoc} */
public void startOfNode() throws FOPException {
super.startOfNode();
flowMap = new java.util.HashMap<String, FONode>();
this.simplePageMaster
= getRoot().getLayoutMasterSet().getSimplePageMaster(masterReference);
if (simplePageMaster == null) {
this.pageSequenceMaster
= getRoot().getLayoutMasterSet().getPageSequenceMaster(masterReference);
if (pageSequenceMaster == null) {
getFOValidationEventProducer().masterNotFound(this, getName(),
masterReference, getLocator());
}
}
getRoot().addPageSequence(this);
getFOEventHandler().startPageSequence(this);
}
/** {@inheritDoc} */
public void endOfNode() throws FOPException {
if (mainFlow == null) {
missingChildElementError("(title?,static-content*,flow)");
}
getFOEventHandler().endPageSequence(this);
}
/**
* {@inheritDoc}
XSL Content Model: (title?,static-content*,flow)
*/
protected void validateChildNode(Locator loc, String nsURI, String localName)
throws ValidationException {
if (FO_URI.equals(nsURI)) {
if ("title".equals(localName)) {
if (titleFO != null) {
tooManyNodesError(loc, "fo:title");
} else if (!flowMap.isEmpty()) {
nodesOutOfOrderError(loc, "fo:title", "fo:static-content");
} else if (mainFlow != null) {
nodesOutOfOrderError(loc, "fo:title", "fo:flow");
}
} else if ("static-content".equals(localName)) {
if (mainFlow != null) {
nodesOutOfOrderError(loc, "fo:static-content", "fo:flow");
}
} else if ("flow".equals(localName)) {
if (mainFlow != null) {
tooManyNodesError(loc, "fo:flow");
}
} else {
invalidChildError(loc, nsURI, localName);
}
}
}
/**
* {@inheritDoc}
* 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();
switch (childId) {
case FO_TITLE:
this.titleFO = (Title)child;
break;
case FO_FLOW:
this.mainFlow = (Flow)child;
addFlow(mainFlow);
break;
case FO_STATIC_CONTENT:
addFlow((StaticContent)child);
flowMap.put(((Flow)child).getFlowName(), (Flow)child);
break;
default:
super.addChildNode(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.
* @param flow the {@link Flow} instance to be added
* @throws org.apache.fop.fo.ValidationException if the fo:flow maps
* to an invalid page-region
*/
private void addFlow(Flow flow) throws ValidationException {
String flowName = flow.getFlowName();
if (hasFlowName(flowName)) {
getFOValidationEventProducer().duplicateFlowNameInPageSequence(this, flow.getName(),
flowName, flow.getLocator());
}
if (!hasRegion(flowName) && !flowName.equals("xsl-before-float-separator")
&& !flowName.equals("xsl-footnote-separator")) {
getFOValidationEventProducer().flowNameNotMapped(this, flow.getName(),
flowName, flow.getLocator());
}
}
private boolean hasRegion(String flowName) {
LayoutMasterSet set = getRoot().getLayoutMasterSet();
PageSequenceMaster psm = set.getPageSequenceMaster(masterReference);
return (psm != null) ? psm.getLayoutMasterSet().regionNameExists(flowName)
: set.getSimplePageMaster(masterReference).regionNameExists(flowName);
}
/**
* 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);
}
/**
* Accessor method for the fo:title associated with this fo:page-sequence
* @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<String, FONode> 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 PageProductionException if there's a problem determining the page master
*/
public SimplePageMaster getNextSimplePageMaster(
int page, boolean isFirstPage, boolean isLastPage, boolean isBlank)
throws PageProductionException {
if (pageSequenceMaster == null) {
return simplePageMaster;
}
boolean isOddPage = ((page % 2) != 0);
if (log.isDebugEnabled()) {
log.debug("getNextSimplePageMaster(page=" + page
+ " isOdd=" + isOddPage
+ " isFirst=" + isFirstPage
+ " isLast=" + isLastPage
+ " isBlank=" + isBlank + ")");
}
return pageSequenceMaster.getNextSimplePageMaster(isOddPage,
isFirstPage, isLastPage, isBlank, getMainFlow().getFlowName());
}
/**
* 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() {
return pageSequenceMaster == null || pageSequenceMaster.goToPreviousSimplePageMaster();
}
/** @return true if the page-sequence has a page-master with page-position="last" */
public boolean hasPagePositionLast() {
return pageSequenceMaster != null && pageSequenceMaster.hasPagePositionLast();
}
/** @return true if the page-sequence has a page-master with page-position="only" */
public boolean hasPagePositionOnly() {
return pageSequenceMaster != null && pageSequenceMaster.hasPagePositionOnly();
}
/**
* Get the value of the <code>master-reference</code> trait.
* @return the "master-reference" trait
*/
public String getMasterReference() {
return masterReference;
}
/** {@inheritDoc} */
public String getLocalName() {
return "page-sequence";
}
/**
* {@inheritDoc}
* @return {@link org.apache.fop.fo.Constants#FO_PAGE_SEQUENCE}
*/
public int getNameId() {
return FO_PAGE_SEQUENCE;
}
public Locale getLocale() {
return locale;
}
/**
* Get the value of the <code>reference-orientation</code> trait.
* @return the reference orientation trait value
*/
public int getReferenceOrientation() {
if (referenceOrientation != null) {
return referenceOrientation.getValue();
} else {
return 0;
}
}
/**
* {@inheritDoc}
*/
public Direction getInlineProgressionDirection() {
if (writingModeTraits != null) {
return writingModeTraits.getInlineProgressionDirection();
} else {
return Direction.LR;
}
}
/**
* {@inheritDoc}
*/
public Direction getBlockProgressionDirection() {
if (writingModeTraits != null) {
return writingModeTraits.getBlockProgressionDirection();
} else {
return Direction.TB;
}
}
/**
* {@inheritDoc}
*/
public Direction getColumnProgressionDirection() {
if (writingModeTraits != null) {
return writingModeTraits.getColumnProgressionDirection();
} else {
return Direction.LR;
}
}
/**
* {@inheritDoc}
*/
public Direction getRowProgressionDirection() {
if (writingModeTraits != null) {
return writingModeTraits.getRowProgressionDirection();
} else {
return Direction.TB;
}
}
/**
* {@inheritDoc}
*/
public Direction getShiftDirection() {
if (writingModeTraits != null) {
return writingModeTraits.getShiftDirection();
} else {
return Direction.TB;
}
}
/**
* {@inheritDoc}
*/
public WritingMode getWritingMode() {
if (writingModeTraits != null) {
return writingModeTraits.getWritingMode();
} else {
return WritingMode.LR_TB;
}
}
/**
* {@inheritDoc}
*/
public boolean getExplicitWritingMode() {
if (writingModeTraits != null) {
return writingModeTraits.getExplicitWritingMode();
} else {
return false;
}
}
@Override
protected Stack<DelimitedTextRange> collectDelimitedTextRanges(Stack<DelimitedTextRange> ranges,
DelimitedTextRange currentRange) {
// collect ranges from static content flows
Map<String, FONode> flows = getFlowMap();
if (flows != null) {
for (FONode fn : flows.values()) {
if (fn instanceof StaticContent) {
ranges = ((StaticContent) fn).collectDelimitedTextRanges(ranges);
}
}
}
// collect ranges in main flow
Flow main = getMainFlow();
if (main != null) {
ranges = main.collectDelimitedTextRanges(ranges);
}
return ranges;
}
@Override
protected boolean isBidiBoundary(boolean propagate) {
return true;
}
/**
* Releases a page-sequence's children after the page-sequence has been fully processed.
*/
public void releasePageSequence() {
this.mainFlow = null;
this.flowMap.clear();
}
public SimplePageMaster getLastSimplePageMaster(int page, boolean isFirstPage, boolean isBlank) {
boolean isOddPage = ((page % 2) != 0); // please findbugs...
log.debug("getNextSimplePageMaster(page=" + page + " isOdd=" + isOddPage + " isFirst="
+ isFirstPage + " isLast=true" + " isBlank=" + isBlank + ")");
if (pageSequenceMaster == null) {
return simplePageMaster;
}
return pageSequenceMaster.getLastSimplePageMaster(isOddPage, isFirstPage, isBlank, getMainFlow()
.getFlowName());
}
/**
* Adds the specified change bar to the active change bar list.
*
* @param changeBarBegin The starting change bar element
*/
public void pushChangeBar(ChangeBar changeBarBegin) {
changeBarList.add(changeBarBegin);
}
/**
* Removes the couple of the specified change bar from the active change bar list.
*
* @param changeBarEnd The ending change bar element
*/
public void popChangeBar(ChangeBar changeBarEnd) {
ChangeBar changeBarBegin = getChangeBarBegin(changeBarEnd);
if (changeBarBegin != null) {
changeBarList.remove(changeBarBegin);
}
}
/**
* Returns the starting counterpart of the specified ending change bar.
*
* @param changeBarEnd The ending change bar element
* @return The starting counterpart of the specified ending change bar
*/
public ChangeBar getChangeBarBegin(ChangeBar changeBarEnd) {
if (changeBarList.isEmpty()) {
return null;
} else {
String changeBarClass = changeBarEnd.getChangeBarClass();
for (int i = changeBarList.size() - 1; i >= 0; i--) {
ChangeBar changeBar = changeBarList.get(i);
if (changeBar.getChangeBarClass().equals(changeBarClass)) {
return changeBar;
}
}
}
return null;
}
/**
* Tests if there are any active change bars.
*
* @return A boolean value true if there are any active change bars
*/
public boolean hasChangeBars() {
return !changeBarList.isEmpty();
}
/**
* Returns the list of active change bars.
*
* @return The list of active change bars
*/
public List<ChangeBar> getChangeBarList() {
return changeBarList;
}
/**
* Returns the copy of active change bars list.
*
* @return The list containing a copy of the active change bars
*/
public List<ChangeBar> getClonedChangeBarList() {
return new LinkedList<ChangeBar>(changeBarList);
}
public void setOnlyTryInfinite(boolean b) {
if (pageSequenceMaster != null) {
pageSequenceMaster.onlyTryInfinite = b;
}
}
}