You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PageSequence.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.fo.pagination;
  19. import java.util.LinkedList;
  20. import java.util.List;
  21. import java.util.Locale;
  22. import java.util.Map;
  23. import java.util.Stack;
  24. import org.xml.sax.Locator;
  25. import org.apache.fop.apps.FOPException;
  26. import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
  27. import org.apache.fop.datatypes.Numeric;
  28. import org.apache.fop.fo.FONode;
  29. import org.apache.fop.fo.PropertyList;
  30. import org.apache.fop.fo.ValidationException;
  31. import org.apache.fop.fo.flow.ChangeBar;
  32. import org.apache.fop.fo.properties.CommonHyphenation;
  33. import org.apache.fop.traits.Direction;
  34. import org.apache.fop.traits.WritingMode;
  35. import org.apache.fop.traits.WritingModeTraits;
  36. import org.apache.fop.traits.WritingModeTraitsGetter;
  37. /**
  38. * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_page-sequence">
  39. * <code>fo:page-sequence</code></a> object.
  40. */
  41. public class PageSequence extends AbstractPageSequence implements WritingModeTraitsGetter {
  42. private String masterReference;
  43. private Numeric referenceOrientation;
  44. private WritingModeTraits writingModeTraits;
  45. private Locale locale;
  46. // There doesn't seem to be anything in the spec requiring flows
  47. // to be in the order given, only that they map to the regions
  48. // defined in the page sequence, so all we need is this one hashmap
  49. // the set of flows includes StaticContent flows also
  50. /** Map of flows to their flow name (flow-name, Flow) */
  51. private Map<String, FONode> flowMap;
  52. /**
  53. * The currentSimplePageMaster is either the page master for the
  54. * whole page sequence if master-reference refers to a simple-page-master,
  55. * or the simple page master produced by the page sequence master otherwise.
  56. * The pageSequenceMaster is null if master-reference refers to a
  57. * simple-page-master.
  58. */
  59. private SimplePageMaster simplePageMaster;
  60. private PageSequenceMaster pageSequenceMaster;
  61. /**
  62. * The fo:title object for this page-sequence.
  63. */
  64. private Title titleFO;
  65. /**
  66. * The fo:flow object for this page-sequence.
  67. */
  68. private Flow mainFlow;
  69. /**
  70. * Active change bars
  71. */
  72. private final List<ChangeBar> changeBarList = new LinkedList<ChangeBar>();
  73. /**
  74. * Create a PageSequence instance that is a child of the
  75. * given {@link FONode}.
  76. *
  77. * @param parent the parent {@link FONode}
  78. */
  79. public PageSequence(FONode parent) {
  80. super(parent);
  81. }
  82. /** {@inheritDoc} */
  83. public void bind(PropertyList pList) throws FOPException {
  84. super.bind(pList);
  85. String country = pList.get(PR_COUNTRY).getString();
  86. String language = pList.get(PR_LANGUAGE).getString();
  87. locale = CommonHyphenation.toLocale(language, country);
  88. masterReference = pList.get(PR_MASTER_REFERENCE).getString();
  89. referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
  90. writingModeTraits = new WritingModeTraits(
  91. WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()),
  92. pList.getExplicit(PR_WRITING_MODE) != null);
  93. if (masterReference == null || masterReference.equals("")) {
  94. missingPropertyError("master-reference");
  95. }
  96. }
  97. /** {@inheritDoc} */
  98. public void startOfNode() throws FOPException {
  99. super.startOfNode();
  100. flowMap = new java.util.HashMap<String, FONode>();
  101. this.simplePageMaster
  102. = getRoot().getLayoutMasterSet().getSimplePageMaster(masterReference);
  103. if (simplePageMaster == null) {
  104. this.pageSequenceMaster
  105. = getRoot().getLayoutMasterSet().getPageSequenceMaster(masterReference);
  106. if (pageSequenceMaster == null) {
  107. getFOValidationEventProducer().masterNotFound(this, getName(),
  108. masterReference, getLocator());
  109. }
  110. }
  111. getRoot().addPageSequence(this);
  112. getFOEventHandler().startPageSequence(this);
  113. }
  114. /** {@inheritDoc} */
  115. public void endOfNode() throws FOPException {
  116. if (mainFlow == null) {
  117. missingChildElementError("(title?,static-content*,flow)");
  118. }
  119. getFOEventHandler().endPageSequence(this);
  120. }
  121. /**
  122. * {@inheritDoc}
  123. XSL Content Model: (title?,static-content*,flow)
  124. */
  125. protected void validateChildNode(Locator loc, String nsURI, String localName)
  126. throws ValidationException {
  127. if (FO_URI.equals(nsURI)) {
  128. if ("title".equals(localName)) {
  129. if (titleFO != null) {
  130. tooManyNodesError(loc, "fo:title");
  131. } else if (!flowMap.isEmpty()) {
  132. nodesOutOfOrderError(loc, "fo:title", "fo:static-content");
  133. } else if (mainFlow != null) {
  134. nodesOutOfOrderError(loc, "fo:title", "fo:flow");
  135. }
  136. } else if ("static-content".equals(localName)) {
  137. if (mainFlow != null) {
  138. nodesOutOfOrderError(loc, "fo:static-content", "fo:flow");
  139. }
  140. } else if ("flow".equals(localName)) {
  141. if (mainFlow != null) {
  142. tooManyNodesError(loc, "fo:flow");
  143. }
  144. } else {
  145. invalidChildError(loc, nsURI, localName);
  146. }
  147. }
  148. }
  149. /**
  150. * {@inheritDoc}
  151. * TODO see if addChildNode() should also be called for fo's other than
  152. * fo:flow.
  153. */
  154. public void addChildNode(FONode child) throws FOPException {
  155. int childId = child.getNameId();
  156. switch (childId) {
  157. case FO_TITLE:
  158. this.titleFO = (Title)child;
  159. break;
  160. case FO_FLOW:
  161. this.mainFlow = (Flow)child;
  162. addFlow(mainFlow);
  163. break;
  164. case FO_STATIC_CONTENT:
  165. addFlow((StaticContent)child);
  166. flowMap.put(((Flow)child).getFlowName(), (Flow)child);
  167. break;
  168. default:
  169. super.addChildNode(child);
  170. }
  171. }
  172. /**
  173. * Add a flow or static content, mapped by its flow-name.
  174. * The flow-name is used to associate the flow with a region on a page,
  175. * based on the region-names given to the regions in the page-master
  176. * used to generate that page.
  177. * @param flow the {@link Flow} instance to be added
  178. * @throws org.apache.fop.fo.ValidationException if the fo:flow maps
  179. * to an invalid page-region
  180. */
  181. private void addFlow(Flow flow) throws ValidationException {
  182. String flowName = flow.getFlowName();
  183. if (hasFlowName(flowName)) {
  184. getFOValidationEventProducer().duplicateFlowNameInPageSequence(this, flow.getName(),
  185. flowName, flow.getLocator());
  186. }
  187. if (!hasRegion(flowName) && !flowName.equals("xsl-before-float-separator")
  188. && !flowName.equals("xsl-footnote-separator")) {
  189. getFOValidationEventProducer().flowNameNotMapped(this, flow.getName(),
  190. flowName, flow.getLocator());
  191. }
  192. }
  193. private boolean hasRegion(String flowName) {
  194. LayoutMasterSet set = getRoot().getLayoutMasterSet();
  195. PageSequenceMaster psm = set.getPageSequenceMaster(masterReference);
  196. return (psm != null) ? psm.getLayoutMasterSet().regionNameExists(flowName)
  197. : set.getSimplePageMaster(masterReference).regionNameExists(flowName);
  198. }
  199. /**
  200. * Get the static content FO node from the flow map.
  201. * This gets the static content flow for the given flow name.
  202. *
  203. * @param name the flow name to find
  204. * @return the static content FO node
  205. */
  206. public StaticContent getStaticContent(String name) {
  207. return (StaticContent) flowMap.get(name);
  208. }
  209. /**
  210. * Accessor method for the fo:title associated with this fo:page-sequence
  211. * @return titleFO for this object
  212. */
  213. public Title getTitleFO() {
  214. return titleFO;
  215. }
  216. /**
  217. * Public accessor for getting the MainFlow to which this PageSequence is
  218. * attached.
  219. * @return the MainFlow object to which this PageSequence is attached.
  220. */
  221. public Flow getMainFlow() {
  222. return mainFlow;
  223. }
  224. /**
  225. * Determine if this PageSequence already has a flow with the given flow-name
  226. * Used for validation of incoming fo:flow or fo:static-content objects
  227. * @param flowName The flow-name to search for
  228. * @return true if flow-name already defined within this page sequence,
  229. * false otherwise
  230. */
  231. public boolean hasFlowName(String flowName) {
  232. return flowMap.containsKey(flowName);
  233. }
  234. /** @return the flow map for this page-sequence */
  235. public Map<String, FONode> getFlowMap() {
  236. return this.flowMap;
  237. }
  238. /**
  239. * Public accessor for determining the next page master to use within this page sequence.
  240. * @param page the page number of the page to be created
  241. * @param isFirstPage indicator whether this page is the first page of the
  242. * page sequence
  243. * @param isLastPage indicator whether this page is the last page of the
  244. * page sequence
  245. * @param isBlank indicator whether the page will be blank
  246. * @return the SimplePageMaster to use for this page
  247. * @throws PageProductionException if there's a problem determining the page master
  248. */
  249. public SimplePageMaster getNextSimplePageMaster(
  250. int page, boolean isFirstPage, boolean isLastPage, boolean isBlank)
  251. throws PageProductionException {
  252. if (pageSequenceMaster == null) {
  253. return simplePageMaster;
  254. }
  255. boolean isOddPage = ((page % 2) != 0);
  256. if (log.isDebugEnabled()) {
  257. log.debug("getNextSimplePageMaster(page=" + page
  258. + " isOdd=" + isOddPage
  259. + " isFirst=" + isFirstPage
  260. + " isLast=" + isLastPage
  261. + " isBlank=" + isBlank + ")");
  262. }
  263. return pageSequenceMaster.getNextSimplePageMaster(isOddPage,
  264. isFirstPage, isLastPage, isBlank, getMainFlow().getFlowName());
  265. }
  266. /**
  267. * Used to set the "cursor position" for the page masters to the previous item.
  268. * @return true if there is a previous item, false if the current one was the first one.
  269. */
  270. public boolean goToPreviousSimplePageMaster() {
  271. return pageSequenceMaster == null || pageSequenceMaster.goToPreviousSimplePageMaster();
  272. }
  273. /** @return true if the page-sequence has a page-master with page-position="last" */
  274. public boolean hasPagePositionLast() {
  275. return pageSequenceMaster != null && pageSequenceMaster.hasPagePositionLast();
  276. }
  277. /** @return true if the page-sequence has a page-master with page-position="only" */
  278. public boolean hasPagePositionOnly() {
  279. return pageSequenceMaster != null && pageSequenceMaster.hasPagePositionOnly();
  280. }
  281. /**
  282. * Get the value of the <code>master-reference</code> trait.
  283. * @return the "master-reference" trait
  284. */
  285. public String getMasterReference() {
  286. return masterReference;
  287. }
  288. /** {@inheritDoc} */
  289. public String getLocalName() {
  290. return "page-sequence";
  291. }
  292. /**
  293. * {@inheritDoc}
  294. * @return {@link org.apache.fop.fo.Constants#FO_PAGE_SEQUENCE}
  295. */
  296. public int getNameId() {
  297. return FO_PAGE_SEQUENCE;
  298. }
  299. public Locale getLocale() {
  300. return locale;
  301. }
  302. /**
  303. * Get the value of the <code>reference-orientation</code> trait.
  304. * @return the reference orientation trait value
  305. */
  306. public int getReferenceOrientation() {
  307. if (referenceOrientation != null) {
  308. return referenceOrientation.getValue();
  309. } else {
  310. return 0;
  311. }
  312. }
  313. /**
  314. * {@inheritDoc}
  315. */
  316. public Direction getInlineProgressionDirection() {
  317. if (writingModeTraits != null) {
  318. return writingModeTraits.getInlineProgressionDirection();
  319. } else {
  320. return Direction.LR;
  321. }
  322. }
  323. /**
  324. * {@inheritDoc}
  325. */
  326. public Direction getBlockProgressionDirection() {
  327. if (writingModeTraits != null) {
  328. return writingModeTraits.getBlockProgressionDirection();
  329. } else {
  330. return Direction.TB;
  331. }
  332. }
  333. /**
  334. * {@inheritDoc}
  335. */
  336. public Direction getColumnProgressionDirection() {
  337. if (writingModeTraits != null) {
  338. return writingModeTraits.getColumnProgressionDirection();
  339. } else {
  340. return Direction.LR;
  341. }
  342. }
  343. /**
  344. * {@inheritDoc}
  345. */
  346. public Direction getRowProgressionDirection() {
  347. if (writingModeTraits != null) {
  348. return writingModeTraits.getRowProgressionDirection();
  349. } else {
  350. return Direction.TB;
  351. }
  352. }
  353. /**
  354. * {@inheritDoc}
  355. */
  356. public Direction getShiftDirection() {
  357. if (writingModeTraits != null) {
  358. return writingModeTraits.getShiftDirection();
  359. } else {
  360. return Direction.TB;
  361. }
  362. }
  363. /**
  364. * {@inheritDoc}
  365. */
  366. public WritingMode getWritingMode() {
  367. if (writingModeTraits != null) {
  368. return writingModeTraits.getWritingMode();
  369. } else {
  370. return WritingMode.LR_TB;
  371. }
  372. }
  373. /**
  374. * {@inheritDoc}
  375. */
  376. public boolean getExplicitWritingMode() {
  377. if (writingModeTraits != null) {
  378. return writingModeTraits.getExplicitWritingMode();
  379. } else {
  380. return false;
  381. }
  382. }
  383. @Override
  384. protected Stack<DelimitedTextRange> collectDelimitedTextRanges(Stack<DelimitedTextRange> ranges,
  385. DelimitedTextRange currentRange) {
  386. // collect ranges from static content flows
  387. Map<String, FONode> flows = getFlowMap();
  388. if (flows != null) {
  389. for (FONode fn : flows.values()) {
  390. if (fn instanceof StaticContent) {
  391. ranges = ((StaticContent) fn).collectDelimitedTextRanges(ranges);
  392. }
  393. }
  394. }
  395. // collect ranges in main flow
  396. Flow main = getMainFlow();
  397. if (main != null) {
  398. ranges = main.collectDelimitedTextRanges(ranges);
  399. }
  400. return ranges;
  401. }
  402. @Override
  403. protected boolean isBidiBoundary(boolean propagate) {
  404. return true;
  405. }
  406. /**
  407. * Releases a page-sequence's children after the page-sequence has been fully processed.
  408. */
  409. public void releasePageSequence() {
  410. this.mainFlow = null;
  411. this.flowMap.clear();
  412. }
  413. public SimplePageMaster getLastSimplePageMaster(int page, boolean isFirstPage, boolean isBlank) {
  414. boolean isOddPage = ((page % 2) != 0); // please findbugs...
  415. log.debug("getNextSimplePageMaster(page=" + page + " isOdd=" + isOddPage + " isFirst="
  416. + isFirstPage + " isLast=true" + " isBlank=" + isBlank + ")");
  417. if (pageSequenceMaster == null) {
  418. return simplePageMaster;
  419. }
  420. return pageSequenceMaster.getLastSimplePageMaster(isOddPage, isFirstPage, isBlank, getMainFlow()
  421. .getFlowName());
  422. }
  423. /**
  424. * Adds the specified change bar to the active change bar list.
  425. *
  426. * @param changeBarBegin The starting change bar element
  427. */
  428. public void pushChangeBar(ChangeBar changeBarBegin) {
  429. changeBarList.add(changeBarBegin);
  430. }
  431. /**
  432. * Removes the couple of the specified change bar from the active change bar list.
  433. *
  434. * @param changeBarEnd The ending change bar element
  435. */
  436. public void popChangeBar(ChangeBar changeBarEnd) {
  437. ChangeBar changeBarBegin = getChangeBarBegin(changeBarEnd);
  438. if (changeBarBegin != null) {
  439. changeBarList.remove(changeBarBegin);
  440. }
  441. }
  442. /**
  443. * Returns the starting counterpart of the specified ending change bar.
  444. *
  445. * @param changeBarEnd The ending change bar element
  446. * @return The starting counterpart of the specified ending change bar
  447. */
  448. public ChangeBar getChangeBarBegin(ChangeBar changeBarEnd) {
  449. if (changeBarList.isEmpty()) {
  450. return null;
  451. } else {
  452. String changeBarClass = changeBarEnd.getChangeBarClass();
  453. for (int i = changeBarList.size() - 1; i >= 0; i--) {
  454. ChangeBar changeBar = changeBarList.get(i);
  455. if (changeBar.getChangeBarClass().equals(changeBarClass)) {
  456. return changeBar;
  457. }
  458. }
  459. }
  460. return null;
  461. }
  462. /**
  463. * Tests if there are any active change bars.
  464. *
  465. * @return A boolean value true if there are any active change bars
  466. */
  467. public boolean hasChangeBars() {
  468. return !changeBarList.isEmpty();
  469. }
  470. /**
  471. * Returns the list of active change bars.
  472. *
  473. * @return The list of active change bars
  474. */
  475. public List<ChangeBar> getChangeBarList() {
  476. return changeBarList;
  477. }
  478. /**
  479. * Returns the copy of active change bars list.
  480. *
  481. * @return The list containing a copy of the active change bars
  482. */
  483. public List<ChangeBar> getClonedChangeBarList() {
  484. return new LinkedList<ChangeBar>(changeBarList);
  485. }
  486. }