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

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