]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Resurrected AWT/Java2D renderer
authorJeremias Maerki <jeremias@apache.org>
Tue, 8 Mar 2005 11:47:55 +0000 (11:47 +0000)
committerJeremias Maerki <jeremias@apache.org>
Tue, 8 Mar 2005 11:47:55 +0000 (11:47 +0000)
Submitted by: Renaud Richardet <renaud.richardet.at.gmail.com>

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

src/java/org/apache/fop/render/awt/AWTGraphicsState.java [new file with mode: 0644]
src/java/org/apache/fop/render/awt/AWTRenderer.java
src/java/org/apache/fop/render/awt/RendererState.java [new file with mode: 0644]
src/java/org/apache/fop/render/awt/viewer/PreviewDialog.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 (file)
index 0000000..7dae50b
--- /dev/null
@@ -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.
+ * <p>
+ * 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;
+    }
+}
index 3d87c43b8d3a3075c63fff301a8da57e61bbd534..ecc8e28cf2d104f8d2b7910523bf8aeea7586dd5 100644 (file)
@@ -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 <code>Java2DRenderer</code> class provides the abstract technical
+ * foundation for all rendering with the Java2D API. Renderers like
+ * <code>AWTRenderer</code> subclass it and provide the concrete output paths.
+ * <p>
+ * A lot of the logic is performed by <code>AbstractRenderer</code>. The
+ * class-variables <code>currentIPPosition</code> and
+ * <code>currentBPPosition</code> hold the position of the currently rendered
+ * area.
+ * <p>
+ * <code>AWTGraphicsState state</code> holds the <code>Graphics2D</code>,
+ * which is used along the whole rendering. <code>state</code> also acts as a
+ * stack (<code>state.push()</code> and <code>state.pop()</code>).
+ * <p>
+ * The rendering process is basically always the same:
+ * <p>
+ * <code>void renderXXXXX(Area area) {
+ *    //calculate the currentPosition
+ *    state.updateFont(name, size, null);
+ *    state.updateColor(ct, false, null);
+ *    state.getGraph.draw(new Shape(args));
+ * }</code>
+ *
  */
-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 <code>PageViewport</code> 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 <code>PageViewport</code> 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 <code>java.awt.image.BufferedImage</code> 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 <code>java.awt.image.BufferedImage</code> 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 <code>java.awt.image.BufferedImage</code> corresponding to the page
-     *  @throws FOPException in case of an out-of-range page number requested
-    */
+     * @return the <code>java.awt.image.BufferedImage</code> 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 (file)
index 0000000..3fdc869
--- /dev/null
@@ -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 <code>transform()</code> 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();
+
+}
index 21218d3e59936de5b7f6e77790254963960c4ad2..340eeec1db910f13ac00faa5ef25316d36615da6 100644 (file)
@@ -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
 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.