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 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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. // Java
  20. import java.util.Map;
  21. import org.xml.sax.Locator;
  22. import org.apache.fop.apps.FOPException;
  23. import org.apache.fop.datatypes.Numeric;
  24. import org.apache.fop.fo.FONode;
  25. import org.apache.fop.fo.FObj;
  26. import org.apache.fop.fo.PropertyList;
  27. import org.apache.fop.fo.ValidationException;
  28. /**
  29. * Implementation of the fo:page-sequence formatting object.
  30. */
  31. public class PageSequence extends FObj {
  32. // The value of properties relevant for fo:page-sequence.
  33. private String country;
  34. private String format;
  35. private String language;
  36. private int letterValue;
  37. private char groupingSeparator;
  38. private int groupingSize;
  39. private String id;
  40. private Numeric initialPageNumber;
  41. private int forcePageCount;
  42. private String masterReference;
  43. // End of property values
  44. /** The parent root object */
  45. private Root root;
  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 flowMap;
  52. private int startingPageNumber = 0;
  53. private PageNumberGenerator pageNumberGenerator;
  54. /**
  55. * The currentSimplePageMaster is either the page master for the
  56. * whole page sequence if master-reference refers to a simple-page-master,
  57. * or the simple page master produced by the page sequence master otherwise.
  58. * The pageSequenceMaster is null if master-reference refers to a
  59. * simple-page-master.
  60. */
  61. private SimplePageMaster simplePageMaster;
  62. private PageSequenceMaster pageSequenceMaster;
  63. /**
  64. * The fo:title object for this page-sequence.
  65. */
  66. private Title titleFO;
  67. /**
  68. * The fo:flow object for this page-sequence.
  69. */
  70. private Flow mainFlow = null;
  71. /**
  72. * Create a page sequence FO node.
  73. *
  74. * @param parent the parent FO node
  75. */
  76. public PageSequence(FONode parent) {
  77. super(parent);
  78. }
  79. /**
  80. * @see org.apache.fop.fo.FObj#bind(PropertyList)
  81. */
  82. public void bind(PropertyList pList) throws FOPException {
  83. country = pList.get(PR_COUNTRY).getString();
  84. format = pList.get(PR_FORMAT).getString();
  85. language = pList.get(PR_LANGUAGE).getString();
  86. letterValue = pList.get(PR_LETTER_VALUE).getEnum();
  87. groupingSeparator = pList.get(PR_GROUPING_SEPARATOR).getCharacter();
  88. groupingSize = pList.get(PR_GROUPING_SIZE).getNumber().intValue();
  89. id = pList.get(PR_ID).getString();
  90. initialPageNumber = pList.get(PR_INITIAL_PAGE_NUMBER).getNumeric();
  91. forcePageCount = pList.get(PR_FORCE_PAGE_COUNT).getEnum();
  92. masterReference = pList.get(PR_MASTER_REFERENCE).getString();
  93. if (masterReference == null || masterReference.equals("")) {
  94. missingPropertyError("master-reference");
  95. }
  96. }
  97. /**
  98. * @see org.apache.fop.fo.FONode#startOfNode()
  99. */
  100. protected void startOfNode() throws FOPException {
  101. this.root = (Root) parent;
  102. flowMap = new java.util.HashMap();
  103. this.simplePageMaster = root.getLayoutMasterSet().getSimplePageMaster(masterReference);
  104. if (this.simplePageMaster == null) {
  105. this.pageSequenceMaster
  106. = root.getLayoutMasterSet().getPageSequenceMaster(masterReference);
  107. if (this.pageSequenceMaster == null) {
  108. throw new ValidationException("master-reference '" + masterReference
  109. + "' for fo:page-sequence matches no"
  110. + " simple-page-master or page-sequence-master", locator);
  111. } else {
  112. pageSequenceMaster.reset();
  113. }
  114. }
  115. this.pageNumberGenerator = new PageNumberGenerator(
  116. format, groupingSeparator, groupingSize, letterValue);
  117. checkId(id);
  118. getFOEventHandler().startPageSequence(this);
  119. }
  120. /** @see org.apache.fop.fo.FONode#endOfNode() */
  121. protected void endOfNode() throws FOPException {
  122. if (mainFlow == null) {
  123. missingChildElementError("(title?,static-content*,flow)");
  124. }
  125. getFOEventHandler().endPageSequence(this);
  126. }
  127. /**
  128. * @see org.apache.fop.fo.FONode#validateChildNode(Locator, String, String)
  129. XSL Content Model: (title?,static-content*,flow)
  130. */
  131. protected void validateChildNode(Locator loc, String nsURI, String localName)
  132. throws ValidationException {
  133. if (FO_URI.equals(nsURI)) {
  134. if (localName.equals("title")) {
  135. if (titleFO != null) {
  136. tooManyNodesError(loc, "fo:title");
  137. } else if (flowMap.size() > 0) {
  138. nodesOutOfOrderError(loc, "fo:title", "fo:static-content");
  139. } else if (mainFlow != null) {
  140. nodesOutOfOrderError(loc, "fo:title", "fo:flow");
  141. }
  142. } else if (localName.equals("static-content")) {
  143. if (mainFlow != null) {
  144. nodesOutOfOrderError(loc, "fo:static-content", "fo:flow");
  145. }
  146. } else if (localName.equals("flow")) {
  147. if (mainFlow != null) {
  148. tooManyNodesError(loc, "fo:flow");
  149. }
  150. } else {
  151. invalidChildError(loc, nsURI, localName);
  152. }
  153. } else {
  154. invalidChildError(loc, nsURI, localName);
  155. }
  156. }
  157. /**
  158. * @see org.apache.fop.fo.FONode#addChildNode(FONode)
  159. * @todo see if addChildNode() should also be called for fo's other than
  160. * fo:flow.
  161. */
  162. public void addChildNode(FONode child) throws FOPException {
  163. int childId = child.getNameId();
  164. if (childId == FO_TITLE) {
  165. this.titleFO = (Title) child;
  166. } else if (childId == FO_FLOW) {
  167. this.mainFlow = (Flow) child;
  168. addFlow(mainFlow);
  169. } else if (childId == FO_STATIC_CONTENT) {
  170. addFlow((StaticContent) child);
  171. String flowName = ((StaticContent) child).getFlowName();
  172. flowMap.put(flowName, child);
  173. }
  174. }
  175. /**
  176. * Add a flow or static content, mapped by its flow-name.
  177. * The flow-name is used to associate the flow with a region on a page,
  178. * based on the region-names given to the regions in the page-master
  179. * used to generate that page.
  180. */
  181. private void addFlow(Flow flow) throws ValidationException {
  182. String flowName = flow.getFlowName();
  183. if (hasFlowName(flowName)) {
  184. throw new ValidationException("duplicate flow-name \""
  185. + flowName
  186. + "\" found within fo:page-sequence", flow.getLocator());
  187. }
  188. if (!root.getLayoutMasterSet().regionNameExists(flowName)
  189. && !flowName.equals("xsl-before-float-separator")
  190. && !flowName.equals("xsl-footnote-separator")) {
  191. throw new ValidationException("flow-name \""
  192. + flowName
  193. + "\" could not be mapped to a region-name in the"
  194. + " layout-master-set", flow.getLocator());
  195. }
  196. }
  197. /**
  198. * Initialize the current page number for the start of the page sequence.
  199. */
  200. public void initPageNumber() {
  201. int pageNumberType = 0;
  202. if (initialPageNumber.getEnum() != 0) {
  203. // auto | auto-odd | auto-even.
  204. startingPageNumber = root.getEndingPageNumberOfPreviousSequence() + 1;
  205. pageNumberType = initialPageNumber.getEnum();
  206. if (pageNumberType == EN_AUTO_ODD) {
  207. if (startingPageNumber % 2 == 0) {
  208. startingPageNumber++;
  209. }
  210. } else if (pageNumberType == EN_AUTO_EVEN) {
  211. if (startingPageNumber % 2 == 1) {
  212. startingPageNumber++;
  213. }
  214. }
  215. } else { // <integer> for explicit page number
  216. int pageStart = initialPageNumber.getValue();
  217. startingPageNumber = (pageStart > 0) ? pageStart : 1; // spec rule
  218. }
  219. }
  220. // /**
  221. // * Returns true when there is more flow elements left to lay out.
  222. // */
  223. // private boolean flowsAreIncomplete() {
  224. // boolean isIncomplete = false;
  225. // for (Iterator e = flowMap.values().iterator(); e.hasNext(); ) {
  226. // Flow flow = (Flow)e.next();
  227. // if (flow instanceof StaticContent) {
  228. // continue;
  229. // }
  230. // Status status = flow.getStatus();
  231. // isIncomplete |= status.isIncomplete();
  232. // }
  233. // return isIncomplete;
  234. // }
  235. // /**
  236. // * Returns the flow that maps to the given region class for the current
  237. // * page master.
  238. // */
  239. // private Flow getCurrentFlow(String regionClass) {
  240. // Region region = getCurrentSimplePageMaster().getRegion(regionClass);
  241. // if (region != null) {
  242. // Flow flow = (Flow)flowMap.get(region.getRegionName());
  243. // return flow;
  244. // } else {
  245. // getLogger().error("flow is null. regionClass = '" + regionClass
  246. // + "' currentSPM = "
  247. // + getCurrentSimplePageMaster());
  248. // return null;
  249. // }
  250. // }
  251. // private boolean isFlowForMasterNameDone(String masterName) {
  252. // // parameter is master-name of PMR; we need to locate PM
  253. // // referenced by this, and determine whether flow(s) are OK
  254. // if (isForcing)
  255. // return false;
  256. // if (masterName != null) {
  257. // SimplePageMaster spm =
  258. // root.getLayoutMasterSet().getSimplePageMaster(masterName);
  259. // Region region = spm.getRegion(FO_REGION_BODY);
  260. // Flow flow = (Flow)flowMap.get(region.getRegionName());
  261. // /*if ((null == flow) || flow.getStatus().isIncomplete())
  262. // return false;
  263. // else
  264. // return true;*/
  265. // }
  266. // return false;
  267. // }
  268. /**
  269. * Get the starting page number for this page sequence.
  270. *
  271. * @return the starting page number
  272. */
  273. public int getStartingPageNumber() {
  274. return startingPageNumber;
  275. }
  276. // private void forcePage(AreaTree areaTree, int firstAvailPageNumber) {
  277. // boolean makePage = false;
  278. // if (this.forcePageCount == ForcePageCount.AUTO) {
  279. // PageSequence nextSequence =
  280. // this.root.getSucceedingPageSequence(this);
  281. // if (nextSequence != null) {
  282. // if (nextSequence.getIpnValue().equals("auto")) {
  283. // // do nothing special
  284. // }
  285. // else if (nextSequence.getIpnValue().equals("auto-odd")) {
  286. // if (firstAvailPageNumber % 2 == 0) {
  287. // makePage = true;
  288. // }
  289. // } else if (nextSequence.getIpnValue().equals("auto-even")) {
  290. // if (firstAvailPageNumber % 2 != 0) {
  291. // makePage = true;
  292. // }
  293. // } else {
  294. // int nextSequenceStartPageNumber =
  295. // nextSequence.getCurrentPageNumber();
  296. // if ((nextSequenceStartPageNumber % 2 == 0)
  297. // && (firstAvailPageNumber % 2 == 0)) {
  298. // makePage = true;
  299. // } else if ((nextSequenceStartPageNumber % 2 != 0)
  300. // && (firstAvailPageNumber % 2 != 0)) {
  301. // makePage = true;
  302. // }
  303. // }
  304. // }
  305. // } else if ((this.forcePageCount == ForcePageCount.EVEN)
  306. // && (this.pageCount % 2 != 0)) {
  307. // makePage = true;
  308. // } else if ((this.forcePageCount == ForcePageCount.ODD)
  309. // && (this.pageCount % 2 == 0)) {
  310. // makePage = true;
  311. // } else if ((this.forcePageCount == ForcePageCount.END_ON_EVEN)
  312. // && (firstAvailPageNumber % 2 == 0)) {
  313. // makePage = true;
  314. // } else if ((this.forcePageCount == ForcePageCount.END_ON_ODD)
  315. // && (firstAvailPageNumber % 2 != 0)) {
  316. // makePage = true;
  317. // } else if (this.forcePageCount == ForcePageCount.NO_FORCE) {
  318. // // do nothing
  319. // }
  320. // if (makePage) {
  321. // try {
  322. // this.isForcing = true;
  323. // this.currentPageNumber++;
  324. // firstAvailPageNumber = this.currentPageNumber;
  325. // currentPage = makePage(areaTree, firstAvailPageNumber, false,
  326. // true);
  327. // String formattedPageNumber =
  328. // pageNumberGenerator.makeFormattedPageNumber(this.currentPageNumber);
  329. // currentPage.setFormattedNumber(formattedPageNumber);
  330. // currentPage.setPageSequence(this);
  331. // formatStaticContent(areaTree);
  332. // log.debug("[forced-" + firstAvailPageNumber + "]");
  333. // areaTree.addPage(currentPage);
  334. // this.root.setRunningPageNumberCounter(this.currentPageNumber);
  335. // this.isForcing = false;
  336. // } catch (FOPException fopex) {
  337. // log.debug("'force-page-count' failure");
  338. // }
  339. // }
  340. // }
  341. /**
  342. * Get the static content FO node from the flow map.
  343. * This gets the static content flow for the given flow name.
  344. *
  345. * @param name the flow name to find
  346. * @return the static content FO node
  347. */
  348. public StaticContent getStaticContent(String name) {
  349. return (StaticContent) flowMap.get(name);
  350. }
  351. /** @return the "id" property. */
  352. public String getId() {
  353. return id;
  354. }
  355. /**
  356. * Accessor method for titleFO
  357. * @return titleFO for this object
  358. */
  359. public Title getTitleFO() {
  360. return titleFO;
  361. }
  362. /**
  363. * Public accessor for getting the MainFlow to which this PageSequence is
  364. * attached.
  365. * @return the MainFlow object to which this PageSequence is attached.
  366. */
  367. public Flow getMainFlow() {
  368. return mainFlow;
  369. }
  370. /**
  371. * Determine if this PageSequence already has a flow with the given flow-name
  372. * Used for validation of incoming fo:flow or fo:static-content objects
  373. * @param flowName The flow-name to search for
  374. * @return true if flow-name already defined within this page sequence,
  375. * false otherwise
  376. */
  377. public boolean hasFlowName(String flowName) {
  378. return flowMap.containsKey(flowName);
  379. }
  380. /** @return the flow map for this page-sequence */
  381. public Map getFlowMap() {
  382. return this.flowMap;
  383. }
  384. /**
  385. * Public accessor for determining the next page master to use within this page sequence.
  386. * @param page the page number of the page to be created
  387. * @param isFirstPage indicator whether this page is the first page of the
  388. * page sequence
  389. * @param isLastPage indicator whether this page is the last page of the
  390. * page sequence
  391. * @param isBlank indicator whether the page will be blank
  392. * @return the SimplePageMaster to use for this page
  393. * @throws FOPException if there's a problem determining the page master
  394. */
  395. public SimplePageMaster getNextSimplePageMaster(int page,
  396. boolean isFirstPage,
  397. boolean isLastPage,
  398. boolean isBlank) throws FOPException {
  399. if (pageSequenceMaster == null) {
  400. return simplePageMaster;
  401. }
  402. boolean isOddPage = ((page % 2) == 1);
  403. if (getLogger().isDebugEnabled()) {
  404. getLogger().debug("getNextSimplePageMaster(page=" + page
  405. + " isOdd=" + isOddPage
  406. + " isFirst=" + isFirstPage
  407. + " isLast=" + isLastPage
  408. + " isBlank=" + isBlank + ")");
  409. }
  410. return pageSequenceMaster.getNextSimplePageMaster(isOddPage,
  411. isFirstPage, isLastPage, isBlank);
  412. }
  413. /**
  414. * Used to set the "cursor position" for the page masters to the previous item.
  415. * @return true if there is a previous item, false if the current one was the first one.
  416. */
  417. public boolean goToPreviousSimplePageMaster() {
  418. if (pageSequenceMaster == null) {
  419. return true;
  420. } else {
  421. return pageSequenceMaster.goToPreviousSimplePageMaster();
  422. }
  423. }
  424. /** @return true if the page-sequence has a page-master with page-position="last" */
  425. public boolean hasPagePositionLast() {
  426. if (pageSequenceMaster == null) {
  427. return false;
  428. } else {
  429. return pageSequenceMaster.hasPagePositionLast();
  430. }
  431. }
  432. /**
  433. * Retrieves the string representation of a page number applicable
  434. * for this page sequence
  435. * @param pageNumber the page number
  436. * @return string representation of the page number
  437. */
  438. public String makeFormattedPageNumber(int pageNumber) {
  439. return pageNumberGenerator.makeFormattedPageNumber(pageNumber);
  440. }
  441. /**
  442. * Public accessor for the ancestor Root.
  443. * @return the ancestor Root
  444. */
  445. public Root getRoot() {
  446. return root;
  447. }
  448. /** @return the "master-reference" property. */
  449. public String getMasterReference() {
  450. return masterReference;
  451. }
  452. /** @see org.apache.fop.fo.FONode#getLocalName() */
  453. public String getLocalName() {
  454. return "page-sequence";
  455. }
  456. /** @see org.apache.fop.fo.FObj#getNameId() */
  457. public int getNameId() {
  458. return FO_PAGE_SEQUENCE;
  459. }
  460. /** @return the force-page-count value */
  461. public int getForcePageCount() {
  462. return forcePageCount;
  463. }
  464. /** @return the initial-page-number property value */
  465. public Numeric getInitialPageNumber() {
  466. return initialPageNumber;
  467. }
  468. /** @return the country property value */
  469. public String getCountry() {
  470. return this.country;
  471. }
  472. /** @return the language property value */
  473. public String getLanguage() {
  474. return this.language;
  475. }
  476. /**
  477. * Releases a page-sequence's children after the page-sequence has been fully processed.
  478. */
  479. public void releasePageSequence() {
  480. this.mainFlow = null;
  481. this.flowMap.clear();
  482. }
  483. }