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

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