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.

AFPGraphics2D.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  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.AlphaComposite;
  20. import java.awt.BasicStroke;
  21. import java.awt.Color;
  22. import java.awt.Dimension;
  23. import java.awt.Font;
  24. import java.awt.FontMetrics;
  25. import java.awt.Graphics;
  26. import java.awt.GraphicsConfiguration;
  27. import java.awt.Image;
  28. import java.awt.Rectangle;
  29. import java.awt.Shape;
  30. import java.awt.Stroke;
  31. import java.awt.geom.AffineTransform;
  32. import java.awt.geom.Ellipse2D;
  33. import java.awt.geom.Line2D;
  34. import java.awt.geom.PathIterator;
  35. import java.awt.geom.Rectangle2D;
  36. import java.awt.image.BufferedImage;
  37. import java.awt.image.ImageObserver;
  38. import java.awt.image.RenderedImage;
  39. import java.awt.image.renderable.RenderableImage;
  40. import java.io.IOException;
  41. import org.apache.commons.io.output.ByteArrayOutputStream;
  42. import org.apache.commons.logging.Log;
  43. import org.apache.commons.logging.LogFactory;
  44. import org.apache.fop.afp.goca.GraphicsSetLineType;
  45. import org.apache.fop.afp.modca.GraphicsObject;
  46. import org.apache.fop.fonts.FontInfo;
  47. import org.apache.xmlgraphics.image.loader.ImageInfo;
  48. import org.apache.xmlgraphics.image.loader.ImageSize;
  49. import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
  50. import org.apache.xmlgraphics.java2d.AbstractGraphics2D;
  51. import org.apache.xmlgraphics.java2d.GraphicContext;
  52. import org.apache.xmlgraphics.java2d.StrokingTextHandler;
  53. import org.apache.xmlgraphics.java2d.TextHandler;
  54. import org.apache.xmlgraphics.ps.ImageEncodingHelper;
  55. import org.apache.xmlgraphics.util.MimeConstants;
  56. /**
  57. * This is a concrete implementation of <tt>AbstractGraphics2D</tt> (and
  58. * therefore of <tt>Graphics2D</tt>) which is able to generate GOCA byte
  59. * codes.
  60. *
  61. * @see org.apache.xmlgraphics.java2d.AbstractGraphics2D
  62. */
  63. public class AFPGraphics2D extends AbstractGraphics2D {
  64. private static final Log log = LogFactory.getLog(AFPGraphics2D.class);
  65. private static final int X = 0;
  66. private static final int Y = 1;
  67. private static final int X1 = 0;
  68. private static final int Y1 = 1;
  69. private static final int X2 = 2;
  70. private static final int Y2 = 3;
  71. /** graphics object */
  72. private GraphicsObject graphicsObj = null;
  73. /** Fallback text handler */
  74. protected TextHandler fallbackTextHandler = new StrokingTextHandler(this);
  75. /** Custom text handler */
  76. protected TextHandler customTextHandler = null;
  77. /** AFP resource manager */
  78. private AFPResourceManager resourceManager = null;
  79. /** AFP resource info */
  80. private AFPResourceInfo resourceInfo = null;
  81. /** Current AFP state */
  82. private AFPPaintingState state = null;
  83. /** The AFP FontInfo */
  84. private FontInfo fontInfo;
  85. /**
  86. * Main constructor
  87. *
  88. * @param textAsShapes
  89. * if true, all text is turned into shapes in the convertion. No
  90. * text is output.
  91. *
  92. */
  93. public AFPGraphics2D(boolean textAsShapes) {
  94. super(textAsShapes);
  95. }
  96. /**
  97. * Copy Constructor
  98. *
  99. * @param g2d
  100. * a AFPGraphics2D whose properties should be copied
  101. */
  102. public AFPGraphics2D(AFPGraphics2D g2d) {
  103. super(g2d);
  104. this.graphicsObj = g2d.graphicsObj;
  105. this.fallbackTextHandler = g2d.fallbackTextHandler;
  106. this.customTextHandler = g2d.customTextHandler;
  107. this.resourceManager = g2d.resourceManager;
  108. this.resourceInfo = g2d.resourceInfo;
  109. this.state = g2d.state;
  110. }
  111. /**
  112. * Sets the AFP resource manager
  113. *
  114. * @param resourceManager the AFP resource manager
  115. */
  116. public void setResourceManager(AFPResourceManager resourceManager) {
  117. this.resourceManager = resourceManager;
  118. }
  119. /**
  120. * Sets the AFP resource info
  121. *
  122. * @param resourceInfo the AFP resource info
  123. */
  124. public void setResourceInfo(AFPResourceInfo resourceInfo) {
  125. this.resourceInfo = resourceInfo;
  126. }
  127. /**
  128. * Sets the GraphicContext
  129. *
  130. * @param gc
  131. * GraphicContext to use
  132. */
  133. public void setGraphicContext(GraphicContext gc) {
  134. this.gc = gc;
  135. }
  136. /**
  137. * Apply the stroke to the AFP graphics object.
  138. * This takes the java stroke and outputs the appropriate settings
  139. * to the AFP graphics object so that the stroke attributes are handled.
  140. *
  141. * @param stroke the java stroke
  142. */
  143. protected void applyStroke(Stroke stroke) {
  144. if (stroke instanceof BasicStroke) {
  145. BasicStroke basicStroke = (BasicStroke) stroke;
  146. // set line width
  147. float lineWidth = basicStroke.getLineWidth();
  148. getGraphicsObject().setLineWidth(Math.round(lineWidth * 2));
  149. // set line type/style (note: this is an approximation at best!)
  150. float[] dashArray = basicStroke.getDashArray();
  151. if (state.setDashArray(dashArray)) {
  152. byte type = GraphicsSetLineType.DEFAULT; // normally SOLID
  153. if (dashArray != null) {
  154. type = GraphicsSetLineType.DOTTED; // default to plain DOTTED if dashed line
  155. // float offset = basicStroke.getDashPhase();
  156. if (dashArray.length == 2) {
  157. if (dashArray[0] < dashArray[1]) {
  158. type = GraphicsSetLineType.SHORT_DASHED;
  159. } else if (dashArray[0] > dashArray[1]) {
  160. type = GraphicsSetLineType.LONG_DASHED;
  161. }
  162. } else if (dashArray.length == 4) {
  163. if (dashArray[0] > dashArray[1]
  164. && dashArray[2] < dashArray[3]) {
  165. type = GraphicsSetLineType.DASH_DOT;
  166. } else if (dashArray[0] < dashArray[1]
  167. && dashArray[2] < dashArray[3]) {
  168. type = GraphicsSetLineType.DOUBLE_DOTTED;
  169. }
  170. } else if (dashArray.length == 6) {
  171. if (dashArray[0] > dashArray[1]
  172. && dashArray[2] < dashArray[3]
  173. && dashArray[4] < dashArray[5]) {
  174. type = GraphicsSetLineType.DASH_DOUBLE_DOTTED;
  175. }
  176. }
  177. }
  178. getGraphicsObject().setLineType(type);
  179. }
  180. } else {
  181. log.warn("Unsupported Stroke: " + stroke.getClass().getName());
  182. }
  183. }
  184. /**
  185. * Handle the Batik drawing event
  186. *
  187. * @param shape
  188. * the shape to draw
  189. * @param fill
  190. * true if the shape is to be drawn filled
  191. */
  192. private void doDrawing(Shape shape, boolean fill) {
  193. if (!fill) {
  194. graphicsObj.newSegment();
  195. }
  196. Color color = getColor();
  197. if (state.setColor(color)) {
  198. graphicsObj.setColor(color);
  199. }
  200. Stroke stroke = getStroke();
  201. applyStroke(stroke);
  202. if (fill) {
  203. graphicsObj.beginArea();
  204. }
  205. AffineTransform trans = super.getTransform();
  206. PathIterator iter = shape.getPathIterator(trans);
  207. double[] dstPts = new double[6];
  208. int[] coords = null;
  209. if (shape instanceof Line2D) {
  210. iter.currentSegment(dstPts);
  211. coords = new int[4];
  212. coords[X1] = (int) Math.round(dstPts[X]);
  213. coords[Y1] = (int) Math.round(dstPts[Y]);
  214. iter.next();
  215. iter.currentSegment(dstPts);
  216. coords[X2] = (int) Math.round(dstPts[X]);
  217. coords[Y2] = (int) Math.round(dstPts[Y]);
  218. graphicsObj.addLine(coords);
  219. } else if (shape instanceof Rectangle2D) {
  220. iter.currentSegment(dstPts);
  221. coords = new int[4];
  222. coords[X2] = (int) Math.round(dstPts[X]);
  223. coords[Y2] = (int) Math.round(dstPts[Y]);
  224. iter.next();
  225. iter.next();
  226. iter.currentSegment(dstPts);
  227. coords[X1] = (int) Math.round(dstPts[X]);
  228. coords[Y1] = (int) Math.round(dstPts[Y]);
  229. graphicsObj.addBox(coords);
  230. } else if (shape instanceof Ellipse2D) {
  231. Ellipse2D elip = (Ellipse2D) shape;
  232. double scale = trans.getScaleX();
  233. double radiusWidth = elip.getWidth() / 2;
  234. double radiusHeight = elip.getHeight() / 2;
  235. graphicsObj.setArcParams(
  236. (int)Math.round(radiusWidth * scale),
  237. (int)Math.round(radiusHeight * scale),
  238. 0,
  239. 0
  240. );
  241. double[] srcPts = new double[] {elip.getCenterX(), elip.getCenterY()};
  242. trans.transform(srcPts, 0, dstPts, 0, 1);
  243. final int mh = 1;
  244. final int mhr = 0;
  245. graphicsObj.addFullArc(
  246. (int)Math.round(dstPts[X]),
  247. (int)Math.round(dstPts[Y]),
  248. mh,
  249. mhr
  250. );
  251. } else {
  252. // graphics segment opening coordinates (x,y)
  253. // current position coordinates (x,y)
  254. for (int[] openingCoords = new int[2], currCoords = new int[2];
  255. !iter.isDone(); iter.next()) {
  256. // round the coordinate values and combine with current position
  257. // coordinates
  258. int type = iter.currentSegment(dstPts);
  259. if (type == PathIterator.SEG_MOVETO) {
  260. openingCoords[X] = currCoords[X] = (int)Math.round(dstPts[X]);
  261. openingCoords[Y] = currCoords[Y] = (int)Math.round(dstPts[Y]);
  262. } else {
  263. int numCoords;
  264. if (type == PathIterator.SEG_LINETO) {
  265. numCoords = 2;
  266. } else if (type == PathIterator.SEG_QUADTO) {
  267. numCoords = 4;
  268. } else if (type == PathIterator.SEG_CUBICTO) {
  269. numCoords = 6;
  270. } else {
  271. // close of the graphics segment
  272. if (type == PathIterator.SEG_CLOSE) {
  273. coords = new int[] {
  274. coords[coords.length - 2], //prev X
  275. coords[coords.length - 1], //prev Y
  276. openingCoords[X],
  277. openingCoords[Y]
  278. };
  279. graphicsObj.addLine(coords);
  280. } else {
  281. log.debug("Unrecognised path iterator type: "
  282. + type);
  283. }
  284. continue;
  285. }
  286. // combine current position coordinates with new graphics
  287. // segment coordinates
  288. coords = new int[numCoords + 2];
  289. coords[X] = currCoords[X];
  290. coords[Y] = currCoords[Y];
  291. for (int i = 0; i < numCoords; i++) {
  292. coords[i + 2] = (int) Math.round(dstPts[i]);
  293. }
  294. if (type == PathIterator.SEG_LINETO) {
  295. graphicsObj.addLine(coords);
  296. } else if (type == PathIterator.SEG_QUADTO
  297. || type == PathIterator.SEG_CUBICTO) {
  298. graphicsObj.addFillet(coords);
  299. }
  300. // update current position coordinates
  301. currCoords[X] = coords[coords.length - 2];
  302. currCoords[Y] = coords[coords.length - 1];
  303. }
  304. }
  305. }
  306. if (fill) {
  307. graphicsObj.endArea();
  308. }
  309. }
  310. /** {@inheritDoc} */
  311. public void draw(Shape shape) {
  312. // log.debug("draw() shape=" + shape);
  313. doDrawing(shape, false);
  314. }
  315. /** {@inheritDoc} */
  316. public void fill(Shape shape) {
  317. // log.debug("fill() shape=" + shape);
  318. doDrawing(shape, true);
  319. }
  320. /**
  321. * Central handler for IOExceptions for this class.
  322. *
  323. * @param ioe
  324. * IOException to handle
  325. */
  326. public void handleIOException(IOException ioe) {
  327. // TODO Surely, there's a better way to do this.
  328. log.error(ioe.getMessage());
  329. ioe.printStackTrace();
  330. }
  331. /** {@inheritDoc} */
  332. public void drawString(String str, float x, float y) {
  333. try {
  334. if (customTextHandler != null && !textAsShapes) {
  335. customTextHandler.drawString(str, x, y);
  336. } else {
  337. fallbackTextHandler.drawString(str, x, y);
  338. }
  339. } catch (IOException ioe) {
  340. handleIOException(ioe);
  341. }
  342. }
  343. /** {@inheritDoc} */
  344. public GraphicsConfiguration getDeviceConfiguration() {
  345. return new AFPGraphicsConfiguration();
  346. }
  347. /** {@inheritDoc} */
  348. public void copyArea(int x, int y, int width, int height, int dx, int dy) {
  349. log.debug("copyArea() NYI: ");
  350. }
  351. /** {@inheritDoc} */
  352. public Graphics create() {
  353. return new AFPGraphics2D(this);
  354. }
  355. /** {@inheritDoc} */
  356. public void dispose() {
  357. this.graphicsObj = null;
  358. }
  359. /** {@inheritDoc} */
  360. public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
  361. return drawImage(img, x, y, img.getWidth(observer), img.getHeight(observer), observer);
  362. }
  363. private BufferedImage buildBufferedImage(Dimension size) {
  364. return new BufferedImage(size.width, size.height,
  365. BufferedImage.TYPE_INT_ARGB);
  366. }
  367. private AFPImageObjectInfo getImageObjectInfo(
  368. RenderedImage img, int x, int y, int width, int height) throws IOException {
  369. ImageInfo imageInfo = new ImageInfo(null, "image/unknown");
  370. ImageSize size = new ImageSize(img.getWidth(), img.getHeight(), 72);
  371. imageInfo.setSize(size);
  372. ImageRendered imageRendered = new ImageRendered(imageInfo, img, null);
  373. RenderedImage renderedImage = imageRendered.getRenderedImage();
  374. // create image object info
  375. AFPImageObjectInfo imageObjectInfo = new AFPImageObjectInfo();
  376. imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS45);
  377. imageObjectInfo.setBitsPerPixel(state.getBitsPerPixel());
  378. imageObjectInfo.setResourceInfo(resourceInfo);
  379. int dataHeight = renderedImage.getHeight();
  380. imageObjectInfo.setDataHeight(dataHeight);
  381. int dataWidth = renderedImage.getWidth();
  382. imageObjectInfo.setDataWidth(dataWidth);
  383. boolean colorImages = state.isColorImages();
  384. imageObjectInfo.setColor(colorImages);
  385. ByteArrayOutputStream boas = new ByteArrayOutputStream();
  386. ImageEncodingHelper.encodeRenderedImageAsRGB(renderedImage, boas);
  387. byte[] imageData = boas.toByteArray();
  388. // convert to grayscale
  389. if (!colorImages) {
  390. boas.reset();
  391. int bitsPerPixel = state.getBitsPerPixel();
  392. imageObjectInfo.setBitsPerPixel(bitsPerPixel);
  393. ImageEncodingHelper.encodeRGBAsGrayScale(
  394. imageData, dataWidth, dataHeight, bitsPerPixel, boas);
  395. imageData = boas.toByteArray();
  396. }
  397. imageObjectInfo.setData(imageData);
  398. if (imageInfo != null) {
  399. imageObjectInfo.setUri(imageInfo.getOriginalURI());
  400. }
  401. // create object area info
  402. AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo();
  403. AffineTransform at = gc.getTransform();
  404. float[] srcPts = new float[] {x, y};
  405. float[] dstPts = new float[srcPts.length];
  406. at.transform(srcPts, 0, dstPts, 0, 1);
  407. objectAreaInfo.setX(Math.round(dstPts[X]));
  408. objectAreaInfo.setY(Math.round(dstPts[Y]));
  409. AFPUnitConverter unitConv = state.getUnitConverter();
  410. int w = Math.round(unitConv.pt2units(width));
  411. objectAreaInfo.setWidth(w);
  412. int h = Math.round(unitConv.pt2units(height));
  413. objectAreaInfo.setHeight(h);
  414. int resolution = state.getResolution();
  415. objectAreaInfo.setWidthRes(resolution);
  416. objectAreaInfo.setHeightRes(resolution);
  417. imageObjectInfo.setObjectAreaInfo(objectAreaInfo);
  418. return imageObjectInfo;
  419. }
  420. /** {@inheritDoc} */
  421. public boolean drawImage(Image img, int x, int y, int width, int height,
  422. ImageObserver observer) {
  423. // draw with AWT Graphics2D
  424. Dimension size = new Dimension(width, height);
  425. BufferedImage bufferedImage = buildBufferedImage(size);
  426. java.awt.Graphics2D g2d = bufferedImage.createGraphics();
  427. g2d.setComposite(AlphaComposite.SrcOver);
  428. Color color = new Color(1, 1, 1, 0);
  429. g2d.setBackground(color);
  430. g2d.setPaint(color);
  431. g2d.fillRect(0, 0, width, height);
  432. int imageWidth = bufferedImage.getWidth();
  433. int imageHeight = bufferedImage.getHeight();
  434. Rectangle clipRect = new Rectangle(0, 0, imageWidth, imageHeight);
  435. g2d.clip(clipRect);
  436. g2d.setComposite(gc.getComposite());
  437. boolean drawn = g2d.drawImage(img, 0, 0, imageWidth, imageHeight, observer);
  438. g2d.dispose(); //drawn so dispose immediately to free system resource
  439. if (drawn) {
  440. try {
  441. // get image object info
  442. AFPImageObjectInfo imageObjectInfo = getImageObjectInfo(bufferedImage, x, y, width, height);
  443. // create image resource
  444. resourceManager.createObject(imageObjectInfo);
  445. return true;
  446. } catch (IOException ioe) {
  447. handleIOException(ioe);
  448. }
  449. }
  450. return false;
  451. }
  452. /** {@inheritDoc} */
  453. public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
  454. log.debug("drawRenderableImage() NYI: img=" + img + ", xform=" + xform);
  455. }
  456. /** {@inheritDoc} */
  457. public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
  458. log.debug("drawRenderedImage() NYI: img=" + img + ", xform=" + xform);
  459. }
  460. /** {@inheritDoc} */
  461. public FontMetrics getFontMetrics(Font f) {
  462. log.debug("getFontMetrics() NYI: f=" + f);
  463. return null;
  464. }
  465. /** {@inheritDoc} */
  466. public void setXORMode(Color col) {
  467. log.debug("setXORMode() NYI: col=" + col);
  468. }
  469. /**
  470. * Sets a custom TextHandler implementation that is responsible for painting
  471. * text. The default TextHandler paints all text as shapes. A custom
  472. * implementation can implement text painting using text painting operators.
  473. *
  474. * @param handler
  475. * the custom TextHandler implementation
  476. */
  477. public void setCustomTextHandler(TextHandler handler) {
  478. this.customTextHandler = handler;
  479. }
  480. /**
  481. * Returns the GOCA graphics object
  482. *
  483. * @return the GOCA graphics object
  484. */
  485. public GraphicsObject getGraphicsObject() {
  486. return this.graphicsObj;
  487. }
  488. /**
  489. * Sets the GOCA graphics object
  490. *
  491. * @param obj the GOCA graphics object
  492. */
  493. public void setGraphicsObject(GraphicsObject obj) {
  494. this.graphicsObj = obj;
  495. }
  496. /**
  497. * Sets the AFP painting state
  498. *
  499. * @param state the AFP painting state
  500. */
  501. public void setPaintingState(AFPPaintingState state) {
  502. this.state = state;
  503. }
  504. /**
  505. * Returns the AFP state
  506. *
  507. * @return the AFP state
  508. */
  509. public AFPPaintingState getPaintingState() {
  510. return this.state;
  511. }
  512. /**
  513. * Sets the FontInfo
  514. *
  515. * @param the FontInfo
  516. */
  517. public void setFontInfo(FontInfo fontInfo) {
  518. this.fontInfo = fontInfo;
  519. }
  520. /**
  521. * Returns the FontInfo
  522. *
  523. * @return the FontInfo
  524. */
  525. public FontInfo getFontInfo() {
  526. return this.fontInfo;
  527. }
  528. }