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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  1. /*
  2. * Copyright 1999-2005 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /* $Id$ */
  17. package org.apache.fop.render.java2d;
  18. // Java
  19. import java.awt.Color;
  20. import java.awt.Graphics;
  21. import java.awt.Graphics2D;
  22. import java.awt.RenderingHints;
  23. import java.awt.color.ColorSpace;
  24. import java.awt.geom.AffineTransform;
  25. import java.awt.geom.Line2D;
  26. import java.awt.geom.Rectangle2D;
  27. import java.awt.image.BufferedImage;
  28. import java.awt.image.ColorModel;
  29. import java.awt.image.ComponentColorModel;
  30. import java.awt.image.DataBuffer;
  31. import java.awt.image.DataBufferByte;
  32. import java.awt.image.PixelInterleavedSampleModel;
  33. import java.awt.image.Raster;
  34. import java.awt.image.SampleModel;
  35. import java.awt.image.WritableRaster;
  36. import java.awt.print.PageFormat;
  37. import java.awt.print.Printable;
  38. import java.awt.print.PrinterException;
  39. import java.io.IOException;
  40. import java.io.OutputStream;
  41. import java.util.Iterator;
  42. import java.util.List;
  43. import java.util.Map;
  44. import org.apache.fop.apps.FOPException;
  45. import org.apache.fop.apps.FOUserAgent;
  46. import org.apache.fop.area.Area;
  47. import org.apache.fop.area.Block;
  48. import org.apache.fop.area.BlockViewport;
  49. import org.apache.fop.area.CTM;
  50. import org.apache.fop.area.PageViewport;
  51. import org.apache.fop.area.Trait;
  52. import org.apache.fop.area.inline.Character;
  53. import org.apache.fop.area.inline.ForeignObject;
  54. import org.apache.fop.area.inline.Image;
  55. import org.apache.fop.area.inline.InlineArea;
  56. import org.apache.fop.area.inline.Leader;
  57. import org.apache.fop.area.inline.TextArea;
  58. import org.apache.fop.datatypes.ColorType;
  59. import org.apache.fop.fo.Constants;
  60. import org.apache.fop.fonts.Font;
  61. import org.apache.fop.fonts.FontInfo;
  62. import org.apache.fop.fonts.FontMetrics;
  63. import org.apache.fop.image.FopImage;
  64. import org.apache.fop.image.ImageFactory;
  65. import org.apache.fop.image.XMLImage;
  66. import org.apache.fop.render.AbstractRenderer;
  67. import org.apache.fop.render.Graphics2DAdapter;
  68. import org.apache.fop.render.RendererContext;
  69. import org.apache.fop.render.pdf.CTMHelper;
  70. import org.apache.fop.traits.BorderProps;
  71. import org.w3c.dom.Document;
  72. /**
  73. * The <code>Java2DRenderer</code> class provides the abstract technical
  74. * foundation for all rendering with the Java2D API. Renderers like
  75. * <code>AWTRenderer</code> subclass it and provide the concrete output paths.
  76. * <p>
  77. * A lot of the logic is performed by <code>AbstractRenderer</code>. The
  78. * class-variables <code>currentIPPosition</code> and
  79. * <code>currentBPPosition</code> hold the position of the currently rendered
  80. * area.
  81. * <p>
  82. * <code>Java2DGraphicsState state</code> holds the <code>Graphics2D</code>,
  83. * which is used along the whole rendering. <code>state</code> also acts as a
  84. * stack (<code>state.push()</code> and <code>state.pop()</code>).
  85. * <p>
  86. * The rendering process is basically always the same:
  87. * <p>
  88. * <code>void renderXXXXX(Area area) {
  89. * //calculate the currentPosition
  90. * state.updateFont(name, size, null);
  91. * state.updateColor(ct, false, null);
  92. * state.getGraph.draw(new Shape(args));
  93. * }</code>
  94. *
  95. */
  96. public abstract class Java2DRenderer extends AbstractRenderer implements Printable {
  97. /** The scale factor for the image size, values: ]0 ; 1] */
  98. protected double scaleFactor = 1;
  99. /** The page width in pixels */
  100. protected int pageWidth = 0;
  101. /** The page height in pixels */
  102. protected int pageHeight = 0;
  103. /** List of Viewports */
  104. protected List pageViewportList = new java.util.ArrayList();
  105. /** The 0-based current page number */
  106. private int currentPageNumber = 0;
  107. /** The 0-based total number of rendered pages */
  108. private int numberOfPages;
  109. /** true if antialiasing is set */
  110. protected boolean antialiasing = true;
  111. /** true if qualityRendering is set */
  112. protected boolean qualityRendering = true;
  113. /** The current state, holds a Graphics2D and its context */
  114. protected Java2DGraphicsState state;
  115. /** a Line2D.Float used to draw text decorations and leaders */
  116. protected Line2D.Float line = new Line2D.Float();
  117. /** Font configuration */
  118. protected FontInfo fontInfo;
  119. protected Map fontNames = new java.util.Hashtable();
  120. protected Map fontStyles = new java.util.Hashtable();
  121. /** true if the renderer has finished rendering all the pages */
  122. public boolean renderingDone;
  123. /** Default constructor */
  124. public Java2DRenderer() {
  125. }
  126. /**
  127. * @see org.apache.fop.render.Renderer#setUserAgent(org.apache.fop.apps.FOUserAgent)
  128. */
  129. public void setUserAgent(FOUserAgent foUserAgent) {
  130. super.setUserAgent(foUserAgent);
  131. Java2DSVGHandler xmlHandler = new Java2DSVGHandler(getMimeType());
  132. userAgent.getXMLHandlerRegistry().addXMLHandler(xmlHandler);
  133. userAgent.setRendererOverride(this); // for document regeneration
  134. }
  135. /** @return the FOUserAgent */
  136. public FOUserAgent getUserAgent() {
  137. return userAgent;
  138. }
  139. /**
  140. * @see org.apache.fop.render.Renderer#setupFontInfo(org.apache.fop.fonts.FontInfo)
  141. */
  142. public void setupFontInfo(FontInfo inFontInfo) {
  143. // create a temp Image to test font metrics on
  144. fontInfo = inFontInfo;
  145. BufferedImage fontImage = new BufferedImage(100, 100,
  146. BufferedImage.TYPE_INT_RGB);
  147. FontSetup.setup(fontInfo, fontImage.createGraphics());
  148. }
  149. /** @see org.apache.fop.render.Renderer#getGraphics2DAdapter() */
  150. public Graphics2DAdapter getGraphics2DAdapter() {
  151. return new Java2DGraphics2DAdapter(state);
  152. }
  153. /**
  154. * Sets the new scale factor.
  155. * @param newScaleFactor ]0 ; 1]
  156. */
  157. public void setScaleFactor(double newScaleFactor) {
  158. scaleFactor = newScaleFactor;
  159. }
  160. public double getScaleFactor() {
  161. return scaleFactor;
  162. }
  163. public void startRenderer(OutputStream out) throws IOException {
  164. // do nothing by default
  165. }
  166. public void stopRenderer() throws IOException {
  167. log.debug("Java2DRenderer stopped");
  168. renderingDone = true;
  169. numberOfPages = currentPageNumber;
  170. // TODO set all vars to null for gc
  171. if (numberOfPages == 0) {
  172. new FOPException("No page could be rendered");
  173. }
  174. }
  175. /**
  176. * @return The 0-based current page number
  177. */
  178. public int getCurrentPageNumber() {
  179. return currentPageNumber;
  180. }
  181. /**
  182. * @param c the 0-based current page number
  183. */
  184. public void setCurrentPageNumber(int c) {
  185. this.currentPageNumber = c;
  186. }
  187. /**
  188. * @return The 0-based total number of rendered pages
  189. */
  190. public int getNumberOfPages() {
  191. return numberOfPages;
  192. }
  193. /**
  194. * Clears the ViewportList.
  195. * Used if the document is reloaded.
  196. */
  197. public void clearViewportList() {
  198. pageViewportList.clear();
  199. setCurrentPageNumber(0);
  200. }
  201. /**
  202. * This method override only stores the PageViewport in a List. No actual
  203. * rendering is performed here. A renderer override renderPage() to get the
  204. * freshly produced PageViewport, and rendere them on the fly (producing the
  205. * desired BufferedImages by calling getPageImage(), which lazily starts the
  206. * rendering process).
  207. *
  208. * @param pageViewport the <code>PageViewport</code> object supplied by
  209. * the Area Tree
  210. * @see org.apache.fop.render.Renderer
  211. */
  212. public void renderPage(PageViewport pageViewport)
  213. throws IOException, FOPException {
  214. // TODO clone
  215. pageViewportList.add(pageViewport.clone());
  216. currentPageNumber++;
  217. }
  218. /**
  219. * Generates a desired page from the renderer's page viewport list.
  220. *
  221. * @param pageViewport the PageViewport to be rendered
  222. * @return the <code>java.awt.image.BufferedImage</code> corresponding to
  223. * the page or null if the page doesn't exist.
  224. */
  225. public BufferedImage getPageImage(PageViewport pageViewport) {
  226. Rectangle2D bounds = pageViewport.getViewArea();
  227. pageWidth = (int) Math.round(bounds.getWidth() / 1000f);
  228. pageHeight = (int) Math.round(bounds.getHeight() / 1000f);
  229. log.info(
  230. "Rendering Page " + pageViewport.getPageNumberString()
  231. + " (pageWidth " + pageWidth + ", pageHeight "
  232. + pageHeight + ")");
  233. double scaleX = scaleFactor
  234. * FOUserAgent.DEFAULT_PX2MM / userAgent.getPixelUnitToMillimeter();
  235. double scaleY = scaleFactor
  236. * FOUserAgent.DEFAULT_PX2MM / userAgent.getPixelUnitToMillimeter();
  237. int bitmapWidth = (int) ((pageWidth * scaleX) + 0.5);
  238. int bitmapHeight = (int) ((pageHeight * scaleY) + 0.5);
  239. BufferedImage currentPageImage = new BufferedImage(
  240. bitmapWidth, bitmapHeight, BufferedImage.TYPE_INT_ARGB);
  241. // FIXME TYPE_BYTE_BINARY ?
  242. Graphics2D graphics = currentPageImage.createGraphics();
  243. graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
  244. RenderingHints.VALUE_FRACTIONALMETRICS_ON);
  245. if (antialiasing) {
  246. graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
  247. RenderingHints.VALUE_ANTIALIAS_ON);
  248. graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
  249. RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
  250. }
  251. if (qualityRendering) {
  252. graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
  253. RenderingHints.VALUE_RENDER_QUALITY);
  254. }
  255. // transform page based on scale factor supplied
  256. AffineTransform at = graphics.getTransform();
  257. at.scale(scaleX, scaleY);
  258. graphics.setTransform(at);
  259. // draw page frame
  260. graphics.setColor(Color.white);
  261. graphics.fillRect(0, 0, pageWidth, pageHeight);
  262. graphics.setColor(Color.black);
  263. graphics.drawRect(-1, -1, pageWidth + 2, pageHeight + 2);
  264. graphics.drawLine(pageWidth + 2, 0, pageWidth + 2, pageHeight + 2);
  265. graphics.drawLine(pageWidth + 3, 1, pageWidth + 3, pageHeight + 3);
  266. graphics.drawLine(0, pageHeight + 2, pageWidth + 2, pageHeight + 2);
  267. graphics.drawLine(1, pageHeight + 3, pageWidth + 3, pageHeight + 3);
  268. state = new Java2DGraphicsState(graphics, this.fontInfo, at);
  269. // reset the current Positions
  270. currentBPPosition = 0;
  271. currentIPPosition = 0;
  272. // this toggles the rendering of all areas
  273. renderPageAreas(pageViewport.getPage());
  274. return currentPageImage;
  275. }
  276. /**
  277. * Returns the page viewport
  278. * @param pageNum the page number
  279. * @exception FOPException If the page is out of range.
  280. */
  281. public PageViewport getPageViewport(int pageNum) throws FOPException {
  282. if (pageNum < 0 || pageNum >= pageViewportList.size()) {
  283. throw new FOPException("Requested page number is out of range: " + pageNum
  284. + "; only " + pageViewportList.size()
  285. + " page(s) available.");
  286. }
  287. return (PageViewport) pageViewportList.get(pageNum);
  288. }
  289. /**
  290. * Generates a desired page from the renderer's page viewport list.
  291. *
  292. * @param pageNum the 0-based page number to generate
  293. * @return the <code>java.awt.image.BufferedImage</code> corresponding to
  294. * the page or null if the page doesn't exist.
  295. * @throws FOPException
  296. */
  297. public BufferedImage getPageImage(int pageNum) throws FOPException {
  298. return getPageImage(getPageViewport(pageNum));
  299. }
  300. /**
  301. * @see org.apache.fop.render.AbstractRenderer#startVParea(CTM)
  302. */
  303. protected void startVParea(CTM ctm) {
  304. // push (and save) the current graphics state
  305. state.push();
  306. // Set the given CTM in the graphics state
  307. state.setTransform(new AffineTransform(CTMHelper.toPDFArray(ctm)));
  308. // TODO Set clip?
  309. }
  310. /**
  311. * @see org.apache.fop.render.AbstractRenderer#endVParea()
  312. */
  313. protected void endVParea() {
  314. state.pop();
  315. }
  316. /**
  317. * @see org.apache.fop.render.AbstractRenderer#renderBlockViewport(BlockViewport,
  318. * List)
  319. */
  320. protected void renderBlockViewport(BlockViewport bv, List children) {
  321. // clip and position viewport if necessary
  322. // save positions
  323. int saveIP = currentIPPosition;
  324. int saveBP = currentBPPosition;
  325. CTM ctm = bv.getCTM();
  326. int borderPaddingStart = bv.getBorderAndPaddingWidthStart();
  327. int borderPaddingBefore = bv.getBorderAndPaddingWidthBefore();
  328. float x, y;
  329. x = (float) (bv.getXOffset() + containingIPPosition) / 1000f;
  330. y = (float) (bv.getYOffset() + containingBPPosition) / 1000f;
  331. if (bv.getPositioning() == Block.ABSOLUTE
  332. || bv.getPositioning() == Block.FIXED) {
  333. // TODO not tested yet
  334. // For FIXED, we need to break out of the current viewports to the
  335. // one established by the page. We save the state stack for
  336. // restoration
  337. // after the block-container has been painted. See below.
  338. List breakOutList = null;
  339. if (bv.getPositioning() == Block.FIXED) {
  340. log.debug("Block.FIXED --> break out");
  341. breakOutList = new java.util.ArrayList();
  342. Graphics2D graph;
  343. while (true) {
  344. graph = state.getGraph();
  345. if (state.pop() == null) {
  346. break;
  347. }
  348. breakOutList.add(0, graph); // Insert because of
  349. // stack-popping
  350. log.debug("Adding to break out list: " + graph);
  351. }
  352. }
  353. CTM tempctm = new CTM(containingIPPosition, containingBPPosition);
  354. ctm = tempctm.multiply(ctm);
  355. // This is the content-rect
  356. float width = (float) bv.getIPD() / 1000f;
  357. float height = (float) bv.getBPD() / 1000f;
  358. // Adjust for spaces (from margin or indirectly by start-indent etc.
  359. Integer spaceStart = (Integer) bv.getTrait(Trait.SPACE_START);
  360. if (spaceStart != null) {
  361. x += spaceStart.floatValue() / 1000;
  362. }
  363. Integer spaceBefore = (Integer) bv.getTrait(Trait.SPACE_BEFORE);
  364. if (spaceBefore != null) {
  365. y += spaceBefore.floatValue() / 1000;
  366. }
  367. float bpwidth = (borderPaddingStart + bv
  368. .getBorderAndPaddingWidthEnd()) / 1000f;
  369. float bpheight = (borderPaddingBefore + bv
  370. .getBorderAndPaddingWidthAfter()) / 1000f;
  371. drawBackAndBorders(bv, x, y, width + bpwidth, height + bpheight);
  372. // Now adjust for border/padding
  373. x += borderPaddingStart / 1000f;
  374. y += borderPaddingBefore / 1000f;
  375. if (bv.getClip()) {
  376. // saves the graphics state in a stack
  377. state.push();
  378. clip(x, y, width, height);
  379. }
  380. startVParea(ctm);
  381. renderBlocks(bv, children);
  382. endVParea();
  383. if (bv.getClip()) {
  384. // restores the last graphics state from the stack
  385. state.pop();
  386. }
  387. // clip if necessary
  388. if (breakOutList != null) {
  389. log.debug(
  390. "Block.FIXED --> restoring context after break-out");
  391. Graphics2D graph;
  392. Iterator i = breakOutList.iterator();
  393. while (i.hasNext()) {
  394. graph = (Graphics2D) i.next();
  395. log.debug("Restoring: " + graph);
  396. state.push();
  397. }
  398. }
  399. currentIPPosition = saveIP;
  400. currentBPPosition = saveBP;
  401. } else { // orientation = Block.STACK or RELATIVE
  402. Integer spaceBefore = (Integer) bv.getTrait(Trait.SPACE_BEFORE);
  403. if (spaceBefore != null) {
  404. currentBPPosition += spaceBefore.intValue();
  405. }
  406. // borders and background in the old coordinate system
  407. handleBlockTraits(bv);
  408. CTM tempctm = new CTM(containingIPPosition, currentBPPosition
  409. + containingBPPosition);
  410. ctm = tempctm.multiply(ctm);
  411. // Now adjust for border/padding
  412. x += borderPaddingStart / 1000f;
  413. y += borderPaddingBefore / 1000f;
  414. // clip if necessary
  415. if (bv.getClip()) {
  416. // saves the graphics state in a stack
  417. state.push();
  418. float width = (float) bv.getIPD() / 1000f;
  419. float height = (float) bv.getBPD() / 1000f;
  420. clip(x, y, width, height);
  421. }
  422. if (ctm != null) {
  423. startVParea(ctm);
  424. }
  425. renderBlocks(bv, children);
  426. if (ctm != null) {
  427. endVParea();
  428. }
  429. if (bv.getClip()) {
  430. // restores the last graphics state from the stack
  431. state.pop();
  432. }
  433. currentIPPosition = saveIP;
  434. currentBPPosition = saveBP;
  435. // Adjust BP position (alloc BPD + spaces)
  436. if (spaceBefore != null) {
  437. currentBPPosition += spaceBefore.intValue();
  438. }
  439. currentBPPosition += (int) (bv.getAllocBPD());
  440. Integer spaceAfter = (Integer) bv.getTrait(Trait.SPACE_AFTER);
  441. if (spaceAfter != null) {
  442. currentBPPosition += spaceAfter.intValue();
  443. }
  444. }
  445. }
  446. /**
  447. * Clip an area.
  448. */
  449. protected void clip() {
  450. // TODO via AWTGraphicsState.updateClip();
  451. // currentStream.add("W\n");
  452. // currentStream.add("n\n");
  453. }
  454. /**
  455. * Clip an area. write a clipping operation given coordinates in the current
  456. * transform.
  457. *
  458. * @param x the x coordinate
  459. * @param y the y coordinate
  460. * @param width the width of the area
  461. * @param height the height of the area
  462. */
  463. protected void clip(float x, float y, float width, float height) {
  464. // TODO via AWTGraphicsState.updateClip();
  465. // currentStream.add(x + " " + y + " " + width + " " + height + "
  466. // re ");
  467. clip();
  468. }
  469. /**
  470. * Draw the background and borders. This draws the background and border
  471. * traits for an area given the position.
  472. *
  473. * @param area the area whose traits are used
  474. * @param startx the start x position
  475. * @param starty the start y position
  476. * @param width the width of the area
  477. * @param height the height of the area
  478. */
  479. protected void drawBackAndBorders(Area area, float startx, float starty,
  480. float width, float height) {
  481. BorderProps bpsBefore = (BorderProps) area
  482. .getTrait(Trait.BORDER_BEFORE);
  483. BorderProps bpsAfter = (BorderProps) area.getTrait(Trait.BORDER_AFTER);
  484. BorderProps bpsStart = (BorderProps) area.getTrait(Trait.BORDER_START);
  485. BorderProps bpsEnd = (BorderProps) area.getTrait(Trait.BORDER_END);
  486. // draw background
  487. Trait.Background back;
  488. back = (Trait.Background) area.getTrait(Trait.BACKGROUND);
  489. if (back != null) {
  490. // Calculate padding rectangle
  491. float sx = startx;
  492. float sy = starty;
  493. float paddRectWidth = width;
  494. float paddRectHeight = height;
  495. if (bpsStart != null) {
  496. sx += bpsStart.width / 1000f;
  497. paddRectWidth -= bpsStart.width / 1000f;
  498. }
  499. if (bpsBefore != null) {
  500. sy += bpsBefore.width / 1000f;
  501. paddRectHeight -= bpsBefore.width / 1000f;
  502. }
  503. if (bpsEnd != null) {
  504. paddRectWidth -= bpsEnd.width / 1000f;
  505. }
  506. if (bpsAfter != null) {
  507. paddRectHeight -= bpsAfter.width / 1000f;
  508. }
  509. if (back.getColor() != null) {
  510. drawBackground(back, sx, sy, paddRectWidth, paddRectHeight);
  511. }
  512. // background image
  513. if (back.getFopImage() != null) {
  514. FopImage fopimage = back.getFopImage();
  515. if (fopimage != null && fopimage.load(FopImage.DIMENSIONS)) {
  516. clip(sx, sy, paddRectWidth, paddRectHeight);
  517. int horzCount = (int) ((paddRectWidth * 1000 / fopimage
  518. .getIntrinsicWidth()) + 1.0f);
  519. int vertCount = (int) ((paddRectHeight * 1000 / fopimage
  520. .getIntrinsicHeight()) + 1.0f);
  521. if (back.getRepeat() == EN_NOREPEAT) {
  522. horzCount = 1;
  523. vertCount = 1;
  524. } else if (back.getRepeat() == EN_REPEATX) {
  525. vertCount = 1;
  526. } else if (back.getRepeat() == EN_REPEATY) {
  527. horzCount = 1;
  528. }
  529. // change from points to millipoints
  530. sx *= 1000;
  531. sy *= 1000;
  532. if (horzCount == 1) {
  533. sx += back.getHoriz();
  534. }
  535. if (vertCount == 1) {
  536. sy += back.getVertical();
  537. }
  538. for (int x = 0; x < horzCount; x++) {
  539. for (int y = 0; y < vertCount; y++) {
  540. // place once
  541. Rectangle2D pos;
  542. pos = new Rectangle2D.Float(sx
  543. + (x * fopimage.getIntrinsicWidth()), sy
  544. + (y * fopimage.getIntrinsicHeight()),
  545. fopimage.getIntrinsicWidth(), fopimage
  546. .getIntrinsicHeight());
  547. putImage(back.getURL(), pos); // TODO test
  548. }
  549. }
  550. } else {
  551. log.warn(
  552. "Can't find background image: " + back.getURL());
  553. }
  554. }
  555. }
  556. // draw border
  557. // BORDER_BEFORE
  558. if (bpsBefore != null) {
  559. int borderWidth = (int) Math.round((bpsBefore.width / 1000f));
  560. state.updateColor(bpsBefore.color);
  561. state.getGraph().fillRect((int) startx, (int) starty, (int) width,
  562. borderWidth);
  563. }
  564. // BORDER_AFTER
  565. if (bpsAfter != null) {
  566. int borderWidth = (int) Math.round((bpsAfter.width / 1000f));
  567. float sy = starty + height;
  568. state.updateColor(bpsAfter.color);
  569. state.getGraph().fillRect((int) startx,
  570. (int) (starty + height - borderWidth), (int) width,
  571. borderWidth);
  572. }
  573. // BORDER_START
  574. if (bpsStart != null) {
  575. int borderWidth = (int) Math.round((bpsStart.width / 1000f));
  576. state.updateColor(bpsStart.color);
  577. state.getGraph().fillRect((int) startx, (int) starty, borderWidth,
  578. (int) height);
  579. }
  580. // BORDER_END
  581. if (bpsEnd != null) {
  582. int borderWidth = (int) Math.round((bpsEnd.width / 1000f));
  583. float sx = startx + width;
  584. state.updateColor(bpsEnd.color);
  585. state.getGraph().fillRect((int) (startx + width - borderWidth),
  586. (int) starty, borderWidth, (int) height);
  587. }
  588. }
  589. /**
  590. * Draw the Background Rectangle of a given area.
  591. *
  592. * @param back the Trait.Background
  593. * @param sx x coordinate of the rectangle to be filled.
  594. * @param sy y the y coordinate of the rectangle to be filled.
  595. * @param paddRectWidth the width of the rectangle to be filled.
  596. * @param paddRectHeight the height of the rectangle to be filled.
  597. */
  598. protected void drawBackground(Trait.Background back, float sx, float sy,
  599. float paddRectWidth, float paddRectHeight) {
  600. state.updateColor(back.getColor());
  601. state.getGraph().fillRect((int) sx, (int) sy, (int) paddRectWidth,
  602. (int) paddRectHeight);
  603. }
  604. /**
  605. * Handle block traits. The block could be any sort of block with any
  606. * positioning so this should render the traits such as border and
  607. * background in its position.
  608. *
  609. * @param block the block to render the traits
  610. */
  611. protected void handleBlockTraits(Block block) {
  612. // copied from pdf
  613. int borderPaddingStart = block.getBorderAndPaddingWidthStart();
  614. int borderPaddingBefore = block.getBorderAndPaddingWidthBefore();
  615. float startx = currentIPPosition / 1000f;
  616. float starty = currentBPPosition / 1000f;
  617. float width = block.getIPD() / 1000f;
  618. float height = block.getBPD() / 1000f;
  619. startx += block.getStartIndent() / 1000f;
  620. startx -= block.getBorderAndPaddingWidthStart() / 1000f;
  621. width += borderPaddingStart / 1000f;
  622. width += block.getBorderAndPaddingWidthEnd() / 1000f;
  623. height += borderPaddingBefore / 1000f;
  624. height += block.getBorderAndPaddingWidthAfter() / 1000f;
  625. drawBackAndBorders(block, startx, starty, width, height);
  626. }
  627. /**
  628. * @see org.apache.fop.render.AbstractRenderer#renderText(TextArea)
  629. */
  630. public void renderText(TextArea text) {
  631. float x = currentIPPosition;
  632. float y = currentBPPosition + text.getOffset() + text.getBaselineOffset(); // baseline
  633. String name = (String) text.getTrait(Trait.FONT_NAME);
  634. int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue();
  635. state.updateFont(name, size, null);
  636. ColorType ct = (ColorType) text.getTrait(Trait.COLOR);
  637. state.updateColor(ct, false, null);
  638. String s = text.getText();
  639. state.getGraph().drawString(s, x / 1000f, y / 1000f);
  640. // getLogger().debug("renderText(): \"" + s + "\", x: "
  641. // + x + ", y: " + y + state);
  642. // rendering text decorations
  643. FontMetrics metrics = fontInfo.getMetricsFor(name);
  644. Font fs = new Font(name, metrics, size);
  645. renderTextDecoration(fs, text, y, x);
  646. super.renderText(text);
  647. }
  648. /**
  649. * @see org.apache.fop.render.AbstractRenderer#renderCharacter(Character)
  650. */
  651. public void renderCharacter(Character ch) {
  652. float x = currentIPPosition;
  653. float y = currentBPPosition + ch.getOffset() + ch.getBaselineOffset(); // baseline
  654. String name = (String) ch.getTrait(Trait.FONT_NAME);
  655. int size = ((Integer) ch.getTrait(Trait.FONT_SIZE)).intValue();
  656. state.updateFont(name, size, null);
  657. ColorType ct = (ColorType) ch.getTrait(Trait.COLOR);
  658. state.updateColor(ct, false, null);
  659. String s = ch.getChar();
  660. state.getGraph().drawString(s, x / 1000f, y / 1000f);
  661. // getLogger().debug( "renderCharacter(): \"" + s + "\", x: "
  662. // + x + ", y: " + y + state);
  663. // rendering text decorations
  664. FontMetrics metrics = fontInfo.getMetricsFor(name);
  665. Font fs = new Font(name, metrics, size);
  666. renderTextDecoration(fs, ch, y, x);
  667. super.renderCharacter(ch);
  668. }
  669. /**
  670. * Paints the text decoration marks.
  671. *
  672. * @param fs Current font
  673. * @param inline inline area to paint the marks for
  674. * @param baseline position of the baseline
  675. * @param startIPD start IPD
  676. */
  677. protected void renderTextDecoration(Font fs, InlineArea inline,
  678. float baseline, float startIPD) {
  679. boolean hasTextDeco = inline.hasUnderline() || inline.hasOverline()
  680. || inline.hasLineThrough();
  681. if (hasTextDeco) {
  682. state.updateStroke((fs.getDescender() / (-8 * 1000f)),
  683. Constants.EN_SOLID);
  684. float endIPD = startIPD + inline.getIPD();
  685. if (inline.hasUnderline()) {
  686. ColorType ct = (ColorType) inline
  687. .getTrait(Trait.UNDERLINE_COLOR);
  688. state.updateColor(ct, false, null);
  689. float y = baseline - fs.getDescender() / 2;
  690. line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f,
  691. y / 1000f);
  692. state.getGraph().draw(line);
  693. }
  694. if (inline.hasOverline()) {
  695. ColorType ct = (ColorType) inline
  696. .getTrait(Trait.OVERLINE_COLOR);
  697. state.updateColor(ct, false, null);
  698. float y = (float) (baseline - (1.1 * fs.getCapHeight()));
  699. line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f,
  700. y / 1000f);
  701. state.getGraph().draw(line);
  702. }
  703. if (inline.hasLineThrough()) {
  704. ColorType ct = (ColorType) inline
  705. .getTrait(Trait.LINETHROUGH_COLOR);
  706. state.updateColor(ct, false, null);
  707. float y = (float) (baseline - (0.45 * fs.getCapHeight()));
  708. line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f,
  709. y / 1000f);
  710. state.getGraph().draw(line);
  711. }
  712. }
  713. }
  714. /**
  715. * Render leader area. This renders a leader area which is an area with a
  716. * rule.
  717. *
  718. * @param area the leader area to render
  719. */
  720. public void renderLeader(Leader area) {
  721. // TODO leader-length: 25%, 50%, 75%, 100% not working yet
  722. // TODO Colors do not work on Leaders yet
  723. float startx = ((float) currentIPPosition) / 1000f;
  724. float starty = ((currentBPPosition + area.getOffset()) / 1000f);
  725. float endx = (currentIPPosition + area.getIPD()) / 1000f;
  726. ColorType ct = (ColorType) area.getTrait(Trait.COLOR);
  727. state.updateColor(ct, true, null);
  728. line.setLine(startx, starty, endx, starty);
  729. float thickness = area.getRuleThickness() / 1000f;
  730. int style = area.getRuleStyle();
  731. switch (style) {
  732. case EN_SOLID:
  733. case EN_DOTTED:
  734. case EN_DASHED:
  735. state.updateStroke(thickness, style);
  736. state.getGraph().draw(line);
  737. break;
  738. case EN_DOUBLE:
  739. state.updateStroke(thickness / 3f, EN_SOLID); // only a third
  740. // upper Leader
  741. line.setLine(startx, starty, endx, starty);
  742. state.getGraph().draw(line);
  743. // lower Leader
  744. line.setLine(startx, starty + 2 * thickness, endx, starty + 2
  745. * thickness);
  746. state.getGraph().draw(line);
  747. break;
  748. case EN_GROOVE:
  749. // The rule looks as though it were carved into the canvas.
  750. // (Top/left half of the rule's thickness is the
  751. // color specified; the other half is white.)
  752. state.updateStroke(thickness / 2f, EN_SOLID); // only the half
  753. // upper Leader
  754. line.setLine(startx, starty, endx, starty);
  755. state.getGraph().draw(line);
  756. // lower Leader
  757. line.setLine(startx, starty + thickness, endx, starty + thickness);
  758. state.getGraph().setColor(Color.white);
  759. state.getGraph().draw(line);
  760. // TODO the implementation could be nicer, f.eg. with triangles at
  761. // the tip of the lines. See also RenderX's implementation (looks
  762. // like a button)
  763. break;
  764. case EN_RIDGE:
  765. // The opposite of "groove", the rule looks as though it were
  766. // coming out of the canvas. (Bottom/right half of the rule's
  767. // thickness is the color specified; the other half is white.)
  768. state.updateStroke(thickness / 2f, EN_SOLID); // only the half
  769. // lower Leader
  770. line.setLine(startx, starty + thickness, endx, starty + thickness);
  771. state.getGraph().draw(line);
  772. // upperLeader
  773. line.setLine(startx, starty, endx, starty);
  774. state.getGraph().setColor(Color.white);
  775. state.getGraph().draw(line);
  776. // TODO the implementation could be nicer, f.eg. with triangles at
  777. // the tip of the lines. See also RenderX's implementation (looks
  778. // like a button)
  779. break;
  780. case EN_NONE:
  781. // No rule is drawn
  782. break;
  783. } // end switch
  784. super.renderLeader(area);
  785. }
  786. /**
  787. * @see org.apache.fop.render.AbstractRenderer#renderImage(Image,
  788. * Rectangle2D)
  789. */
  790. public void renderImage(Image image, Rectangle2D pos) {
  791. // endTextObject();
  792. String url = image.getURL();
  793. putImage(url, pos);
  794. }
  795. /**
  796. * Draws an image
  797. *
  798. * @param pUrl URL of the bitmap
  799. * @param pos Position of the bitmap
  800. */
  801. protected void putImage(String pUrl, Rectangle2D pos) {
  802. int x = currentIPPosition; // TODO + area.getXOffset();
  803. int y = currentBPPosition;
  804. String url = ImageFactory.getURL(pUrl);
  805. ImageFactory fact = ImageFactory.getInstance();
  806. FopImage fopimage = fact.getImage(url, userAgent);
  807. if (fopimage == null) {
  808. return;
  809. }
  810. if (!fopimage.load(FopImage.DIMENSIONS)) {
  811. return;
  812. }
  813. int w = fopimage.getWidth();
  814. int h = fopimage.getHeight();
  815. String mime = fopimage.getMimeType();
  816. if ("text/xml".equals(mime)) {
  817. if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
  818. return;
  819. }
  820. Document doc = ((XMLImage) fopimage).getDocument();
  821. String ns = ((XMLImage) fopimage).getNameSpace();
  822. renderDocument(doc, ns, pos);
  823. } else if ("image/svg+xml".equals(mime)) {
  824. if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
  825. return;
  826. }
  827. Document doc = ((XMLImage) fopimage).getDocument();
  828. String ns = ((XMLImage) fopimage).getNameSpace();
  829. renderDocument(doc, ns, pos);
  830. } else if ("image/eps".equals(mime)) {
  831. log.warn("EPS images are not supported by this renderer");
  832. currentBPPosition += (h * 1000);
  833. } else if ("image/jpeg".equals(mime)) {
  834. if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
  835. return;
  836. }
  837. // TODO Load JPEGs rather through fopimage.load(FopImage.BITMAP),
  838. // but JpegImage will need to be extended for that
  839. // url = url.substring(7);
  840. // url = "C:/eclipse/myWorkbenches/fop4/xml-fop/examples/fo" + url;
  841. java.awt.Image awtImage = new javax.swing.ImageIcon(url).getImage();
  842. state.getGraph().drawImage(awtImage,
  843. (int)(x / 1000f), (int)(y / 1000f),
  844. (int)(pos.getWidth() / 1000f), (int)(pos.getHeight() / 1000f), null);
  845. currentBPPosition += (h * 1000);
  846. } else {
  847. if (!fopimage.load(FopImage.BITMAP)) {
  848. log.warn("Loading of bitmap failed: " + url);
  849. return;
  850. }
  851. byte[] raw = fopimage.getBitmaps();
  852. // TODO Hardcoded color and sample models, FIX ME!
  853. ColorModel cm = new ComponentColorModel(
  854. ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB),
  855. new int[] {8, 8, 8},
  856. false, false,
  857. ColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
  858. SampleModel sampleModel = new PixelInterleavedSampleModel(
  859. DataBuffer.TYPE_BYTE, w, h, 3, w * 3, new int[] { 0, 1, 2 });
  860. DataBuffer dbuf = new DataBufferByte(raw, w * h * 3);
  861. WritableRaster raster = Raster.createWritableRaster(sampleModel,
  862. dbuf, null);
  863. java.awt.Image awtImage;
  864. // Combine the color model and raster into a buffered image
  865. awtImage = new BufferedImage(cm, raster, false, null);
  866. state.getGraph().drawImage(awtImage,
  867. (int)(x / 1000f), (int)(y / 1000f),
  868. (int)(pos.getWidth() / 1000f), (int)(pos.getHeight() / 1000f), null);
  869. currentBPPosition += (h * 1000);
  870. }
  871. }
  872. /**
  873. * @see org.apache.fop.render.AbstractRenderer#renderForeignObject(ForeignObject,
  874. * Rectangle2D)
  875. */
  876. public void renderForeignObject(ForeignObject fo, Rectangle2D pos) {
  877. Document doc = fo.getDocument();
  878. String ns = fo.getNameSpace();
  879. renderDocument(doc, ns, pos);
  880. }
  881. /**
  882. * Renders an XML document (SVG for example).
  883. *
  884. * @param doc DOM document representing the XML document
  885. * @param ns Namespace for the document
  886. * @param pos Position on the page
  887. */
  888. public void renderDocument(Document doc, String ns, Rectangle2D pos) {
  889. RendererContext context;
  890. context = new RendererContext(this, getMimeType());
  891. context.setUserAgent(userAgent);
  892. context.setProperty(Java2DSVGHandler.JAVA2D_STATE, state);
  893. context.setProperty(Java2DSVGHandler.JAVA2D_XPOS,
  894. new Integer(currentIPPosition + (int)pos.getX()));
  895. context.setProperty(Java2DSVGHandler.JAVA2D_YPOS,
  896. new Integer(currentBPPosition + (int)pos.getY()));
  897. context.setProperty(Java2DSVGHandler.JAVA2D_WIDTH,
  898. new Integer((int)pos.getWidth()));
  899. context.setProperty(Java2DSVGHandler.JAVA2D_HEIGHT,
  900. new Integer((int) pos.getHeight()));
  901. renderXML(context, doc, ns);
  902. }
  903. /**
  904. * @see java.awt.print.Printable#print(java.awt.Graphics,
  905. * java.awt.print.PageFormat, int)
  906. */
  907. public int print(Graphics g, PageFormat pageFormat, int pageIndex)
  908. throws PrinterException {
  909. if (pageIndex >= getNumberOfPages()) {
  910. return NO_SUCH_PAGE;
  911. }
  912. Graphics2D graphics = (Graphics2D) g;
  913. Java2DGraphicsState oldState = state;
  914. BufferedImage image;
  915. try {
  916. PageViewport viewport = getPageViewport(pageIndex);
  917. AffineTransform at = graphics.getTransform();
  918. state = new Java2DGraphicsState(graphics, this.fontInfo, at);
  919. // reset the current Positions
  920. currentBPPosition = 0;
  921. currentIPPosition = 0;
  922. renderPageAreas(viewport.getPage());
  923. return PAGE_EXISTS;
  924. } catch (FOPException e) {
  925. e.printStackTrace();
  926. return NO_SUCH_PAGE;
  927. } finally {
  928. oldState = state;
  929. }
  930. }
  931. }