Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

Java2DRenderer.java 40KB

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