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

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