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

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