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

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