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

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