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.

DataStream.java 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  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.afp;
  19. import java.awt.Color;
  20. import java.awt.Point;
  21. import java.io.IOException;
  22. import java.io.OutputStream;
  23. import java.io.UnsupportedEncodingException;
  24. import java.util.Iterator;
  25. import java.util.Map;
  26. import org.apache.commons.logging.Log;
  27. import org.apache.commons.logging.LogFactory;
  28. import org.apache.fop.afp.fonts.AFPFont;
  29. import org.apache.fop.afp.fonts.AFPFontAttributes;
  30. import org.apache.fop.afp.fonts.CharacterSet;
  31. import org.apache.fop.afp.modca.AbstractPageObject;
  32. import org.apache.fop.afp.modca.Document;
  33. import org.apache.fop.afp.modca.InterchangeSet;
  34. import org.apache.fop.afp.modca.Overlay;
  35. import org.apache.fop.afp.modca.PageGroup;
  36. import org.apache.fop.afp.modca.PageObject;
  37. import org.apache.fop.afp.modca.ResourceGroup;
  38. import org.apache.fop.afp.modca.TagLogicalElementBean;
  39. import org.apache.fop.afp.modca.triplets.FullyQualifiedNameTriplet;
  40. import org.apache.fop.afp.ptoca.PtocaBuilder;
  41. import org.apache.fop.afp.ptoca.PtocaProducer;
  42. import org.apache.fop.fonts.Font;
  43. import org.apache.fop.util.CharUtilities;
  44. /**
  45. * A data stream is a continuous ordered stream of data elements and objects
  46. * conforming to a given format. Application programs can generate data streams
  47. * destined for a presentation service, archive library, presentation device or
  48. * another application program. The strategic presentation data stream
  49. * architectures used is Mixed Object Document Content Architecture (MO:DCA).
  50. *
  51. * The MO:DCA architecture defines the data stream used by applications to
  52. * describe documents and object envelopes for interchange with other
  53. * applications and application services. Documents defined in the MO:DCA format
  54. * may be archived in a database, then later retrieved, viewed, annotated and
  55. * printed in local or distributed systems environments. Presentation fidelity
  56. * is accommodated by including resource objects in the documents that reference
  57. * them.
  58. */
  59. public class DataStream {
  60. /** Static logging instance */
  61. protected static final Log LOG = LogFactory.getLog("org.apache.xmlgraphics.afp");
  62. /** Boolean completion indicator */
  63. private boolean complete = false;
  64. /** The AFP document object */
  65. private Document document = null;
  66. /** The current page group object */
  67. private PageGroup currentPageGroup = null;
  68. /** The current page object */
  69. private PageObject currentPageObject = null;
  70. /** The current overlay object */
  71. private Overlay currentOverlay = null;
  72. /** The current page */
  73. private AbstractPageObject currentPage = null;
  74. /** Sequence number for TLE's.*/
  75. private int tleSequence = 0;
  76. /** The MO:DCA interchange set in use (default to MO:DCA-P IS/2 set) */
  77. private InterchangeSet interchangeSet
  78. = InterchangeSet.valueOf(InterchangeSet.MODCA_PRESENTATION_INTERCHANGE_SET_2);
  79. private final Factory factory;
  80. private OutputStream outputStream;
  81. /** the afp painting state */
  82. private final AFPPaintingState paintingState;
  83. /**
  84. * Default constructor for the AFPDocumentStream.
  85. *
  86. * @param factory the resource factory
  87. * @param paintingState the AFP painting state
  88. * @param outputStream the outputstream to write to
  89. */
  90. public DataStream(Factory factory, AFPPaintingState paintingState, OutputStream outputStream) {
  91. this.paintingState = paintingState;
  92. this.factory = factory;
  93. this.outputStream = outputStream;
  94. }
  95. /**
  96. * Returns the outputstream
  97. *
  98. * @return the outputstream
  99. */
  100. public OutputStream getOutputStream() {
  101. return this.outputStream;
  102. }
  103. /**
  104. * Returns the document object
  105. *
  106. * @return the document object
  107. */
  108. private Document getDocument() {
  109. return this.document;
  110. }
  111. /**
  112. * Returns the current page
  113. *
  114. * @return the current page
  115. */
  116. public AbstractPageObject getCurrentPage() {
  117. return this.currentPage;
  118. }
  119. /**
  120. * The document is started by invoking this method which creates an instance
  121. * of the AFP Document object.
  122. *
  123. * @param name
  124. * the name of this document.
  125. */
  126. public void setDocumentName(String name) {
  127. if (name != null) {
  128. getDocument().setFullyQualifiedName(
  129. FullyQualifiedNameTriplet.TYPE_BEGIN_DOCUMENT_REF,
  130. FullyQualifiedNameTriplet.FORMAT_CHARSTR, name);
  131. }
  132. }
  133. /**
  134. * Helper method to mark the end of the current document.
  135. *
  136. * @throws IOException thrown if an I/O exception of some sort has occurred
  137. */
  138. public void endDocument() throws IOException {
  139. if (complete) {
  140. String msg = "Invalid state - document already ended.";
  141. LOG.warn("endDocument():: " + msg);
  142. throw new IllegalStateException(msg);
  143. }
  144. if (currentPageObject != null) {
  145. // End the current page if necessary
  146. endPage();
  147. }
  148. if (currentPageGroup != null) {
  149. // End the current page group if necessary
  150. endPageGroup();
  151. }
  152. // Write out document
  153. if (document != null) {
  154. document.endDocument();
  155. document.writeToStream(this.outputStream);
  156. }
  157. this.outputStream.flush();
  158. this.complete = true;
  159. this.document = null;
  160. this.outputStream = null;
  161. }
  162. /**
  163. * Start a new page. When processing has finished on the current page, the
  164. * {@link #endPage()}method must be invoked to mark the page ending.
  165. *
  166. * @param pageWidth
  167. * the width of the page
  168. * @param pageHeight
  169. * the height of the page
  170. * @param pageRotation
  171. * the rotation of the page
  172. * @param pageWidthRes
  173. * the width resolution of the page
  174. * @param pageHeightRes
  175. * the height resolution of the page
  176. */
  177. public void startPage(int pageWidth, int pageHeight, int pageRotation,
  178. int pageWidthRes, int pageHeightRes) {
  179. currentPageObject = factory.createPage(pageWidth, pageHeight,
  180. pageRotation, pageWidthRes, pageHeightRes);
  181. currentPage = currentPageObject;
  182. currentOverlay = null;
  183. }
  184. /**
  185. * Start a new overlay. When processing has finished on the current overlay,
  186. * the {@link #endOverlay()}method must be invoked to mark the overlay
  187. * ending.
  188. *
  189. * @param x
  190. * the x position of the overlay on the page
  191. * @param y
  192. * the y position of the overlay on the page
  193. * @param width
  194. * the width of the overlay
  195. * @param height
  196. * the height of the overlay
  197. * @param widthRes
  198. * the width resolution of the overlay
  199. * @param heightRes
  200. * the height resolution of the overlay
  201. * @param overlayRotation
  202. * the rotation of the overlay
  203. */
  204. public void startOverlay(int x, int y, int width, int height, int widthRes,
  205. int heightRes, int overlayRotation) {
  206. this.currentOverlay = factory.createOverlay(
  207. width, height, widthRes, heightRes, overlayRotation);
  208. String overlayName = currentOverlay.getName();
  209. currentPageObject.createIncludePageOverlay(overlayName, x, y, 0);
  210. currentPage = currentOverlay;
  211. }
  212. /**
  213. * Helper method to mark the end of the current overlay.
  214. *
  215. * @throws IOException thrown if an I/O exception of some sort has occurred
  216. */
  217. public void endOverlay() throws IOException {
  218. if (currentOverlay != null) {
  219. currentOverlay.endPage();
  220. currentOverlay = null;
  221. currentPage = currentPageObject;
  222. }
  223. }
  224. /**
  225. * Helper method to save the current page.
  226. *
  227. * @return current page object that was saved
  228. */
  229. public PageObject savePage() {
  230. PageObject pageObject = currentPageObject;
  231. if (currentPageGroup != null) {
  232. currentPageGroup.addPage(currentPageObject);
  233. } else {
  234. document.addPage(currentPageObject);
  235. }
  236. currentPageObject = null;
  237. currentPage = null;
  238. return pageObject;
  239. }
  240. /**
  241. * Helper method to restore the current page.
  242. *
  243. * @param pageObject
  244. * page object
  245. */
  246. public void restorePage(PageObject pageObject) {
  247. currentPageObject = pageObject;
  248. currentPage = pageObject;
  249. }
  250. /**
  251. * Helper method to mark the end of the current page.
  252. *
  253. * @throws IOException thrown if an I/O exception of some sort has occurred
  254. */
  255. public void endPage() throws IOException {
  256. if (currentPageObject != null) {
  257. currentPageObject.endPage();
  258. if (currentPageGroup != null) {
  259. currentPageGroup.addPage(currentPageObject);
  260. currentPageGroup.writeToStream(this.outputStream);
  261. } else {
  262. document.addPage(currentPageObject);
  263. document.writeToStream(this.outputStream);
  264. }
  265. currentPageObject = null;
  266. currentPage = null;
  267. }
  268. }
  269. /**
  270. * Creates the given page fonts in the current page
  271. *
  272. * @param pageFonts
  273. * a collection of AFP font attributes
  274. */
  275. public void addFontsToCurrentPage(Map pageFonts) {
  276. Iterator iter = pageFonts.values().iterator();
  277. while (iter.hasNext()) {
  278. AFPFontAttributes afpFontAttributes = (AFPFontAttributes) iter
  279. .next();
  280. createFont(afpFontAttributes.getFontReference(), afpFontAttributes
  281. .getFont(), afpFontAttributes.getPointSize());
  282. }
  283. }
  284. /**
  285. * Helper method to create a map coded font object on the current page, this
  286. * method delegates the construction of the map coded font object to the
  287. * active environment group on the current page.
  288. *
  289. * @param fontReference
  290. * the font number used as the resource identifier
  291. * @param font
  292. * the font
  293. * @param size
  294. * the point size of the font
  295. */
  296. public void createFont(int fontReference, AFPFont font, int size) {
  297. currentPage.createFont(fontReference, font, size);
  298. }
  299. /**
  300. * Returns a point on the current page
  301. *
  302. * @param x the X-coordinate
  303. * @param y the Y-coordinate
  304. * @return a point on the current page
  305. */
  306. private Point getPoint(int x, int y) {
  307. return paintingState.getPoint(x, y);
  308. }
  309. /**
  310. * Helper method to create text on the current page, this method delegates
  311. * to the current presentation text object in order to construct the text.
  312. *
  313. * @param textDataInfo the afp text data
  314. * @param letterSpacing letter spacing to draw text with
  315. * @param wordSpacing word Spacing to draw text with
  316. * @param font is the font to draw text with
  317. * @param charSet is the AFP Character Set to use with the text
  318. * @throws UnsupportedEncodingException thrown if character encoding is not supported
  319. */
  320. public void createText(final AFPTextDataInfo textDataInfo, final int letterSpacing,
  321. final int wordSpacing, final Font font, final CharacterSet charSet)
  322. throws UnsupportedEncodingException {
  323. int rotation = paintingState.getRotation();
  324. if (rotation != 0) {
  325. textDataInfo.setRotation(rotation);
  326. Point p = getPoint(textDataInfo.getX(), textDataInfo.getY());
  327. textDataInfo.setX(p.x);
  328. textDataInfo.setY(p.y);
  329. }
  330. // use PtocaProducer to create PTX records
  331. PtocaProducer producer = new PtocaProducer() {
  332. public void produce(PtocaBuilder builder) throws IOException {
  333. builder.setTextOrientation(textDataInfo.getRotation());
  334. builder.absoluteMoveBaseline(textDataInfo.getY());
  335. builder.absoluteMoveInline(textDataInfo.getX());
  336. builder.setExtendedTextColor(textDataInfo.getColor());
  337. builder.setCodedFont((byte)textDataInfo.getFontReference());
  338. int l = textDataInfo.getString().length();
  339. StringBuffer sb = new StringBuffer();
  340. int interCharacterAdjustment = 0;
  341. AFPUnitConverter unitConv = paintingState.getUnitConverter();
  342. if (letterSpacing != 0) {
  343. interCharacterAdjustment = Math.round(unitConv.mpt2units(letterSpacing));
  344. }
  345. builder.setInterCharacterAdjustment(interCharacterAdjustment);
  346. int spaceWidth = font.getCharWidth(CharUtilities.SPACE);
  347. int spacing = spaceWidth + letterSpacing;
  348. int fixedSpaceCharacterIncrement = Math.round(unitConv.mpt2units(spacing));
  349. int varSpaceCharacterIncrement = fixedSpaceCharacterIncrement;
  350. if (wordSpacing != 0) {
  351. varSpaceCharacterIncrement = Math.round(unitConv.mpt2units(
  352. spaceWidth + wordSpacing + letterSpacing));
  353. }
  354. builder.setVariableSpaceCharacterIncrement(varSpaceCharacterIncrement);
  355. boolean fixedSpaceMode = false;
  356. for (int i = 0; i < l; i++) {
  357. char orgChar = textDataInfo.getString().charAt(i);
  358. float glyphAdjust = 0;
  359. if (CharUtilities.isFixedWidthSpace(orgChar)) {
  360. flushText(builder, sb, charSet);
  361. builder.setVariableSpaceCharacterIncrement(
  362. fixedSpaceCharacterIncrement);
  363. fixedSpaceMode = true;
  364. sb.append(CharUtilities.SPACE);
  365. int charWidth = font.getCharWidth(orgChar);
  366. glyphAdjust += (charWidth - spaceWidth);
  367. } else {
  368. if (fixedSpaceMode) {
  369. flushText(builder, sb, charSet);
  370. builder.setVariableSpaceCharacterIncrement(
  371. varSpaceCharacterIncrement);
  372. fixedSpaceMode = false;
  373. }
  374. char ch;
  375. if (orgChar == CharUtilities.NBSPACE) {
  376. ch = ' '; //converted to normal space to allow word spacing
  377. } else {
  378. ch = orgChar;
  379. }
  380. sb.append(ch);
  381. }
  382. if (glyphAdjust != 0) {
  383. flushText(builder, sb, charSet);
  384. int increment = Math.round(unitConv.mpt2units(glyphAdjust));
  385. builder.relativeMoveInline(increment);
  386. }
  387. }
  388. flushText(builder, sb, charSet);
  389. }
  390. private void flushText(PtocaBuilder builder, StringBuffer sb,
  391. final CharacterSet charSet) throws IOException {
  392. if (sb.length() > 0) {
  393. builder.addTransparentData(charSet.encodeChars(sb));
  394. sb.setLength(0);
  395. }
  396. }
  397. };
  398. currentPage.createText(producer);
  399. }
  400. /**
  401. * Method to create a line on the current page.
  402. *
  403. * @param lineDataInfo the line data information.
  404. */
  405. public void createLine(AFPLineDataInfo lineDataInfo) {
  406. currentPage.createLine(lineDataInfo);
  407. }
  408. /**
  409. * This method will create shading on the page using the specified
  410. * coordinates (the shading contrast is controlled via the red, green, blue
  411. * parameters, by converting this to grey scale).
  412. *
  413. * @param x
  414. * the x coordinate of the shading
  415. * @param y
  416. * the y coordinate of the shading
  417. * @param w
  418. * the width of the shaded area
  419. * @param h
  420. * the height of the shaded area
  421. * @param col
  422. * the shading color
  423. */
  424. public void createShading(int x, int y, int w, int h, Color col) {
  425. currentPageObject.createShading(x, y, w, h, col.getRed(), col.getGreen(), col.getBlue());
  426. }
  427. /**
  428. * Helper method which allows creation of the MPO object, via the AEG. And
  429. * the IPO via the Page. (See actual object for descriptions.)
  430. *
  431. * @param name
  432. * the name of the static overlay
  433. * @param x x-coordinate
  434. * @param y y-coordinate
  435. */
  436. public void createIncludePageOverlay(String name, int x, int y) {
  437. currentPageObject.createIncludePageOverlay(name, x, y, paintingState.getRotation());
  438. currentPageObject.getActiveEnvironmentGroup().createOverlay(name);
  439. }
  440. /**
  441. * Helper method which allows creation of the IMM object.
  442. *
  443. * @param name
  444. * the name of the medium map
  445. */
  446. public void createInvokeMediumMap(String name) {
  447. currentPageGroup.createInvokeMediumMap(name);
  448. }
  449. /**
  450. * Creates an IncludePageSegment on the current page.
  451. *
  452. * @param name
  453. * the name of the include page segment
  454. * @param x
  455. * the x coordinate for the overlay
  456. * @param y
  457. * the y coordinate for the overlay
  458. * @param width
  459. * the width of the image
  460. * @param height
  461. * the height of the image
  462. */
  463. public void createIncludePageSegment(String name, int x, int y, int width, int height) {
  464. int xOrigin;
  465. int yOrigin;
  466. int orientation = paintingState.getRotation();
  467. switch (orientation) {
  468. case 90:
  469. xOrigin = x - height;
  470. yOrigin = y;
  471. break;
  472. case 180:
  473. xOrigin = x - width;
  474. yOrigin = y - height;
  475. break;
  476. case 270:
  477. xOrigin = x;
  478. yOrigin = y - width;
  479. break;
  480. default:
  481. xOrigin = x;
  482. yOrigin = y;
  483. break;
  484. }
  485. boolean createHardPageSegments = true;
  486. currentPage.createIncludePageSegment(name, xOrigin, yOrigin, createHardPageSegments);
  487. }
  488. /**
  489. * Creates a TagLogicalElement on the current page.
  490. *
  491. * @param attributes
  492. * the array of key value pairs.
  493. */
  494. public void createPageTagLogicalElement(TagLogicalElementBean[] attributes) {
  495. for (int i = 0; i < attributes.length; i++) {
  496. String name = attributes[i].getKey();
  497. String value = attributes[i].getValue();
  498. currentPage.createTagLogicalElement(name, value, tleSequence++);
  499. }
  500. }
  501. /**
  502. * Creates a TagLogicalElement on the current page group.
  503. *
  504. * @param attributes
  505. * the array of key value pairs.
  506. */
  507. public void createPageGroupTagLogicalElement(TagLogicalElementBean[] attributes) {
  508. for (int i = 0; i < attributes.length; i++) {
  509. String name = attributes[i].getKey();
  510. String value = attributes[i].getValue();
  511. currentPageGroup.createTagLogicalElement(name, value);
  512. }
  513. }
  514. /**
  515. * Creates a TagLogicalElement on the current page or page group
  516. *
  517. * @param name
  518. * The tag name
  519. * @param value
  520. * The tag value
  521. */
  522. public void createTagLogicalElement(String name, String value) {
  523. if (currentPage != null) {
  524. currentPage.createTagLogicalElement(name, value, tleSequence++);
  525. } else {
  526. currentPageGroup.createTagLogicalElement(name, value);
  527. }
  528. }
  529. /**
  530. * Creates a NoOperation item
  531. *
  532. * @param content
  533. * byte data
  534. */
  535. public void createNoOperation(String content) {
  536. if (currentPage != null) {
  537. currentPage.createNoOperation(content);
  538. } else if (currentPageGroup != null) {
  539. currentPageGroup.createNoOperation(content);
  540. } else {
  541. document.createNoOperation(content);
  542. }
  543. }
  544. /**
  545. * Returns the current page group
  546. *
  547. * @return the current page group
  548. */
  549. public PageGroup getCurrentPageGroup() {
  550. return this.currentPageGroup;
  551. }
  552. /**
  553. * Start a new document.
  554. *
  555. * @throws IOException thrown if an I/O exception of some sort has occurred
  556. */
  557. public void startDocument() throws IOException {
  558. this.document = factory.createDocument();
  559. document.writeToStream(this.outputStream);
  560. }
  561. /**
  562. * Start a new page group. When processing has finished on the current page
  563. * group the {@link #endPageGroup()}method must be invoked to mark the page
  564. * group ending.
  565. *
  566. * @throws IOException thrown if an I/O exception of some sort has occurred
  567. */
  568. public void startPageGroup() throws IOException {
  569. endPageGroup();
  570. this.currentPageGroup = factory.createPageGroup(tleSequence);
  571. }
  572. /**
  573. * Helper method to mark the end of the page group.
  574. *
  575. * @throws IOException thrown if an I/O exception of some sort has occurred
  576. */
  577. public void endPageGroup() throws IOException {
  578. if (currentPageGroup != null) {
  579. currentPageGroup.endPageGroup();
  580. tleSequence = currentPageGroup.getTleSequence();
  581. document.addPageGroup(currentPageGroup);
  582. currentPageGroup = null;
  583. }
  584. document.writeToStream(outputStream); //Flush objects
  585. }
  586. /**
  587. * Sets the MO:DCA interchange set to use
  588. *
  589. * @param interchangeSet the MO:DCA interchange set
  590. */
  591. public void setInterchangeSet(InterchangeSet interchangeSet) {
  592. this.interchangeSet = interchangeSet;
  593. }
  594. /**
  595. * Returns the MO:DCA interchange set in use
  596. *
  597. * @return the MO:DCA interchange set in use
  598. */
  599. public InterchangeSet getInterchangeSet() {
  600. return this.interchangeSet;
  601. }
  602. /**
  603. * Returns the resource group for a given resource info
  604. *
  605. * @param level a resource level
  606. * @return a resource group for the given resource info
  607. */
  608. public ResourceGroup getResourceGroup(AFPResourceLevel level) {
  609. ResourceGroup resourceGroup = null;
  610. if (level.isDocument()) {
  611. resourceGroup = document.getResourceGroup();
  612. } else if (level.isPageGroup()) {
  613. resourceGroup = currentPageGroup.getResourceGroup();
  614. } else if (level.isPage()) {
  615. resourceGroup = currentPageObject.getResourceGroup();
  616. }
  617. return resourceGroup;
  618. }
  619. }