您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

Java2DRenderer.java 38KB

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