From c230af051c6cab4460cee8b8d7d1b985fac93c27 Mon Sep 17 00:00:00 2001 From: Jeremias Maerki Date: Tue, 8 Mar 2005 11:47:55 +0000 Subject: [PATCH] Resurrected AWT/Java2D renderer Submitted by: Renaud Richardet Modifications to the patch: - correct copyright years - warning message when EPS files are used as images - removed/commented hard-coded path names, should be improved by loading the image from an InputStream obtained throug the user agent - Enabled loading images from the bitmap array coming from FopImage. There are hard-coded value which need to be fixed but they show the approach. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@198472 13f79535-47bb-0310-9956-ffa450edef68 --- .../fop/render/awt/AWTGraphicsState.java | 332 +++++ .../apache/fop/render/awt/AWTRenderer.java | 1121 ++++++++++++++--- .../apache/fop/render/awt/RendererState.java | 145 +++ .../fop/render/awt/viewer/PreviewDialog.java | 61 +- 4 files changed, 1442 insertions(+), 217 deletions(-) create mode 100644 src/java/org/apache/fop/render/awt/AWTGraphicsState.java create mode 100644 src/java/org/apache/fop/render/awt/RendererState.java diff --git a/src/java/org/apache/fop/render/awt/AWTGraphicsState.java b/src/java/org/apache/fop/render/awt/AWTGraphicsState.java new file mode 100644 index 000000000..7dae50b78 --- /dev/null +++ b/src/java/org/apache/fop/render/awt/AWTGraphicsState.java @@ -0,0 +1,332 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.util.List; + +import org.apache.fop.datatypes.ColorType; +import org.apache.fop.fo.Constants; +import org.apache.fop.fonts.FontInfo; + +/** + * Keeps information about the current state of the Graphics2D currentGraphics. + * It is also used as a stack to hold a graphics context. + *

+ * The graphics context is updated with the updateXXX() methods. + */ +public class AWTGraphicsState implements Constants, RendererState { + + /** Holds the datas of the current state */ + private Graphics2D currentGraphics; + + private BasicStroke currentStroke; + + private float currentStrokeWidth; + + private int currentStrokeStyle; + + private List stateStack = new java.util.ArrayList(); + + /** Font configuration, passed from AWTRenderer */ + private FontInfo fontInfo; + + /** State for storing graphics state. */ + public AWTGraphicsState(Graphics2D graphics, FontInfo fontInfo) { + this.fontInfo = fontInfo; + this.currentGraphics = graphics; + } + + /** + * @return the currently valid state + */ + public Graphics2D getGraph() { + return currentGraphics; + } + + /** @see org.apache.fop.render.awt.RendererState#push() */ + public void push() { + Graphics2D tmpGraphics = (Graphics2D) currentGraphics.create(); + stateStack.add(tmpGraphics); + } + + /** @see org.apache.fop.render.awt.RendererState#pop() */ + public Graphics2D pop() { + if (getStackLevel() > 0) { + Graphics2D popped = (Graphics2D) stateStack.remove(stateStack + .size() - 1); + + currentGraphics = popped; + return popped; + } else { + return null; + } + } + + /** @see org.apache.fop.render.awt.RendererState#getStackLevel() */ + public int getStackLevel() { + return stateStack.size(); + } + + /** + * Restore the state to a particular level. this can be used to restore to a + * known level without making multiple pop calls. + * + * @param stack the level to restore to + */ + /* + * public void restoreLevel(int stack) { int pos = stack; while + * (stateStack.size() > pos + 1) { stateStack.remove(stateStack.size() - 1); } + * if (stateStack.size() > pos) { pop(); } } + */ + + /** + * Set the current background color. Check if the background color will + * change and then set the new color. + * + * @param col the new color as a java.awt.Color + * @return true if the background color has changed + */ + public boolean updateColor(Color col) { + if (!col.equals(getGraph().getColor())) { + getGraph().setColor(col); + return true; + } else { + return false; + } + } + + /** + * Converts a ColorType to a java.awt.Color (sRGB). + * + * @param col the color as a org.apache.fop.datatypes.ColorType + * @return the converted color as a java.awt.Color + */ + public Color toColor(ColorType col) { + return new Color(col.getRed(), col.getGreen(), col.getBlue()); + } + + /** + * @see org.apache.fop.render.awt.RendererState#updateColor(org.apache.fop.datatypes.ColorType, + * boolean, java.lang.StringBuffer) + */ + public boolean updateColor(ColorType col, boolean fill, StringBuffer pdf) { + if (col == null) { + return false; + } + Color newCol = toColor(col); + return updateColor(newCol); + } + + /** + * Update the current Color + * @param col the ColorType + */ + public void updateColor(ColorType col) { + if (col == null) { + return; + } + Color newCol = toColor(col); + updateColor(newCol); + } + + /** + * @return the current java.awt.Color + */ + public java.awt.Color getColor() { + return currentGraphics.getColor(); + } + + /** + * @see org.apache.fop.render.awt.RendererState#updateFont(java.lang.String, + * int, java.lang.StringBuffer) + */ + public boolean updateFont(String name, int size, StringBuffer pdf) { + + boolean updateName = (!name.equals(getGraph().getFont().getFontName())); + boolean updateSize = (size != (getGraph().getFont().getSize())); + + if (updateName || updateSize) { + // the font name and/or the font size have changed + FontMetricsMapper mapper = (FontMetricsMapper) fontInfo + .getMetricsFor(name); + java.awt.Font font = mapper.getFont(size); + + currentGraphics.setFont(font); + return true; + } else { + return false; + } + } + + /** + * @return the current java.awt.Font + */ + public java.awt.Font getFont() { + return currentGraphics.getFont(); + } + + /** + * @see org.apache.fop.render.awt.RendererState#updateStroke(float, int) + */ + public boolean updateStroke(float width, int style) { + + boolean update = false; + + // only update if necessary + if ((width != currentStrokeWidth) || (style != currentStrokeStyle)) { + + update = true; + + switch (style) { + case EN_DOTTED: + + currentStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_BEVEL, 0f, new float[] { 2f }, 0f); + currentGraphics.setStroke(currentStroke); + + currentStrokeWidth = width; + currentStrokeStyle = style; + + break; + + case EN_DASHED: + + currentStroke = new BasicStroke(width, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_BEVEL, 0f, new float[] { 8f, 2f }, 0f); + currentGraphics.setStroke(currentStroke); + + currentStrokeWidth = width; + currentStrokeStyle = style; + + break; + + default: // EN_SOLID: + + currentStroke = new BasicStroke(width); + currentGraphics.setStroke(currentStroke); + + currentStrokeWidth = width; + currentStrokeStyle = style; + + break; + } + } + + return update; + } + + public BasicStroke getStroke() { + return (BasicStroke) currentGraphics.getStroke(); + } + + /** @see org.apache.fop.render.awt.RendererState#updatePaint(java.awt.Paint) */ + public boolean updatePaint(Paint p) { + if (getGraph().getPaint() == null) { + if (p != null) { + getGraph().setPaint(p); + return true; + } + } else if (p.equals(getGraph().getPaint())) { + getGraph().setPaint(p); + return true; + } + return false; + } + + /** @see org.apache.fop.render.awt.RendererState#checkClip(java.awt.Shape) */ + // TODO implement and test + public boolean checkClip(Shape cl) { + if (getGraph().getClip() == null) { + if (cl != null) { + return true; + } + } else if (cl.equals(getGraph().getClip())) { + return true; + } + // TODO check for clips that are larger than the current + return false; + } + + /** + * @see org.apache.fop.render.awt.RendererState#updateClip(java.awt.Shape) + */ + public boolean updateClip(Shape cl) { + if (getGraph().getClip() != null) { + Area newClip = new Area(getGraph().getClip()); + newClip.intersect(new Area(cl)); + getGraph().setClip(new GeneralPath(newClip)); + } else { + getGraph().setClip(cl); + } + return true; // TODO only update if necessary + } + + /** + * @see org.apache.fop.render.awt.RendererState#checkTransform(java.awt.geom.AffineTransform) + */ + public boolean checkTransform(AffineTransform tf) { + return !tf.equals(getGraph().getTransform()); + } + + /** + * @see org.apache.fop.render.awt.RendererState#setTransform(java.awt.geom.AffineTransform) + */ + public void setTransform(AffineTransform tf) { + getGraph().setTransform(tf); + } + + /** + * @see org.apache.fop.render.awt.RendererState#transform(java.awt.geom.AffineTransform) + */ + public void transform(AffineTransform tf) { + getGraph().transform(tf); + } + + /** + * @see org.apache.fop.render.awt.RendererState#getTransform() + */ + public AffineTransform getTransform() { + /* + * AffineTransform tf; AffineTransform at = new AffineTransform(); for + * (Iterator iter = stateStack.iterator(); iter.hasNext();) { Data d = + * (Data) iter.next(); tf = d.transform; at.concatenate(tf); } + * at.concatenate(getCurrentGraphics().transform); + * + * return at; + */ + return getGraph().getTransform(); + } + + /** a verbose description of the current state */ + public String toString() { + String s = "AWTGraphicsState " + currentGraphics.toString() + + ", Stroke (width: " + currentStrokeWidth + " style: " + + currentStrokeStyle + "), " + getTransform() + + ", StackLevel: " + getStackLevel(); + return s; + } +} diff --git a/src/java/org/apache/fop/render/awt/AWTRenderer.java b/src/java/org/apache/fop/render/awt/AWTRenderer.java index 3d87c43b8..ecc8e28cf 100644 --- a/src/java/org/apache/fop/render/awt/AWTRenderer.java +++ b/src/java/org/apache/fop/render/awt/AWTRenderer.java @@ -1,6 +1,6 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. - * + * Copyright 1999-2005 The Apache Software Foundation. + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -27,78 +27,154 @@ package org.apache.fop.render.awt; // Java import java.awt.Color; +import java.awt.Component; +import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Toolkit; +import java.awt.color.ColorSpace; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.MemoryImageSource; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; import java.awt.print.PageFormat; import java.awt.print.Pageable; import java.awt.print.Printable; import java.io.IOException; import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Vector; -import org.apache.fop.fonts.FontInfo; +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.bridge.ViewBox; +import org.apache.batik.gvt.GraphicsNode; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.Area; +import org.apache.fop.area.Block; +import org.apache.fop.area.BlockViewport; +import org.apache.fop.area.CTM; import org.apache.fop.area.Page; import org.apache.fop.area.PageViewport; -import org.apache.fop.area.RegionViewport; import org.apache.fop.area.Trait; +import org.apache.fop.area.inline.Character; +import org.apache.fop.area.inline.ForeignObject; +import org.apache.fop.area.inline.Image; +import org.apache.fop.area.inline.InlineArea; +import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.TextArea; import org.apache.fop.datatypes.ColorType; +import org.apache.fop.fo.Constants; +import org.apache.fop.fo.properties.ColorTypeProperty; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontMetrics; import org.apache.fop.image.FopImage; import org.apache.fop.image.ImageFactory; +import org.apache.fop.image.XMLImage; import org.apache.fop.render.AbstractRenderer; -import org.apache.fop.traits.BorderProps; -import org.apache.fop.render.awt.FontMetricsMapper; +import org.apache.fop.render.RendererContext; import org.apache.fop.render.awt.viewer.PreviewDialog; import org.apache.fop.render.awt.viewer.Translator; +import org.apache.fop.render.pdf.CTMHelper; +import org.apache.fop.svg.SVGUserAgent; +import org.apache.fop.traits.BorderProps; +import org.w3c.dom.Document; +import org.w3c.dom.svg.SVGDocument; +import org.w3c.dom.svg.SVGSVGElement; /** - * This is FOP's AWT renderer. + * The Java2DRenderer class provides the abstract technical + * foundation for all rendering with the Java2D API. Renderers like + * AWTRenderer subclass it and provide the concrete output paths. + *

+ * A lot of the logic is performed by AbstractRenderer. The + * class-variables currentIPPosition and + * currentBPPosition hold the position of the currently rendered + * area. + *

+ * AWTGraphicsState state holds the Graphics2D, + * which is used along the whole rendering. state also acts as a + * stack (state.push() and state.pop()). + *

+ * The rendering process is basically always the same: + *

+ * void renderXXXXX(Area area) { + * //calculate the currentPosition + * state.updateFont(name, size, null); + * state.updateColor(ct, false, null); + * state.getGraph.draw(new Shape(args)); + * } + * */ -public class AWTRenderer extends AbstractRenderer implements Printable, Pageable { +public class AWTRenderer extends AbstractRenderer implements Printable, + Pageable { - /** The MIME type for PostScript */ + /** The MIME type for AWT-Rendering */ public static final String MIME_TYPE = "application/awt"; protected double scaleFactor = 100.0; + protected int pageNumber = 0; + private int pageWidth = 0; + private int pageHeight = 0; + private Vector pageViewportList = new java.util.Vector(); + private Vector pageList = new java.util.Vector(); + private Vector bufferedImageList = new java.util.Vector(); - private BufferedImage currentPageImage = null; - + + protected BufferedImage currentPageImage = null; + + protected boolean antialiasing = true; + + protected boolean qualityRendering = true; + + /** The current state, holds a Graphics2D and its context */ + protected AWTGraphicsState state; + + /** a Line2D.Float used to draw text decorations and leaders */ + protected Line2D.Float line = new Line2D.Float(); + /** Font configuration */ protected FontInfo fontInfo; - /** - * The resource bundle used for AWT messages. - */ + /** The resource bundle used for AWT messages. */ protected Translator translator = null; private Map fontNames = new java.util.Hashtable(); + private Map fontStyles = new java.util.Hashtable(); - private Color saveColor = null; /** - * The preview dialog frame used for display of the documents. - * Also used as the AWT Component for FontSetup in generating - * valid font measures. + * The preview dialog frame used for display of the documents. Also used as + * the AWT Component for FontSetup in generating valid font measures. */ protected PreviewDialog frame; + /** Flag for visual-debugging */ + public boolean debug = false; + public AWTRenderer() { translator = new Translator(); } @@ -124,11 +200,16 @@ public class AWTRenderer extends AbstractRenderer implements Printable, Pageable return translator; } + /** @see org.apache.fop.render.AbstractRenderer */ + public String getMimeType() { + return MIME_TYPE; + } + public void setupFontInfo(FontInfo inFontInfo) { // create a temp Image to test font metrics on fontInfo = inFontInfo; - BufferedImage fontImage = - new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + BufferedImage fontImage = new BufferedImage(100, 100, + BufferedImage.TYPE_INT_RGB); FontSetup.setup(fontInfo, fontImage.createGraphics()); } @@ -148,19 +229,19 @@ public class AWTRenderer extends AbstractRenderer implements Printable, Pageable return scaleFactor; } - public void startRenderer(OutputStream out) - throws IOException { + public void startRenderer(OutputStream out) throws IOException { // empty pageViewportList, in case of a reload from PreviewDialog pageViewportList.removeAllElements(); pageList.removeAllElements(); bufferedImageList.removeAllElements(); - System.out.println("\nRegion Types: 0-Before/Top, 1-Start/Left, 2-Body, 3-End/Right, 4-After/Bottom"); + System.out.println("\nRegion Types: 0-Before/Top, 1-Start/Left," + + " 2-Body, 3-End/Right, 4-After/Bottom"); } - public void stopRenderer() - throws IOException { + public void stopRenderer() throws IOException { frame.setStatus(translator.getString("Status.Show")); frame.showPage(); + // TODO set all vars to null for gc } // Printable Interface @@ -188,7 +269,7 @@ public class AWTRenderer extends AbstractRenderer implements Printable, Pageable } }); - //Centers the window + // Centers the window Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); if (frameSize.height > screenSize.height) { @@ -198,52 +279,68 @@ public class AWTRenderer extends AbstractRenderer implements Printable, Pageable frameSize.width = screenSize.width; } frame.setLocation((screenSize.width - frameSize.width) / 2, - (screenSize.height - frameSize.height) / 2); + (screenSize.height - frameSize.height) / 2); frame.setVisible(true); frame.setStatus(translator.getString("Status.Build.FO.tree")); return frame; } - /** This method override only stores the PageViewport in a vector. - * No actual rendering performed -- this is done by getPageImage(pageNum) instead. - * @param pageViewport the PageViewport object supplied by the Area Tree - * @see org.apache.fop.render.Renderer - */ - public void renderPage(PageViewport pageViewport) throws IOException, FOPException { + /** + * This method override only stores the PageViewport in a vector. No actual + * rendering performed -- this is done by getPageImage(pageNum) instead. + * + * @param pageViewport the PageViewport object supplied by + * the Area Tree + * @see org.apache.fop.render.Renderer + */ + public void renderPage(PageViewport pageViewport) throws IOException, + FOPException { pageViewportList.add(pageViewport); pageList.add(pageViewport.getPage().clone()); - bufferedImageList.add(getPageImage(pageViewport)); + bufferedImageList + .add(getPageImage(pageViewport, pageViewport.getPage())); } public BufferedImage getBufferedPageImage(int pageNum) throws FOPException { return (BufferedImage) bufferedImageList.get(pageNum); } - /** Generates a desired page from the renderer's page viewport vector. + /** + * Generates a desired page from the renderer's page viewport vector. + * * @param pageNum the 0-based page number to generate - * @return the java.awt.image.BufferedImage corresponding to the page - * @throws FOPException in case of an out-of-range page number requested - */ - public BufferedImage getPageImage(PageViewport pageViewport) throws FOPException { - Page page = pageViewport.getPage(); + * @return the java.awt.image.BufferedImage corresponding to + * the page + * @throws FOPException in case of an out-of-range page number requested + */ + public BufferedImage getPageImage(PageViewport pageViewport, Page page) + throws FOPException { Rectangle2D bounds = pageViewport.getViewArea(); - pageWidth = (int) Math.round(bounds.getWidth() / 1000f ); - pageHeight = (int) Math.round(bounds.getHeight() / 1000f ); -/* - System.out.println("(Page) X, Y, Width, Height: " + bounds.getX() - + " " + bounds.getY() - + " " + bounds.getWidth() - + " " + bounds.getHeight()); -*/ - currentPageImage = - new BufferedImage((int)((pageWidth * (int)scaleFactor) / 100), - (int)((pageHeight * (int)scaleFactor) / 100), - BufferedImage.TYPE_INT_RGB); + pageWidth = (int) Math.round(bounds.getWidth() / 1000f); + pageHeight = (int) Math.round(bounds.getHeight() / 1000f); + + getLogger().info( + "Rendering Page " + pageViewport.getPageNumberString() + + " (pageWidth " + pageWidth + ", pageHeight " + + pageHeight + ")"); + + currentPageImage = new BufferedImage( + (int) ((pageWidth * (int) scaleFactor) / 100), + (int) ((pageHeight * (int) scaleFactor) / 100), + BufferedImage.TYPE_INT_RGB); Graphics2D graphics = currentPageImage.createGraphics(); - graphics.setRenderingHint (RenderingHints.KEY_FRACTIONALMETRICS, - RenderingHints.VALUE_FRACTIONALMETRICS_ON); + graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + if (antialiasing) { + graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + } + if (qualityRendering) { + graphics.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + } // transform page based on scale factor supplied AffineTransform at = graphics.getTransform(); @@ -260,98 +357,243 @@ public class AWTRenderer extends AbstractRenderer implements Printable, Pageable graphics.drawLine(0, pageHeight + 2, pageWidth + 2, pageHeight + 2); graphics.drawLine(1, pageHeight + 3, pageWidth + 3, pageHeight + 3); + state = new AWTGraphicsState(graphics, this.fontInfo); + + // reset the current Positions + currentBPPosition = 0; + currentIPPosition = 0; + + // this toggles the rendering of all areas renderPageAreas(page); return currentPageImage; } - /** Generates a desired page from the renderer's page viewport vector. + /** + * Generates a desired page from the renderer's page viewport vector. + * * @param pageNum the 0-based page number to generate - * @return the java.awt.image.BufferedImage corresponding to the page - * @throws FOPException in case of an out-of-range page number requested - */ + * @return the java.awt.image.BufferedImage corresponding to + * the page + * @throws FOPException in case of an out-of-range page number requested + */ public BufferedImage getPageImage(int pageNum) throws FOPException { if (pageNum < 0 || pageNum >= pageViewportList.size()) { throw new FOPException("out-of-range page number (" + pageNum - + ") requested; only " + pageViewportList.size() - + " page(s) available."); + + ") requested; only " + pageViewportList.size() + + " page(s) available."); } - PageViewport pageViewport = (PageViewport) pageViewportList.get(pageNum); + PageViewport pageViewport = (PageViewport) pageViewportList + .get(pageNum); Page page = (Page) pageList.get(pageNum); + return getPageImage(pageViewport, page); + } - Rectangle2D bounds = pageViewport.getViewArea(); - pageWidth = (int) Math.round(bounds.getWidth() / 1000f ); - pageHeight = (int) Math.round(bounds.getHeight() / 1000f ); -/* - System.out.println("(Page) X, Y, Width, Height: " + bounds.getX() - + " " + bounds.getY() - + " " + bounds.getWidth() - + " " + bounds.getHeight()); -*/ - currentPageImage = - new BufferedImage((int)((pageWidth * (int)scaleFactor) / 100), - (int)((pageHeight * (int)scaleFactor) / 100), - BufferedImage.TYPE_INT_RGB); + /** + * @see org.apache.fop.render.AbstractRenderer#startVParea(CTM) + */ + protected void startVParea(CTM ctm) { - Graphics2D graphics = currentPageImage.createGraphics(); - graphics.setRenderingHint (RenderingHints.KEY_FRACTIONALMETRICS, - RenderingHints.VALUE_FRACTIONALMETRICS_ON); + // push (and save) the current graphics state + state.push(); - // transform page based on scale factor supplied - AffineTransform at = graphics.getTransform(); - at.scale(scaleFactor / 100.0, scaleFactor / 100.0); - graphics.setTransform(at); + // Set the given CTM in the graphics state + state.setTransform(new AffineTransform(CTMHelper.toPDFArray(ctm))); - // draw page frame - graphics.setColor(Color.white); - graphics.fillRect(0, 0, pageWidth, pageHeight); - graphics.setColor(Color.black); - graphics.drawRect(-1, -1, pageWidth + 2, pageHeight + 2); - graphics.drawLine(pageWidth + 2, 0, pageWidth + 2, pageHeight + 2); - graphics.drawLine(pageWidth + 3, 1, pageWidth + 3, pageHeight + 3); - graphics.drawLine(0, pageHeight + 2, pageWidth + 2, pageHeight + 2); - graphics.drawLine(1, pageHeight + 3, pageWidth + 3, pageHeight + 3); + // TODO Set clip? + } - renderPageAreas(page); - return currentPageImage; + /** + * @see org.apache.fop.render.AbstractRenderer#endVParea() + */ + protected void endVParea() { + state.pop(); } /** - * Handle the traits for a region - * This is used to draw the traits for the given page region. - * (See Sect. 6.4.1.2 of XSL-FO spec.) - * @param region the RegionViewport whose region is to be drawn + * @see org.apache.fop.render.AbstractRenderer#renderBlockViewport(BlockViewport, + * List) */ - protected void handleRegionTraits(RegionViewport region) { - Rectangle2D viewArea = region.getViewArea(); - - int startX = (int) Math.round((viewArea.getX() / 1000f) - * (scaleFactor / 100f)); - int startY = (int) Math.round((viewArea.getY() / 1000f) - * (scaleFactor / 100f)); - // for rounding to work correctly, need to take into account - // fractional portion of X and Y. - int width = (int) Math.round(((viewArea.getX() + viewArea.getWidth()) / 1000f) - * (scaleFactor / 100f)) - startX; - int height = (int) Math.round(((viewArea.getY() + viewArea.getHeight()) / 1000f) - * (scaleFactor / 100f)) - startY; - - if (region.getRegion() != null) { - System.out.print("\nRegion type = " + region.getRegion().getRegionClass()); + protected void renderBlockViewport(BlockViewport bv, List children) { + // clip and position viewport if necessary + + // save positions + int saveIP = currentIPPosition; + int saveBP = currentBPPosition; + + CTM ctm = bv.getCTM(); + int borderPaddingStart = bv.getBorderAndPaddingWidthStart(); + int borderPaddingBefore = bv.getBorderAndPaddingWidthBefore(); + float x, y; + x = (float) (bv.getXOffset() + containingIPPosition) / 1000f; + y = (float) (bv.getYOffset() + containingBPPosition) / 1000f; + + if (bv.getPositioning() == Block.ABSOLUTE + || bv.getPositioning() == Block.FIXED) { + // TODO not tested yet + // For FIXED, we need to break out of the current viewports to the + // one established by the page. We save the state stack for + // restoration + // after the block-container has been painted. See below. + List breakOutList = null; + if (bv.getPositioning() == Block.FIXED) { + getLogger().debug("Block.FIXED --> break out"); + breakOutList = new java.util.ArrayList(); + Graphics2D graph; + while (true) { + graph = state.getGraph(); + if (state.pop() == null) { + break; + } + breakOutList.add(0, graph); // Insert because of + // stack-popping + getLogger().debug("Adding to break out list: " + graph); + } + } + + CTM tempctm = new CTM(containingIPPosition, containingBPPosition); + ctm = tempctm.multiply(ctm); + + // This is the content-rect + float width = (float) bv.getIPD() / 1000f; + float height = (float) bv.getBPD() / 1000f; + + // Adjust for spaces (from margin or indirectly by start-indent etc. + Integer spaceStart = (Integer) bv.getTrait(Trait.SPACE_START); + if (spaceStart != null) { + x += spaceStart.floatValue() / 1000; + } + Integer spaceBefore = (Integer) bv.getTrait(Trait.SPACE_BEFORE); + if (spaceBefore != null) { + y += spaceBefore.floatValue() / 1000; + } + + float bpwidth = (borderPaddingStart + bv + .getBorderAndPaddingWidthEnd()) / 1000f; + float bpheight = (borderPaddingBefore + bv + .getBorderAndPaddingWidthAfter()) / 1000f; + + drawBackAndBorders(bv, x, y, width + bpwidth, height + bpheight); + + // Now adjust for border/padding + x += borderPaddingStart / 1000f; + y += borderPaddingBefore / 1000f; + + if (bv.getClip()) { + // saves the graphics state in a stack + state.push(); + + clip(x, y, width, height); + } + + startVParea(ctm); + + renderBlocks(bv, children); + endVParea(); + + if (bv.getClip()) { + // restores the last graphics state from the stack + state.pop(); + } + + // clip if necessary + + if (breakOutList != null) { + getLogger().debug( + "Block.FIXED --> restoring context after break-out"); + Graphics2D graph; + Iterator i = breakOutList.iterator(); + while (i.hasNext()) { + graph = (Graphics2D) i.next(); + getLogger().debug("Restoring: " + graph); + state.push(); + } + } + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + + } else { // orientation = Block.STACK or RELATIVE + + Integer spaceBefore = (Integer) bv.getTrait(Trait.SPACE_BEFORE); + if (spaceBefore != null) { + currentBPPosition += spaceBefore.intValue(); + } + + // borders and background in the old coordinate system + handleBlockTraits(bv); + + CTM tempctm = new CTM(containingIPPosition, currentBPPosition + + containingBPPosition); + ctm = tempctm.multiply(ctm); + + // Now adjust for border/padding + x += borderPaddingStart / 1000f; + y += borderPaddingBefore / 1000f; + + // clip if necessary + if (bv.getClip()) { + // saves the graphics state in a stack + state.push(); + float width = (float) bv.getIPD() / 1000f; + float height = (float) bv.getBPD() / 1000f; + clip(x, y, width, height); + } + + if (ctm != null) { + startVParea(ctm); + } + renderBlocks(bv, children); + if (ctm != null) { + endVParea(); + } + + if (bv.getClip()) { + // restores the last graphics state from the stack + state.pop(); + } + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + + // Adjust BP position (alloc BPD + spaces) + if (spaceBefore != null) { + currentBPPosition += spaceBefore.intValue(); + } + currentBPPosition += (int) (bv.getAllocBPD()); + Integer spaceAfter = (Integer) bv.getTrait(Trait.SPACE_AFTER); + if (spaceAfter != null) { + currentBPPosition += spaceAfter.intValue(); + } } + } - System.out.println(" X, Width, Y, Height: " + startX - + " " + width - + " " + startY - + " " + height - ); + /** + * Clip an area. + */ + protected void clip() { + // TODO via AWTGraphicsState.updateClip(); + // currentStream.add("W\n"); + // currentStream.add("n\n"); + } - drawBackAndBorders(region, startX, startY, width, height); + /** + * Clip an area. write a clipping operation given coordinates in the current + * transform. + * @param x the x coordinate + * @param y the y coordinate + * @param width the width of the area + * @param height the height of the area + */ + protected void clip(float x, float y, float width, float height) { + // TODO via AWTGraphicsState.updateClip(); + // currentStream.add(x + " " + y + " " + width + " " + height + " + // re "); + clip(); } /** - * Draw the background and borders. - * This draws the background and border traits for an area given - * the position. + * Draw the background and borders. This draws the background and border + * traits for an area given the position. * * @param block the area to get the traits from * @param startx the start x position @@ -359,112 +601,597 @@ public class AWTRenderer extends AbstractRenderer implements Printable, Pageable * @param width the width of the area * @param height the height of the area */ - protected void drawBackAndBorders(Area block, - int startx, int starty, - int width, int height) { + protected void drawBackAndBorders(Area area, float startx, float starty, + float width, float height) { - // draw background then border - Graphics2D graphics = currentPageImage.createGraphics(); + if (debug) { // TODO implement visual-debugging as standalone + // Renderer + debugBackAndBorders(area, startx, starty, width, height); + } + + BorderProps bpsBefore = (BorderProps) area + .getTrait(Trait.BORDER_BEFORE); + BorderProps bpsAfter = (BorderProps) area.getTrait(Trait.BORDER_AFTER); + BorderProps bpsStart = (BorderProps) area.getTrait(Trait.BORDER_START); + BorderProps bpsEnd = (BorderProps) area.getTrait(Trait.BORDER_END); + + // draw background Trait.Background back; - back = (Trait.Background) block.getTrait(Trait.BACKGROUND); + back = (Trait.Background) area.getTrait(Trait.BACKGROUND); if (back != null) { + // Calculate padding rectangle + float sx = startx; + float sy = starty; + float paddRectWidth = width; + float paddRectHeight = height; + + if (bpsStart != null) { + sx += bpsStart.width / 1000f; + paddRectWidth -= bpsStart.width / 1000f; + } + if (bpsBefore != null) { + sy += bpsBefore.width / 1000f; + paddRectHeight -= bpsBefore.width / 1000f; + } + if (bpsEnd != null) { + paddRectWidth -= bpsEnd.width / 1000f; + } + if (bpsAfter != null) { + paddRectHeight -= bpsAfter.width / 1000f; + } + if (back.getColor() != null) { - graphics.setColor(back.getColor().getAWTColor()); - graphics.fillRect(startx, starty, width, height); + drawBackground(back, sx, sy, paddRectWidth, paddRectHeight); } - if (back.getURL() != null) { // TODO: implement - ImageFactory fact = ImageFactory.getInstance(); - FopImage fopimage = fact.getImage(back.getURL(), userAgent); + + // background image + if (back.getFopImage() != null) { + FopImage fopimage = back.getFopImage(); if (fopimage != null && fopimage.load(FopImage.DIMENSIONS)) { - if (back.getRepeat() == EN_REPEAT) { - // create a pattern for the image - } else { - // place once - Rectangle2D pos; - pos = new Rectangle2D.Float((startx + back.getHoriz()) * 1000, - (starty + back.getVertical()) * 1000, - fopimage.getWidth() * 1000, - fopimage.getHeight() * 1000); -// putImage(back.getURL(), pos); + clip(sx, sy, paddRectWidth, paddRectHeight); + int horzCount = (int) ((paddRectWidth * 1000 / fopimage + .getIntrinsicWidth()) + 1.0f); + int vertCount = (int) ((paddRectHeight * 1000 / fopimage + .getIntrinsicHeight()) + 1.0f); + if (back.getRepeat() == EN_NOREPEAT) { + horzCount = 1; + vertCount = 1; + } else if (back.getRepeat() == EN_REPEATX) { + vertCount = 1; + } else if (back.getRepeat() == EN_REPEATY) { + horzCount = 1; + } + // change from points to millipoints + sx *= 1000; + sy *= 1000; + if (horzCount == 1) { + sx += back.getHoriz(); } + if (vertCount == 1) { + sy += back.getVertical(); + } + for (int x = 0; x < horzCount; x++) { + for (int y = 0; y < vertCount; y++) { + // place once + Rectangle2D pos; + pos = new Rectangle2D.Float(sx + + (x * fopimage.getIntrinsicWidth()), sy + + (y * fopimage.getIntrinsicHeight()), + fopimage.getIntrinsicWidth(), fopimage + .getIntrinsicHeight()); + putImage(back.getURL(), pos); // TODO test + } + } + + } else { + getLogger().warn( + "Can't find background image: " + back.getURL()); } } } - BorderProps bps = (BorderProps) block.getTrait(Trait.BORDER_BEFORE); - if (bps != null) { - int borderWidth = (int) Math.round((bps.width / 1000f) * (scaleFactor / 100f)); - graphics.setColor(bps.color.getAWTColor()); - graphics.fillRect(startx, starty, width, borderWidth); + // draw border + // BORDER_BEFORE + if (bpsBefore != null) { + int borderWidth = (int) Math.round((bpsBefore.width / 1000f) + * (scaleFactor / 100f)); + state.updateColor(bpsBefore.color); + state.getGraph().fillRect((int) startx, (int) starty, (int) width, + borderWidth); } - bps = (BorderProps) block.getTrait(Trait.BORDER_AFTER); - if (bps != null) { - int borderWidth = (int) Math.round((bps.width / 1000f) * (scaleFactor / 100f)); - int sy = starty + height; - graphics.setColor(bps.color.getAWTColor()); - graphics.fillRect(startx, starty + height - borderWidth, - width, borderWidth); + // BORDER_AFTER + if (bpsAfter != null) { + int borderWidth = (int) Math.round((bpsAfter.width / 1000f) + * (scaleFactor / 100f)); + float sy = starty + height; + state.updateColor(bpsAfter.color); + state.getGraph().fillRect((int) startx, + (int) (starty + height - borderWidth), (int) width, + borderWidth); } - bps = (BorderProps) block.getTrait(Trait.BORDER_START); - if (bps != null) { - int borderWidth = (int) Math.round((bps.width / 1000f) * (scaleFactor / 100f)); - graphics.setColor(bps.color.getAWTColor()); - graphics.fillRect(startx, starty, borderWidth, height); + // BORDER_START + if (bpsStart != null) { + int borderWidth = (int) Math.round((bpsStart.width / 1000f) + * (scaleFactor / 100f)); + state.updateColor(bpsStart.color); + state.getGraph().fillRect((int) startx, (int) starty, borderWidth, + (int) height); } - bps = (BorderProps) block.getTrait(Trait.BORDER_END); - if (bps != null) { - int borderWidth = (int) Math.round((bps.width / 1000f) * (scaleFactor / 100f)); - int sx = startx + width; - graphics.setColor(bps.color.getAWTColor()); - graphics.fillRect(startx + width - borderWidth, starty, - borderWidth, height); + // BORDER_END + if (bpsEnd != null) { + int borderWidth = (int) Math.round((bpsEnd.width / 1000f) + * (scaleFactor / 100f)); + float sx = startx + width; + state.updateColor(bpsEnd.color); + state.getGraph().fillRect((int) (startx + width - borderWidth), + (int) starty, borderWidth, (int) height); } - + + } + + /** Draws a thin border around every area to help debugging */ + private void debugBackAndBorders(Area area, float startx, float starty, + float width, float height) { + + // saves the graphics state in a stack + state.push(); + + ColorType ct = new ColorTypeProperty(0.7f, 0.7f, 0.7f); + state.updateColor(ct, true, null); + state.updateStroke(0.4f, EN_SOLID); + state.getGraph().draw( + new Rectangle2D.Float(startx, starty, width, height)); + + // restores the last graphics state from the stack + state.pop(); } - + + /** + * Draw the Background Rectangle of a given area. + * + * @param back the Trait.Background + * @param sx x coordinate of the rectangle to be filled. + * @param sy y the y coordinate of the rectangle to be filled. + * @param paddRectWidth the width of the rectangle to be filled. + * @param paddRectHeight the height of the rectangle to be filled. + */ + protected void drawBackground(Trait.Background back, float sx, float sy, + float paddRectWidth, float paddRectHeight) { + + state.updateColor(back.getColor()); + state.getGraph().fillRect((int) sx, (int) sy, (int) paddRectWidth, + (int) paddRectHeight); + } + + /** + * Handle block traits. The block could be any sort of block with any + * positioning so this should render the traits such as border and + * background in its position. + * + * @param block the block to render the traits + */ + protected void handleBlockTraits(Block block) { + // copied from pdf + int borderPaddingStart = block.getBorderAndPaddingWidthStart(); + int borderPaddingBefore = block.getBorderAndPaddingWidthBefore(); + + float startx = currentIPPosition / 1000f; + float starty = currentBPPosition / 1000f; + float width = block.getIPD() / 1000f; + float height = block.getBPD() / 1000f; + + startx += block.getStartIndent() / 1000f; + startx -= block.getBorderAndPaddingWidthStart() / 1000f; + width += borderPaddingStart / 1000f; + width += block.getBorderAndPaddingWidthEnd() / 1000f; + height += borderPaddingBefore / 1000f; + height += block.getBorderAndPaddingWidthAfter() / 1000f; + + drawBackAndBorders(block, startx, starty, width, height); + } + /** * @see org.apache.fop.render.Renderer#renderText(TextArea) */ public void renderText(TextArea text) { - System.out.println("In render text: " + text.getTextArea()); - Graphics2D graphics = currentPageImage.createGraphics(); - String fontName = (String) text.getTrait(Trait.FONT_NAME); + float x = currentIPPosition; + float y = currentBPPosition + text.getOffset(); // baseline + + String name = (String) text.getTrait(Trait.FONT_NAME); int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue(); -// Typeface f = (Typeface) fontInfo.getFonts().get(fontName); + state.updateFont(name, size, null); + ColorType ct = (ColorType) text.getTrait(Trait.COLOR); + state.updateColor(ct, false, null); + + String s = text.getTextArea(); + state.getGraph().drawString(s, x / 1000f, y / 1000f); + + getLogger().debug( + "renderText(): \"" + s + "\", x: " + x + ", y: " + y + state); + + // rendering text decorations + FontMetrics metrics = fontInfo.getMetricsFor(name); + Font fs = new Font(name, metrics, size); + renderTextDecoration(fs, text, y, x); + + super.renderText(text); + } - FontMetricsMapper mapper = (FontMetricsMapper) - fontInfo.getMetricsFor(fontName); - if (mapper == null) { - mapper = new FontMetricsMapper("MonoSpaced", java.awt.Font.PLAIN, - graphics); + /** + * @see org.apache.fop.render.Renderer#renderCharacter(Character) + */ + public void renderCharacter(Character ch) { + + float x = currentIPPosition; + float y = currentBPPosition + ch.getOffset(); // baseline + + String name = (String) ch.getTrait(Trait.FONT_NAME); + int size = ((Integer) ch.getTrait(Trait.FONT_SIZE)).intValue(); + state.updateFont(name, size, null); + + ColorType ct = (ColorType) ch.getTrait(Trait.COLOR); + state.updateColor(ct, false, null); + + String s = ch.getChar(); + state.getGraph().drawString(s, x / 1000f, y / 1000f); + + getLogger().debug( + "renderCharacter(): \"" + s + "\", x: " + x + ", y: " + y + + state); + + // rendering text decorations + FontMetrics metrics = fontInfo.getMetricsFor(name); + Font fs = new Font(name, metrics, size); + renderTextDecoration(fs, ch, y, x); + + super.renderCharacter(ch); + } + + /** + * Paints the text decoration marks. + * @param fs Current font + * @param inline inline area to paint the marks for + * @param baseline position of the baseline + * @param startIPD start IPD + */ + protected void renderTextDecoration(Font fs, InlineArea inline, + float baseline, float startIPD) { + + boolean hasTextDeco = inline.hasUnderline() || inline.hasOverline() + || inline.hasLineThrough(); + + if (hasTextDeco) { + state.updateStroke((fs.getDescender() / (-8 * 1000f)), + Constants.EN_SOLID); + float endIPD = startIPD + inline.getIPD(); + if (inline.hasUnderline()) { + ColorType ct = (ColorType) inline + .getTrait(Trait.UNDERLINE_COLOR); + state.updateColor(ct, false, null); + float y = baseline - fs.getDescender() / 2; + line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f, + y / 1000f); + state.getGraph().draw(line); + } + if (inline.hasOverline()) { + ColorType ct = (ColorType) inline + .getTrait(Trait.OVERLINE_COLOR); + state.updateColor(ct, false, null); + float y = (float) (baseline - (1.1 * fs.getCapHeight())); + line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f, + y / 1000f); + state.getGraph().draw(line); + } + if (inline.hasLineThrough()) { + ColorType ct = (ColorType) inline + .getTrait(Trait.LINETHROUGH_COLOR); + state.updateColor(ct, false, null); + float y = (float) (baseline - (0.45 * fs.getCapHeight())); + line.setLine(startIPD / 1000f, y / 1000f, endIPD / 1000f, + y / 1000f); + state.getGraph().draw(line); + } } + } -// graphics.setColor(ct.getAWTColor()); -// graphics.setFont(mapper.getFont(size)); - graphics.setColor(java.awt.Color.black); - graphics.setFont(new java.awt.Font("monospaced", java.awt.Font.PLAIN, - 10)); - - int rx = currentIPPosition; - int bl = currentBPPosition + text.getOffset(); - - int newx = (int) (rx + 500) / 1000; - int newy = (int) (pageHeight - (bl + 500) / 1000); - - String s = text.getTextArea(); -// graphics.drawString(s, newx, newy); - graphics.drawString(s, 220, 200); + /** + * Render leader area. This renders a leader area which is an area with a + * rule. + * + * @param area the leader area to render + */ + public void renderLeader(Leader area) { + + // TODO leader-length: 25%, 50%, 75%, 100% not working yet + // TODO Colors do not work on Leaders yet + + float startx = ((float) currentIPPosition) / 1000f; + float starty = ((currentBPPosition + area.getOffset()) / 1000f); + float endx = (currentIPPosition + area.getIPD()) / 1000f; + + ColorType ct = (ColorType) area.getTrait(Trait.COLOR); + state.updateColor(ct, true, null); + + line.setLine(startx, starty, endx, starty); + float thickness = area.getRuleThickness() / 1000f; + + int style = area.getRuleStyle(); + switch (style) { + case EN_SOLID: + case EN_DOTTED: + case EN_DASHED: + state.updateStroke(thickness, style); + state.getGraph().draw(line); + break; + case EN_DOUBLE: + + state.updateStroke(thickness / 3f, EN_SOLID); // only a third + + // upper Leader + line.setLine(startx, starty, endx, starty); + state.getGraph().draw(line); + // lower Leader + line.setLine(startx, starty + 2 * thickness, endx, starty + 2 + * thickness); + state.getGraph().draw(line); + + break; + + case EN_GROOVE: + // The rule looks as though it were carved into the canvas. + // (Top/left half of the rule's thickness is the + // color specified; the other half is white.) - // TODO: render text decorations - currentIPPosition += text.getAllocIPD(); + state.updateStroke(thickness / 2f, EN_SOLID); // only the half + + // upper Leader + line.setLine(startx, starty, endx, starty); + state.getGraph().draw(line); + // lower Leader + line.setLine(startx, starty + thickness, endx, starty + thickness); + state.getGraph().setColor(Color.WHITE); + state.getGraph().draw(line); + + // TODO the implementation could be nicer, f.eg. with triangles at + // the tip of the lines. See also RenderX's implementation (looks + // like a button) + + break; + + case EN_RIDGE: + // The opposite of "groove", the rule looks as though it were + // coming out of the canvas. (Bottom/right half of the rule's + // thickness is the color specified; the other half is white.) + + state.updateStroke(thickness / 2f, EN_SOLID); // only the half + + // lower Leader + line.setLine(startx, starty + thickness, endx, starty + thickness); + state.getGraph().draw(line); + // upperLeader + line.setLine(startx, starty, endx, starty); + state.getGraph().setColor(Color.WHITE); + state.getGraph().draw(line); + + // TODO the implementation could be nicer, f.eg. with triangles at + // the tip of the lines. See also RenderX's implementation (looks + // like a button) + + break; + + case EN_NONE: + // No rule is drawn + break; + + } // end switch + + super.renderLeader(area); } - /** @see org.apache.fop.render.AbstractRenderer */ - public String getMimeType() { - return MIME_TYPE; + /** + * @see org.apache.fop.render.AbstractRenderer#renderImage(Image, + * Rectangle2D) + */ + public void renderImage(Image image, Rectangle2D pos) { + // endTextObject(); + String url = image.getURL(); + putImage(url, pos); + } + + /** + * draws an image + * + * @param url URL of the bitmap + * @param pos Position of the bitmap + */ + protected void putImage(String pUrl, Rectangle2D pos) { + + int x = currentIPPosition; // TODO + area.getXOffset(); + int y = currentBPPosition; + String url = ImageFactory.getURL(pUrl); + + ImageFactory fact = ImageFactory.getInstance(); + FopImage fopimage = fact.getImage(url, userAgent); + + if (fopimage == null) { + return; + } + if (!fopimage.load(FopImage.DIMENSIONS)) { + return; + } + int w = fopimage.getWidth(); + int h = fopimage.getHeight(); + String mime = fopimage.getMimeType(); + if ("text/xml".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA)) { + return; + } + Document doc = ((XMLImage) fopimage).getDocument(); + String ns = ((XMLImage) fopimage).getNameSpace(); + renderDocument(doc, ns, pos); + + } else if ("image/svg+xml".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA)) { + return; + } + Document doc = ((XMLImage) fopimage).getDocument(); + renderSVGDocument(doc, pos); // TODO check if ok. + + } else if ("image/eps".equals(mime)) { + getLogger().warn("EPS images are not supported by this renderer"); + currentBPPosition += (h * 1000); + } else if ("image/jpeg".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA)) { + return; + } + + // TODO Load JPEGs rather through fopimage.load(FopImage.BITMAP), + // but JpegImage will need to be extended for that + + //url = url.substring(7); + //url = "C:/eclipse/myWorkbenches/fop4/xml-fop/examples/fo" + url; + java.awt.Image awtImage = new javax.swing.ImageIcon(url).getImage(); + + state.getGraph().drawImage(awtImage, (int) (x / 1000f), + (int) (y / 1000f), (int) w, h, null); + currentBPPosition += (h * 1000); + } else { + if (!fopimage.load(FopImage.BITMAP)) { + getLogger().warn("Loading of bitmap failed: " + url); + return; + } + + byte[] raw = fopimage.getBitmaps(); + + //TODO Hardcoded color and sample models, FIX ME! + ColorModel cm = new ComponentColorModel( + ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), + false, false, ColorModel.OPAQUE, DataBuffer.TYPE_BYTE); + SampleModel sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, + w, h, 3, w * 3, new int[] {0, 1, 2}); + DataBuffer dbuf = new DataBufferByte(raw, w * h * 3); + + WritableRaster raster = Raster.createWritableRaster(sampleModel, dbuf, null); + + java.awt.Image awtImage; + // Combine the color model and raster into a buffered image + awtImage = new BufferedImage(cm, raster, false, null); + + state.getGraph().drawImage(awtImage, (int) (x / 1000f), + (int) (y / 1000f), (int) w, h, null); + currentBPPosition += (h * 1000); + } + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderForeignObject(ForeignObject, + * Rectangle2D) + */ + public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { + Document doc = fo.getDocument(); + String ns = fo.getNameSpace(); + if (ns.equals("http://www.w3.org/2000/svg")) { + renderSVGDocument(doc, pos); + } else { + renderDocument(doc, ns, pos); + } + // this.currentXPosition += area.getContentWidth(); + } + + /** + * Renders an XML document (SVG for example). + * + * @param doc DOM document representing the XML document + * @param ns Namespace for the document + * @param pos Position on the page + */ + public void renderDocument(Document doc, String ns, Rectangle2D pos) { + RendererContext context; + context = new RendererContext(MIME_TYPE); + context.setUserAgent(userAgent); + // TODO implement + /* + * context.setProperty(PDFXMLHandler.PDF_DOCUMENT, pdfDoc); + * context.setProperty(PDFXMLHandler.OUTPUT_STREAM, ostream); + * context.setProperty(PDFXMLHandler.PDF_STATE, currentState); + * context.setProperty(PDFXMLHandler.PDF_PAGE, currentPage); + * context.setProperty(PDFXMLHandler.PDF_CONTEXT, currentContext == null ? + * currentPage : currentContext); + * context.setProperty(PDFXMLHandler.PDF_CONTEXT, currentContext); + * context.setProperty(PDFXMLHandler.PDF_STREAM, currentStream); + * context.setProperty(PDFXMLHandler.PDF_XPOS, new + * Integer(currentIPPosition + (int) pos.getX())); + * context.setProperty(PDFXMLHandler.PDF_YPOS, new + * Integer(currentBPPosition + (int) pos.getY())); + * context.setProperty(PDFXMLHandler.PDF_FONT_INFO, fontInfo); + * context.setProperty(PDFXMLHandler.PDF_FONT_NAME, currentFontName); + * context.setProperty(PDFXMLHandler.PDF_FONT_SIZE, new + * Integer(currentFontSize)); + * context.setProperty(PDFXMLHandler.PDF_WIDTH, new Integer((int) + * pos.getWidth())); context.setProperty(PDFXMLHandler.PDF_HEIGHT, new + * Integer((int) pos.getHeight())); renderXML(userAgent, context, doc, + * ns); + */ } + protected void renderSVGDocument(Document doc, Rectangle2D pos) { + + int x = currentIPPosition; // TODO + area.getXOffset(); + int y = currentBPPosition; + + RendererContext context; + context = new RendererContext(MIME_TYPE); + context.setUserAgent(userAgent); + + SVGUserAgent ua = new SVGUserAgent(context.getUserAgent() + .getPixelUnitToMillimeter(), new AffineTransform()); + + GVTBuilder builder = new GVTBuilder(); + BridgeContext ctx = new BridgeContext(ua); + + GraphicsNode root; + try { + root = builder.build(ctx, doc); + } catch (Exception e) { + getLogger().error( + "svg graphic could not be built: " + e.getMessage(), e); + return; + } + float w = (float) ctx.getDocumentSize().getWidth() * 1000f; + float h = (float) ctx.getDocumentSize().getHeight() * 1000f; + + // correct integer roundoff + state.getGraph().translate(x / 1000, y / 1000); + + SVGSVGElement svg = ((SVGDocument) doc).getRootElement(); + AffineTransform at = ViewBox.getPreserveAspectRatioTransform(svg, + w / 1000f, h / 1000f); + AffineTransform inverse = null; + try { + inverse = at.createInverse(); + } catch (NoninvertibleTransformException e) { + getLogger().warn(e); + } + if (!at.isIdentity()) { + state.getGraph().transform(at); + } + + try { + root.paint(state.getGraph()); + } catch (Exception e) { + e.printStackTrace(); + } + + if (inverse != null && !inverse.isIdentity()) { + state.getGraph().transform(inverse); + } + // correct integer roundoff + // currentState.getCurrentGraphics().translate(-x / 1000f, y / 1000f - + // pageHeight); + state.getGraph().translate(-(x + 500) / 1000, + (y + 500) / 1000 - pageHeight); + } } diff --git a/src/java/org/apache/fop/render/awt/RendererState.java b/src/java/org/apache/fop/render/awt/RendererState.java new file mode 100644 index 000000000..3fdc8690a --- /dev/null +++ b/src/java/org/apache/fop/render/awt/RendererState.java @@ -0,0 +1,145 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.awt; + +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.geom.AffineTransform; + +import org.apache.fop.datatypes.ColorType; + +/** + * An interface for the classes which hold the state of the current graphics context. + */ +public interface RendererState { + + /** + * Push the current state onto the stack. + */ + public abstract void push(); + + /** + * Pop the state from the stack and restore the graphics context. + * @return the restored state, null if the stack is empty. + */ + public abstract Graphics2D pop(); + + /** + * Get the current stack level. + * + * @return the current stack level + */ + public abstract int getStackLevel(); + + /** + * Establishes a new foreground or fill color. + * @param col the color to apply (null skips this operation) + * @param fill true to set the fill color, false for the foreground color + * @param pdf only used by the PDFRenderer, is set to null. + * @return true if the new Color changes the current Color + */ + public abstract boolean updateColor(ColorType col, boolean fill, StringBuffer pdf); + + /** + * Set the current font name. Check if the font name will change and then + * set the new name. + * + * @param name the new font name + * @param size + * @param pdf + * @return true if the new Font changes the current Font + */ + public abstract boolean updateFont(String name, int size, StringBuffer pdf); + + /** + * Sets the current Stroke. The line width should be set with + * updateLineWidth() before calling this method + * + * @param style the constant for the style of the line as an int + * @return true if the new Stroke changes the current Stroke + */ + public abstract boolean updateStroke(float width, int style); + + /** + * Set the current paint. This checks if the paint will change and then sets + * the current paint. + * + * @param p the new paint + * @return true if the new paint changes the current paint + */ + public abstract boolean updatePaint(Paint p); + + /** + * Check if the clip will change the current state. A clip is assumed to be + * used in a situation where it will add to any clip in the current or + * parent states. A clip cannot be cleared, this can only be achieved by + * going to a parent level with the correct clip. If the clip is different + * then it may start a new state so that it can return to the previous clip. + * + * @param cl the clip shape to check + * @return true if the clip will change the current clip. + */ + // TODO test + public abstract boolean checkClip(Shape cl); + + /** + * Set the current clip. This either sets a new clip or sets the clip to the + * intersect of the old clip and the new clip. + * + * @param cl the new clip in the current state + */ + public abstract boolean updateClip(Shape cl); + + /** + * Check the current transform. The transform for the current state is the + * combination of all transforms in the current state. The parameter is + * compared against this current transform. + * + * @param tf the transform to check against + * @return true if the new transform is different from the current transform + */ + public abstract boolean checkTransform(AffineTransform tf); + + /** + * Overwrites the Transform in the Graphics2D context. Use transform() if you + * wish to compose with the current Affinetransform instead. + * @see java.awt.Graphics2D.setTransform(). + * @param tf the transform to concatonate to the current level transform + */ + public abstract void setTransform(AffineTransform tf); + + /** + * Composes an AffineTransform object with the Transform in this Graphics2D + * according to the rule last-specified-first-applied. + * @see java.awt.Graphics2D.transform(). + * + * @param tf the transform to concatonate to the current level transform + */ + public abstract void transform(AffineTransform tf); + + /** + * Get the current transform. This gets the combination of all transforms in + * the current state. + * + * @return the calculate combined transform for the current state + */ + public abstract AffineTransform getTransform(); + +} diff --git a/src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java b/src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java index 21218d3e5..340eeec1d 100644 --- a/src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java +++ b/src/java/org/apache/fop/render/awt/viewer/PreviewDialog.java @@ -1,6 +1,6 @@ /* - * Copyright 1999-2004 The Apache Software Foundation. - * + * Copyright 1999-2005 The Apache Software Foundation. + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -19,19 +19,6 @@ package org.apache.fop.render.awt.viewer; //Java -import javax.swing.BorderFactory; -import javax.swing.ImageIcon; -import javax.swing.JComboBox; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JToolBar; -import javax.swing.SwingUtilities; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; @@ -44,13 +31,26 @@ import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; -import java.awt.print.PrinterJob; import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JToolBar; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; -//FOP -import org.apache.fop.apps.Fop; -import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.Fop; import org.apache.fop.fo.Constants; import org.apache.fop.render.awt.AWTRenderer; @@ -123,6 +123,18 @@ public class PreviewDialog extends JFrame { reload(); } }; + Command debugAction = new Command(" Debug") { + //TODO use Translator + public void doit() { + debug(); + } + }; + + //set the system look&feel + try { + UIManager.setLookAndFeel( + UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { } setTitle("FOP: AWT-" + translator.getString("Title.Preview")); setDefaultCloseOperation(DISPOSE_ON_CLOSE); @@ -171,8 +183,9 @@ public class PreviewDialog extends JFrame { toolBar.add(lastPageAction); toolBar.addSeparator(); toolBar.add(new JLabel(translator.getString("Menu.Zoom"))); - toolBar.addSeparator(); toolBar.add(scale); + toolBar.addSeparator(); + toolBar.add(debugAction); getContentPane().add(toolBar, BorderLayout.NORTH); //Status bar JPanel statusBar = new JPanel(); @@ -378,6 +391,14 @@ public class PreviewDialog extends JFrame { } } + /** + * Allows a (yet) simple visual debug of the document. + */ + private void debug(){ + renderer.debug = !renderer.debug; + showPage(); + } + /** * This class is used to reload document in * a thread safe way. -- 2.39.5