| /* |
| * 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); |
| } |
| } |