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.

Java2DRenderer.java 38KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015
  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.java2d;
  19. // Java
  20. import java.awt.BasicStroke;
  21. import java.awt.Color;
  22. import java.awt.Graphics;
  23. import java.awt.Graphics2D;
  24. import java.awt.RenderingHints;
  25. import java.awt.color.ColorSpace;
  26. import java.awt.font.GlyphVector;
  27. import java.awt.geom.AffineTransform;
  28. import java.awt.geom.GeneralPath;
  29. import java.awt.geom.Line2D;
  30. import java.awt.geom.Point2D;
  31. import java.awt.geom.Rectangle2D;
  32. import java.awt.image.BufferedImage;
  33. import java.awt.image.ColorModel;
  34. import java.awt.image.ComponentColorModel;
  35. import java.awt.image.DataBuffer;
  36. import java.awt.image.DataBufferByte;
  37. import java.awt.image.PixelInterleavedSampleModel;
  38. import java.awt.image.Raster;
  39. import java.awt.image.SampleModel;
  40. import java.awt.image.WritableRaster;
  41. import java.awt.print.PageFormat;
  42. import java.awt.print.Printable;
  43. import java.awt.print.PrinterException;
  44. import java.io.IOException;
  45. import java.io.OutputStream;
  46. import java.util.Iterator;
  47. import java.util.List;
  48. import java.util.Map;
  49. import java.util.Stack;
  50. import org.w3c.dom.Document;
  51. import org.apache.fop.apps.FOPException;
  52. import org.apache.fop.apps.FOUserAgent;
  53. import org.apache.fop.apps.FopFactoryConfigurator;
  54. import org.apache.fop.area.CTM;
  55. import org.apache.fop.area.PageViewport;
  56. import org.apache.fop.area.Trait;
  57. import org.apache.fop.area.inline.Image;
  58. import org.apache.fop.area.inline.InlineArea;
  59. import org.apache.fop.area.inline.Leader;
  60. import org.apache.fop.area.inline.SpaceArea;
  61. import org.apache.fop.area.inline.TextArea;
  62. import org.apache.fop.area.inline.WordArea;
  63. import org.apache.fop.fo.Constants;
  64. import org.apache.fop.fonts.Font;
  65. import org.apache.fop.fonts.FontInfo;
  66. import org.apache.fop.fonts.Typeface;
  67. import org.apache.fop.image.FopImage;
  68. import org.apache.fop.image.ImageFactory;
  69. import org.apache.fop.image.XMLImage;
  70. import org.apache.fop.render.AbstractPathOrientedRenderer;
  71. import org.apache.fop.render.Graphics2DAdapter;
  72. import org.apache.fop.render.RendererContext;
  73. import org.apache.fop.render.pdf.CTMHelper;
  74. import org.apache.fop.util.CharUtilities;
  75. /**
  76. * The <code>Java2DRenderer</code> class provides the abstract technical
  77. * foundation for all rendering with the Java2D API. Renderers like
  78. * <code>AWTRenderer</code> subclass it and provide the concrete output paths.
  79. * <p>
  80. * A lot of the logic is performed by <code>AbstractRenderer</code>. The
  81. * class-variables <code>currentIPPosition</code> and
  82. * <code>currentBPPosition</code> hold the position of the currently rendered
  83. * area.
  84. * <p>
  85. * <code>Java2DGraphicsState state</code> holds the <code>Graphics2D</code>,
  86. * which is used along the whole rendering. <code>state</code> also acts as a
  87. * stack (<code>state.push()</code> and <code>state.pop()</code>).
  88. * <p>
  89. * The rendering process is basically always the same:
  90. * <p>
  91. * <code>void renderXXXXX(Area area) {
  92. * //calculate the currentPosition
  93. * state.updateFont(name, size, null);
  94. * state.updateColor(ct, false, null);
  95. * state.getGraph.draw(new Shape(args));
  96. * }</code>
  97. *
  98. */
  99. public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implements Printable {
  100. /** Rendering Options key for the controlling the transparent page background option. */
  101. public static final String JAVA2D_TRANSPARENT_PAGE_BACKGROUND = "transparent-page-background";
  102. /** The scale factor for the image size, values: ]0 ; 1] */
  103. protected double scaleFactor = 1;
  104. /** The page width in pixels */
  105. protected int pageWidth = 0;
  106. /** The page height in pixels */
  107. protected int pageHeight = 0;
  108. /** List of Viewports */
  109. protected List pageViewportList = new java.util.ArrayList();
  110. /** The 0-based current page number */
  111. private int currentPageNumber = 0;
  112. /** The 0-based total number of rendered pages */
  113. private int numberOfPages;
  114. /** true if antialiasing is set */
  115. protected boolean antialiasing = true;
  116. /** true if qualityRendering is set */
  117. protected boolean qualityRendering = true;
  118. /** false: paints a non-transparent white background, true: for a transparent background */
  119. protected boolean transparentPageBackground = false;
  120. /** The current state, holds a Graphics2D and its context */
  121. protected Java2DGraphicsState state;
  122. private Stack stateStack = new Stack();
  123. /** true if the renderer has finished rendering all the pages */
  124. private boolean renderingDone;
  125. private GeneralPath currentPath = null;
  126. /** Default constructor */
  127. public Java2DRenderer() {
  128. }
  129. /**
  130. * @see org.apache.fop.render.Renderer#setUserAgent(org.apache.fop.apps.FOUserAgent)
  131. */
  132. public void setUserAgent(FOUserAgent foUserAgent) {
  133. super.setUserAgent(foUserAgent);
  134. userAgent.setRendererOverride(this); // for document regeneration
  135. String s = (String)userAgent.getRendererOptions().get(JAVA2D_TRANSPARENT_PAGE_BACKGROUND);
  136. if (s != null) {
  137. this.transparentPageBackground = "true".equalsIgnoreCase(s);
  138. }
  139. }
  140. /** @return the FOUserAgent */
  141. public FOUserAgent getUserAgent() {
  142. return userAgent;
  143. }
  144. /**
  145. * @see org.apache.fop.render.Renderer#setupFontInfo(org.apache.fop.fonts.FontInfo)
  146. */
  147. public void setupFontInfo(FontInfo inFontInfo) {
  148. //Don't call super.setupFontInfo() here! Java2D needs a special font setup
  149. // create a temp Image to test font metrics on
  150. fontInfo = inFontInfo;
  151. BufferedImage fontImage = new BufferedImage(100, 100,
  152. BufferedImage.TYPE_INT_RGB);
  153. Graphics2D g = fontImage.createGraphics();
  154. //The next line is important to get accurate font metrics!
  155. g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
  156. RenderingHints.VALUE_FRACTIONALMETRICS_ON);
  157. FontSetup.setup(fontInfo, g);
  158. }
  159. /** @see org.apache.fop.render.Renderer#getGraphics2DAdapter() */
  160. public Graphics2DAdapter getGraphics2DAdapter() {
  161. return new Java2DGraphics2DAdapter(state);
  162. }
  163. /**
  164. * Sets the new scale factor.
  165. * @param newScaleFactor ]0 ; 1]
  166. */
  167. public void setScaleFactor(double newScaleFactor) {
  168. scaleFactor = newScaleFactor;
  169. }
  170. /** @return the scale factor */
  171. public double getScaleFactor() {
  172. return scaleFactor;
  173. }
  174. /** @see org.apache.fop.render.Renderer#startRenderer(java.io.OutputStream) */
  175. public void startRenderer(OutputStream out) throws IOException {
  176. // do nothing by default
  177. }
  178. /** @see org.apache.fop.render.Renderer#stopRenderer() */
  179. public void stopRenderer() throws IOException {
  180. log.debug("Java2DRenderer stopped");
  181. renderingDone = true;
  182. numberOfPages = currentPageNumber;
  183. // TODO set all vars to null for gc
  184. if (numberOfPages == 0) {
  185. new FOPException("No page could be rendered");
  186. }
  187. }
  188. /** @return true if the renderer is not currently processing */
  189. public boolean isRenderingDone() {
  190. return this.renderingDone;
  191. }
  192. /**
  193. * @return The 0-based current page number
  194. */
  195. public int getCurrentPageNumber() {
  196. return currentPageNumber;
  197. }
  198. /**
  199. * @param c the 0-based current page number
  200. */
  201. public void setCurrentPageNumber(int c) {
  202. this.currentPageNumber = c;
  203. }
  204. /**
  205. * @return The 0-based total number of rendered pages
  206. */
  207. public int getNumberOfPages() {
  208. return numberOfPages;
  209. }
  210. /**
  211. * Clears the ViewportList.
  212. * Used if the document is reloaded.
  213. */
  214. public void clearViewportList() {
  215. pageViewportList.clear();
  216. setCurrentPageNumber(0);
  217. }
  218. /**
  219. * This method override only stores the PageViewport in a List. No actual
  220. * rendering is performed here. A renderer override renderPage() to get the
  221. * freshly produced PageViewport, and rendere them on the fly (producing the
  222. * desired BufferedImages by calling getPageImage(), which lazily starts the
  223. * rendering process).
  224. *
  225. * @param pageViewport the <code>PageViewport</code> object supplied by
  226. * the Area Tree
  227. * @throws IOException In case of an I/O error
  228. * @see org.apache.fop.render.Renderer
  229. */
  230. public void renderPage(PageViewport pageViewport) throws IOException {
  231. // TODO clone?
  232. pageViewportList.add(pageViewport.clone());
  233. currentPageNumber++;
  234. }
  235. /**
  236. * Generates a desired page from the renderer's page viewport list.
  237. *
  238. * @param pageViewport the PageViewport to be rendered
  239. * @return the <code>java.awt.image.BufferedImage</code> corresponding to
  240. * the page or null if the page doesn't exist.
  241. */
  242. public BufferedImage getPageImage(PageViewport pageViewport) {
  243. this.currentPageViewport = pageViewport;
  244. try {
  245. Rectangle2D bounds = pageViewport.getViewArea();
  246. pageWidth = (int) Math.round(bounds.getWidth() / 1000f);
  247. pageHeight = (int) Math.round(bounds.getHeight() / 1000f);
  248. log.info(
  249. "Rendering Page " + pageViewport.getPageNumberString()
  250. + " (pageWidth " + pageWidth + ", pageHeight "
  251. + pageHeight + ")");
  252. double scaleX = scaleFactor
  253. * (25.4 / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION)
  254. / userAgent.getTargetPixelUnitToMillimeter();
  255. double scaleY = scaleFactor
  256. * (25.4 / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION)
  257. / userAgent.getTargetPixelUnitToMillimeter();
  258. int bitmapWidth = (int) ((pageWidth * scaleX) + 0.5);
  259. int bitmapHeight = (int) ((pageHeight * scaleY) + 0.5);
  260. BufferedImage currentPageImage = getBufferedImage(bitmapWidth, bitmapHeight);
  261. Graphics2D graphics = currentPageImage.createGraphics();
  262. graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
  263. RenderingHints.VALUE_FRACTIONALMETRICS_ON);
  264. if (antialiasing) {
  265. graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
  266. RenderingHints.VALUE_ANTIALIAS_ON);
  267. graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
  268. RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
  269. }
  270. if (qualityRendering) {
  271. graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
  272. RenderingHints.VALUE_RENDER_QUALITY);
  273. }
  274. // transform page based on scale factor supplied
  275. AffineTransform at = graphics.getTransform();
  276. at.scale(scaleX, scaleY);
  277. graphics.setTransform(at);
  278. // draw page frame
  279. if (!transparentPageBackground) {
  280. graphics.setColor(Color.white);
  281. graphics.fillRect(0, 0, pageWidth, pageHeight);
  282. }
  283. graphics.setColor(Color.black);
  284. graphics.drawRect(-1, -1, pageWidth + 2, pageHeight + 2);
  285. graphics.drawLine(pageWidth + 2, 0, pageWidth + 2, pageHeight + 2);
  286. graphics.drawLine(pageWidth + 3, 1, pageWidth + 3, pageHeight + 3);
  287. graphics.drawLine(0, pageHeight + 2, pageWidth + 2, pageHeight + 2);
  288. graphics.drawLine(1, pageHeight + 3, pageWidth + 3, pageHeight + 3);
  289. state = new Java2DGraphicsState(graphics, this.fontInfo, at);
  290. try {
  291. // reset the current Positions
  292. currentBPPosition = 0;
  293. currentIPPosition = 0;
  294. // this toggles the rendering of all areas
  295. renderPageAreas(pageViewport.getPage());
  296. } finally {
  297. state = null;
  298. }
  299. return currentPageImage;
  300. } finally {
  301. this.currentPageViewport = null;
  302. }
  303. }
  304. /**
  305. * Returns a specific <code>BufferedImage</code> to paint a page image on. This method can
  306. * be overridden in subclasses to produce different image formats (ex. grayscale or b/w).
  307. * @param bitmapWidth width of the image in pixels
  308. * @param bitmapHeight heigth of the image in pixels
  309. * @return the newly created BufferedImage
  310. */
  311. protected BufferedImage getBufferedImage(int bitmapWidth, int bitmapHeight) {
  312. return new BufferedImage(
  313. bitmapWidth, bitmapHeight, BufferedImage.TYPE_INT_ARGB);
  314. }
  315. /**
  316. * Returns a page viewport.
  317. * @param pageNum the page number
  318. * @return the requested PageViewport instance
  319. * @exception FOPException If the page is out of range.
  320. */
  321. public PageViewport getPageViewport(int pageNum) throws FOPException {
  322. if (pageNum < 0 || pageNum >= pageViewportList.size()) {
  323. throw new FOPException("Requested page number is out of range: " + pageNum
  324. + "; only " + pageViewportList.size()
  325. + " page(s) available.");
  326. }
  327. return (PageViewport) pageViewportList.get(pageNum);
  328. }
  329. /**
  330. * Generates a desired page from the renderer's page viewport list.
  331. *
  332. * @param pageNum the 0-based page number to generate
  333. * @return the <code>java.awt.image.BufferedImage</code> corresponding to
  334. * the page or null if the page doesn't exist.
  335. * @throws FOPException If there's a problem preparing the page image
  336. */
  337. public BufferedImage getPageImage(int pageNum) throws FOPException {
  338. return getPageImage(getPageViewport(pageNum));
  339. }
  340. /** Saves the graphics state of the rendering engine. */
  341. protected void saveGraphicsState() {
  342. // push (and save) the current graphics state
  343. stateStack.push(state);
  344. state = new Java2DGraphicsState(state);
  345. }
  346. /** Restores the last graphics state of the rendering engine. */
  347. protected void restoreGraphicsState() {
  348. state.dispose();
  349. state = (Java2DGraphicsState)stateStack.pop();
  350. }
  351. /**
  352. * @see org.apache.fop.render.AbstractRenderer#startVParea(CTM, Rectangle2D)
  353. */
  354. protected void startVParea(CTM ctm, Rectangle2D clippingRect) {
  355. saveGraphicsState();
  356. if (clippingRect != null) {
  357. clipRect((float)clippingRect.getX() / 1000f,
  358. (float)clippingRect.getY() / 1000f,
  359. (float)clippingRect.getWidth() / 1000f,
  360. (float)clippingRect.getHeight() / 1000f);
  361. }
  362. // Set the given CTM in the graphics state
  363. //state.setTransform(new AffineTransform(CTMHelper.toPDFArray(ctm)));
  364. state.transform(new AffineTransform(CTMHelper.toPDFArray(ctm)));
  365. }
  366. /**
  367. * @see org.apache.fop.render.AbstractRenderer#endVParea()
  368. */
  369. protected void endVParea() {
  370. restoreGraphicsState();
  371. }
  372. /**
  373. * @see org.apache.fop.render.AbstractPathOrientedRenderer#breakOutOfStateStack()
  374. */
  375. protected List breakOutOfStateStack() {
  376. log.debug("Block.FIXED --> break out");
  377. List breakOutList;
  378. breakOutList = new java.util.ArrayList();
  379. while (!stateStack.isEmpty()) {
  380. breakOutList.add(0, state);
  381. //We only pop, we don't dispose, because we can use the instances again later
  382. state = (Java2DGraphicsState)stateStack.pop();
  383. }
  384. return breakOutList;
  385. }
  386. /**
  387. * @see org.apache.fop.render.AbstractPathOrientedRenderer#restoreStateStackAfterBreakOut(
  388. * java.util.List)
  389. */
  390. protected void restoreStateStackAfterBreakOut(List breakOutList) {
  391. log.debug("Block.FIXED --> restoring context after break-out");
  392. Iterator i = breakOutList.iterator();
  393. while (i.hasNext()) {
  394. Java2DGraphicsState s = (Java2DGraphicsState)i.next();
  395. stateStack.push(state);
  396. state = s;
  397. }
  398. }
  399. /**
  400. * @see org.apache.fop.render.AbstractPathOrientedRenderer#updateColor(Color, boolean)
  401. */
  402. protected void updateColor(Color col, boolean fill) {
  403. state.updateColor(col);
  404. }
  405. /**
  406. * @see org.apache.fop.render.AbstractPathOrientedRenderer#clip()
  407. */
  408. protected void clip() {
  409. if (currentPath == null) {
  410. throw new IllegalStateException("No current path available!");
  411. }
  412. state.updateClip(currentPath);
  413. currentPath = null;
  414. }
  415. /**
  416. * @see org.apache.fop.render.AbstractPathOrientedRenderer#closePath()
  417. */
  418. protected void closePath() {
  419. currentPath.closePath();
  420. }
  421. /**
  422. * @see org.apache.fop.render.AbstractPathOrientedRenderer#lineTo(float, float)
  423. */
  424. protected void lineTo(float x, float y) {
  425. if (currentPath == null) {
  426. currentPath = new GeneralPath();
  427. }
  428. currentPath.lineTo(x, y);
  429. }
  430. /**
  431. * @see org.apache.fop.render.AbstractPathOrientedRenderer#moveTo(float, float)
  432. */
  433. protected void moveTo(float x, float y) {
  434. if (currentPath == null) {
  435. currentPath = new GeneralPath();
  436. }
  437. currentPath.moveTo(x, y);
  438. }
  439. /**
  440. * @see org.apache.fop.render.AbstractPathOrientedRenderer#clipRect(float, float, float, float)
  441. */
  442. protected void clipRect(float x, float y, float width, float height) {
  443. state.updateClip(new Rectangle2D.Float(x, y, width, height));
  444. }
  445. /**
  446. * @see org.apache.fop.render.AbstractPathOrientedRenderer#fillRect(float, float, float, float)
  447. */
  448. protected void fillRect(float x, float y, float width, float height) {
  449. state.getGraph().fill(new Rectangle2D.Float(x, y, width, height));
  450. }
  451. /**
  452. * @see org.apache.fop.render.AbstractPathOrientedRenderer#drawBorderLine(float, float, float, float, boolean, boolean, int, Color)
  453. */
  454. protected void drawBorderLine(float x1, float y1, float x2, float y2,
  455. boolean horz, boolean startOrBefore, int style, Color col) {
  456. Graphics2D g2d = state.getGraph();
  457. drawBorderLine(new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1),
  458. horz, startOrBefore, style, col, g2d);
  459. }
  460. /**
  461. * Draw a border segment of an XSL-FO style border.
  462. * @param lineRect the line defined by its bounding rectangle
  463. * @param horz true for horizontal border segments, false for vertical border segments
  464. * @param startOrBefore true for border segments on the start or before edge,
  465. * false for end or after.
  466. * @param style the border style (one of Constants.EN_DASHED etc.)
  467. * @param col the color for the border segment
  468. * @param g2d the Graphics2D instance to paint to
  469. */
  470. public static void drawBorderLine(Rectangle2D.Float lineRect,
  471. boolean horz, boolean startOrBefore, int style, Color col, Graphics2D g2d) {
  472. float x1 = lineRect.x;
  473. float y1 = lineRect.y;
  474. float x2 = x1 + lineRect.width;
  475. float y2 = y1 + lineRect.height;
  476. float w = lineRect.width;
  477. float h = lineRect.height;
  478. if ((w < 0) || (h < 0)) {
  479. log.error("Negative extent received. Border won't be painted.");
  480. return;
  481. }
  482. switch (style) {
  483. case Constants.EN_DASHED:
  484. g2d.setColor(col);
  485. if (horz) {
  486. float unit = Math.abs(2 * h);
  487. int rep = (int)(w / unit);
  488. if (rep % 2 == 0) {
  489. rep++;
  490. }
  491. unit = w / rep;
  492. float ym = y1 + (h / 2);
  493. BasicStroke s = new BasicStroke(h, BasicStroke.CAP_BUTT,
  494. BasicStroke.JOIN_MITER, 10.0f, new float[] {unit}, 0);
  495. g2d.setStroke(s);
  496. g2d.draw(new Line2D.Float(x1, ym, x2, ym));
  497. } else {
  498. float unit = Math.abs(2 * w);
  499. int rep = (int)(h / unit);
  500. if (rep % 2 == 0) {
  501. rep++;
  502. }
  503. unit = h / rep;
  504. float xm = x1 + (w / 2);
  505. BasicStroke s = new BasicStroke(w, BasicStroke.CAP_BUTT,
  506. BasicStroke.JOIN_MITER, 10.0f, new float[] {unit}, 0);
  507. g2d.setStroke(s);
  508. g2d.draw(new Line2D.Float(xm, y1, xm, y2));
  509. }
  510. break;
  511. case Constants.EN_DOTTED:
  512. g2d.setColor(col);
  513. if (horz) {
  514. float unit = Math.abs(2 * h);
  515. int rep = (int)(w / unit);
  516. if (rep % 2 == 0) {
  517. rep++;
  518. }
  519. unit = w / rep;
  520. float ym = y1 + (h / 2);
  521. BasicStroke s = new BasicStroke(h, BasicStroke.CAP_ROUND,
  522. BasicStroke.JOIN_MITER, 10.0f, new float[] {0, unit}, 0);
  523. g2d.setStroke(s);
  524. g2d.draw(new Line2D.Float(x1, ym, x2, ym));
  525. } else {
  526. float unit = Math.abs(2 * w);
  527. int rep = (int)(h / unit);
  528. if (rep % 2 == 0) {
  529. rep++;
  530. }
  531. unit = h / rep;
  532. float xm = x1 + (w / 2);
  533. BasicStroke s = new BasicStroke(w, BasicStroke.CAP_ROUND,
  534. BasicStroke.JOIN_MITER, 10.0f, new float[] {0, unit}, 0);
  535. g2d.setStroke(s);
  536. g2d.draw(new Line2D.Float(xm, y1, xm, y2));
  537. }
  538. break;
  539. case Constants.EN_DOUBLE:
  540. g2d.setColor(col);
  541. if (horz) {
  542. float h3 = h / 3;
  543. float ym1 = y1 + (h3 / 2);
  544. float ym2 = ym1 + h3 + h3;
  545. BasicStroke s = new BasicStroke(h3);
  546. g2d.setStroke(s);
  547. g2d.draw(new Line2D.Float(x1, ym1, x2, ym1));
  548. g2d.draw(new Line2D.Float(x1, ym2, x2, ym2));
  549. } else {
  550. float w3 = w / 3;
  551. float xm1 = x1 + (w3 / 2);
  552. float xm2 = xm1 + w3 + w3;
  553. BasicStroke s = new BasicStroke(w3);
  554. g2d.setStroke(s);
  555. g2d.draw(new Line2D.Float(xm1, y1, xm1, y2));
  556. g2d.draw(new Line2D.Float(xm2, y1, xm2, y2));
  557. }
  558. break;
  559. case Constants.EN_GROOVE:
  560. case Constants.EN_RIDGE:
  561. float colFactor = (style == EN_GROOVE ? 0.4f : -0.4f);
  562. if (horz) {
  563. Color uppercol = lightenColor(col, -colFactor);
  564. Color lowercol = lightenColor(col, colFactor);
  565. float h3 = h / 3;
  566. float ym1 = y1 + (h3 / 2);
  567. g2d.setStroke(new BasicStroke(h3));
  568. g2d.setColor(uppercol);
  569. g2d.draw(new Line2D.Float(x1, ym1, x2, ym1));
  570. g2d.setColor(col);
  571. g2d.draw(new Line2D.Float(x1, ym1 + h3, x2, ym1 + h3));
  572. g2d.setColor(lowercol);
  573. g2d.draw(new Line2D.Float(x1, ym1 + h3 + h3, x2, ym1 + h3 + h3));
  574. } else {
  575. Color leftcol = lightenColor(col, -colFactor);
  576. Color rightcol = lightenColor(col, colFactor);
  577. float w3 = w / 3;
  578. float xm1 = x1 + (w3 / 2);
  579. g2d.setStroke(new BasicStroke(w3));
  580. g2d.setColor(leftcol);
  581. g2d.draw(new Line2D.Float(xm1, y1, xm1, y2));
  582. g2d.setColor(col);
  583. g2d.draw(new Line2D.Float(xm1 + w3, y1, xm1 + w3, y2));
  584. g2d.setColor(rightcol);
  585. g2d.draw(new Line2D.Float(xm1 + w3 + w3, y1, xm1 + w3 + w3, y2));
  586. }
  587. break;
  588. case Constants.EN_INSET:
  589. case Constants.EN_OUTSET:
  590. colFactor = (style == EN_OUTSET ? 0.4f : -0.4f);
  591. if (horz) {
  592. col = lightenColor(col, (startOrBefore ? 1 : -1) * colFactor);
  593. g2d.setStroke(new BasicStroke(h));
  594. float ym1 = y1 + (h / 2);
  595. g2d.setColor(col);
  596. g2d.draw(new Line2D.Float(x1, ym1, x2, ym1));
  597. } else {
  598. col = lightenColor(col, (startOrBefore ? 1 : -1) * colFactor);
  599. float xm1 = x1 + (w / 2);
  600. g2d.setStroke(new BasicStroke(w));
  601. g2d.setColor(col);
  602. g2d.draw(new Line2D.Float(xm1, y1, xm1, y2));
  603. }
  604. break;
  605. case Constants.EN_HIDDEN:
  606. break;
  607. default:
  608. g2d.setColor(col);
  609. if (horz) {
  610. float ym = y1 + (h / 2);
  611. g2d.setStroke(new BasicStroke(h));
  612. g2d.draw(new Line2D.Float(x1, ym, x2, ym));
  613. } else {
  614. float xm = x1 + (w / 2);
  615. g2d.setStroke(new BasicStroke(w));
  616. g2d.draw(new Line2D.Float(xm, y1, xm, y2));
  617. }
  618. }
  619. }
  620. /**
  621. * @see org.apache.fop.render.AbstractRenderer#renderText(TextArea)
  622. */
  623. public void renderText(TextArea text) {
  624. renderInlineAreaBackAndBorders(text);
  625. int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
  626. int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
  627. int saveIP = currentIPPosition;
  628. Font font = getFontFromArea(text);
  629. state.updateFont(font.getFontName(), font.getFontSize());
  630. saveGraphicsState();
  631. AffineTransform at = new AffineTransform();
  632. at.translate(rx / 1000f, bl / 1000f);
  633. state.transform(at);
  634. renderText(text, state.getGraph(), font);
  635. restoreGraphicsState();
  636. currentIPPosition = saveIP + text.getAllocIPD();
  637. //super.renderText(text);
  638. // rendering text decorations
  639. Typeface tf = (Typeface) fontInfo.getFonts().get(font.getFontName());
  640. int fontsize = text.getTraitAsInteger(Trait.FONT_SIZE);
  641. renderTextDecoration(tf, fontsize, text, bl, rx);
  642. }
  643. /**
  644. * Renders a TextArea to a Graphics2D instance. Adjust the coordinate system so that the
  645. * start of the baseline of the first character is at coordinate (0,0).
  646. * @param text the TextArea
  647. * @param g2d the Graphics2D to render to
  648. * @param font the font to paint with
  649. */
  650. public static void renderText(TextArea text, Graphics2D g2d, Font font) {
  651. Color col = (Color) text.getTrait(Trait.COLOR);
  652. g2d.setColor(col);
  653. float textCursor = 0;
  654. Iterator iter = text.getChildAreas().iterator();
  655. while (iter.hasNext()) {
  656. InlineArea child = (InlineArea)iter.next();
  657. if (child instanceof WordArea) {
  658. WordArea word = (WordArea)child;
  659. String s = word.getWord();
  660. int[] letterAdjust = word.getLetterAdjustArray();
  661. GlyphVector gv = g2d.getFont().createGlyphVector(g2d.getFontRenderContext(), s);
  662. double additionalWidth = 0.0;
  663. if (letterAdjust == null
  664. && text.getTextLetterSpaceAdjust() == 0
  665. && text.getTextWordSpaceAdjust() == 0) {
  666. //nop
  667. } else {
  668. int[] offsets = getGlyphOffsets(s, font, text, letterAdjust);
  669. float cursor = 0.0f;
  670. for (int i = 0; i < offsets.length; i++) {
  671. Point2D pt = gv.getGlyphPosition(i);
  672. pt.setLocation(cursor, pt.getY());
  673. gv.setGlyphPosition(i, pt);
  674. cursor += offsets[i] / 1000f;
  675. }
  676. additionalWidth = cursor - gv.getLogicalBounds().getWidth();
  677. }
  678. g2d.drawGlyphVector(gv, textCursor, 0);
  679. textCursor += gv.getLogicalBounds().getWidth() + additionalWidth;
  680. } else if (child instanceof SpaceArea) {
  681. SpaceArea space = (SpaceArea)child;
  682. String s = space.getSpace();
  683. char sp = s.charAt(0);
  684. int tws = (space.isAdjustable()
  685. ? text.getTextWordSpaceAdjust()
  686. + 2 * text.getTextLetterSpaceAdjust()
  687. : 0);
  688. textCursor += (font.getCharWidth(sp) + tws) / 1000f;
  689. } else {
  690. throw new IllegalStateException("Unsupported child element: " + child);
  691. }
  692. }
  693. }
  694. private static int[] getGlyphOffsets(String s, Font font, TextArea text,
  695. int[] letterAdjust) {
  696. int textLen = s.length();
  697. int[] offsets = new int[textLen];
  698. for (int i = 0; i < textLen; i++) {
  699. final char c = s.charAt(i);
  700. final char mapped = font.mapChar(c);
  701. int wordSpace;
  702. if (CharUtilities.isAdjustableSpace(mapped)) {
  703. wordSpace = text.getTextWordSpaceAdjust();
  704. } else {
  705. wordSpace = 0;
  706. }
  707. int cw = font.getWidth(mapped);
  708. int ladj = (letterAdjust != null && i < textLen - 1 ? letterAdjust[i + 1] : 0);
  709. int tls = (i < textLen - 1 ? text.getTextLetterSpaceAdjust() : 0);
  710. offsets[i] = cw + ladj + tls + wordSpace;
  711. }
  712. return offsets;
  713. }
  714. /**
  715. * Render leader area. This renders a leader area which is an area with a
  716. * rule.
  717. *
  718. * @param area the leader area to render
  719. */
  720. public void renderLeader(Leader area) {
  721. renderInlineAreaBackAndBorders(area);
  722. // TODO leader-length: 25%, 50%, 75%, 100% not working yet
  723. // TODO Colors do not work on Leaders yet
  724. float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
  725. float starty = ((currentBPPosition + area.getOffset()) / 1000f);
  726. float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
  727. + area.getIPD()) / 1000f;
  728. Color col = (Color) area.getTrait(Trait.COLOR);
  729. state.updateColor(col);
  730. Line2D line = new Line2D.Float();
  731. line.setLine(startx, starty, endx, starty);
  732. float ruleThickness = area.getRuleThickness() / 1000f;
  733. int style = area.getRuleStyle();
  734. switch (style) {
  735. case EN_SOLID:
  736. case EN_DASHED:
  737. case EN_DOUBLE:
  738. drawBorderLine(startx, starty, endx, starty + ruleThickness,
  739. true, true, style, col);
  740. break;
  741. case EN_DOTTED:
  742. //TODO Dots should be shifted to the left by ruleThickness / 2
  743. state.updateStroke(ruleThickness, style);
  744. float rt2 = ruleThickness / 2f;
  745. line.setLine(line.getX1(), line.getY1() + rt2, line.getX2(), line.getY2() + rt2);
  746. state.getGraph().draw(line);
  747. break;
  748. case EN_GROOVE:
  749. case EN_RIDGE:
  750. float half = area.getRuleThickness() / 2000f;
  751. state.updateColor(lightenColor(col, 0.6f));
  752. moveTo(startx, starty);
  753. lineTo(endx, starty);
  754. lineTo(endx, starty + 2 * half);
  755. lineTo(startx, starty + 2 * half);
  756. closePath();
  757. state.getGraph().fill(currentPath);
  758. currentPath = null;
  759. state.updateColor(col);
  760. if (style == EN_GROOVE) {
  761. moveTo(startx, starty);
  762. lineTo(endx, starty);
  763. lineTo(endx, starty + half);
  764. lineTo(startx + half, starty + half);
  765. lineTo(startx, starty + 2 * half);
  766. } else {
  767. moveTo(endx, starty);
  768. lineTo(endx, starty + 2 * half);
  769. lineTo(startx, starty + 2 * half);
  770. lineTo(startx, starty + half);
  771. lineTo(endx - half, starty + half);
  772. }
  773. closePath();
  774. state.getGraph().fill(currentPath);
  775. currentPath = null;
  776. case EN_NONE:
  777. // No rule is drawn
  778. break;
  779. default:
  780. } // end switch
  781. super.renderLeader(area);
  782. }
  783. /**
  784. * @see org.apache.fop.render.AbstractRenderer#renderImage(Image,
  785. * Rectangle2D)
  786. */
  787. public void renderImage(Image image, Rectangle2D pos) {
  788. // endTextObject();
  789. String url = image.getURL();
  790. drawImage(url, pos);
  791. }
  792. /**
  793. * @see org.apache.fop.render.AbstractPathOrientedRenderer#drawImage(
  794. * java.lang.String, java.awt.geom.Rectangle2D, java.util.Map)
  795. */
  796. protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) {
  797. int x = currentIPPosition + (int)Math.round(pos.getX());
  798. int y = currentBPPosition + (int)Math.round(pos.getY());
  799. url = ImageFactory.getURL(url);
  800. ImageFactory fact = userAgent.getFactory().getImageFactory();
  801. FopImage fopimage = fact.getImage(url, userAgent);
  802. if (fopimage == null) {
  803. return;
  804. }
  805. if (!fopimage.load(FopImage.DIMENSIONS)) {
  806. return;
  807. }
  808. int w = fopimage.getWidth();
  809. int h = fopimage.getHeight();
  810. String mime = fopimage.getMimeType();
  811. if ("text/xml".equals(mime)) {
  812. if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
  813. return;
  814. }
  815. Document doc = ((XMLImage) fopimage).getDocument();
  816. String ns = ((XMLImage) fopimage).getNameSpace();
  817. renderDocument(doc, ns, pos, foreignAttributes);
  818. } else if ("image/svg+xml".equals(mime)) {
  819. if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
  820. return;
  821. }
  822. Document doc = ((XMLImage) fopimage).getDocument();
  823. String ns = ((XMLImage) fopimage).getNameSpace();
  824. renderDocument(doc, ns, pos, foreignAttributes);
  825. } else if ("image/eps".equals(mime)) {
  826. log.warn("EPS images are not supported by this renderer");
  827. } else {
  828. if (!fopimage.load(FopImage.BITMAP)) {
  829. log.warn("Loading of bitmap failed: " + url);
  830. return;
  831. }
  832. byte[] raw = fopimage.getBitmaps();
  833. // TODO Hardcoded color and sample models, FIX ME!
  834. ColorModel cm = new ComponentColorModel(
  835. ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB),
  836. new int[] {8, 8, 8},
  837. false, false,
  838. ColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
  839. SampleModel sampleModel = new PixelInterleavedSampleModel(
  840. DataBuffer.TYPE_BYTE, w, h, 3, w * 3, new int[] {0, 1, 2});
  841. DataBuffer dbuf = new DataBufferByte(raw, w * h * 3);
  842. WritableRaster raster = Raster.createWritableRaster(sampleModel,
  843. dbuf, null);
  844. java.awt.Image awtImage;
  845. // Combine the color model and raster into a buffered image
  846. awtImage = new BufferedImage(cm, raster, false, null);
  847. state.getGraph().drawImage(awtImage,
  848. (int)(x / 1000f), (int)(y / 1000f),
  849. (int)(pos.getWidth() / 1000f), (int)(pos.getHeight() / 1000f), null);
  850. }
  851. }
  852. /**
  853. * @see org.apache.fop.render.PrintRenderer#createRendererContext(
  854. * int, int, int, int, java.util.Map)
  855. */
  856. protected RendererContext createRendererContext(int x, int y, int width, int height,
  857. Map foreignAttributes) {
  858. RendererContext context = super.createRendererContext(
  859. x, y, width, height, foreignAttributes);
  860. context.setProperty(Java2DRendererContextConstants.JAVA2D_STATE, state);
  861. return context;
  862. }
  863. /**
  864. * @see java.awt.print.Printable#print(java.awt.Graphics,
  865. * java.awt.print.PageFormat, int)
  866. */
  867. public int print(Graphics g, PageFormat pageFormat, int pageIndex)
  868. throws PrinterException {
  869. if (pageIndex >= getNumberOfPages()) {
  870. return NO_SUCH_PAGE;
  871. }
  872. if (state != null) {
  873. throw new IllegalStateException("state must be null");
  874. }
  875. Graphics2D graphics = (Graphics2D) g;
  876. try {
  877. PageViewport viewport = getPageViewport(pageIndex);
  878. AffineTransform at = graphics.getTransform();
  879. state = new Java2DGraphicsState(graphics, this.fontInfo, at);
  880. // reset the current Positions
  881. currentBPPosition = 0;
  882. currentIPPosition = 0;
  883. renderPageAreas(viewport.getPage());
  884. return PAGE_EXISTS;
  885. } catch (FOPException e) {
  886. log.error(e);
  887. return NO_SUCH_PAGE;
  888. } finally {
  889. state = null;
  890. }
  891. }
  892. /** @see org.apache.fop.render.AbstractPathOrientedRenderer#beginTextObject() */
  893. protected void beginTextObject() {
  894. //not necessary in Java2D
  895. }
  896. /** @see org.apache.fop.render.AbstractPathOrientedRenderer#endTextObject() */
  897. protected void endTextObject() {
  898. //not necessary in Java2D
  899. }
  900. public void setTransparentPageBackground(boolean transparentPageBackground) {
  901. this.transparentPageBackground = transparentPageBackground;
  902. }
  903. }