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

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