/*-- $Id$ --

 ============================================================================
				   The Apache Software License, Version 1.1
 ============================================================================

	Copyright (C) 1999 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of	source code must  retain the above copyright  notice,
	this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
	this list of conditions and the following disclaimer in the documentation
	and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
	include  the following	acknowledgment:  "This product includes  software
	developed  by the  Apache Software Foundation  (http://www.apache.org/)."
	Alternately, this  acknowledgment may  appear in the software itself,  if
	and wherever such third-party acknowledgments normally appear.

 4. The names "FOP" and  "Apache Software Foundation"  must not be used to
	endorse  or promote  products derived  from this  software without	prior
	written permission. For written permission, please contact
	apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
	"Apache" appear  in their name,  without prior written permission  of the
	Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR	PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT	OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)	HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,	WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR	OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software	consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software	Foundation and was	originally created by
 James Tauber <jtauber@jtauber.com>. For more  information on the Apache
 Software Foundation, please see <http://www.apache.org/>.

 */

package org.apache.fop.render.pdf;

// FOP
import org.apache.fop.messaging.MessageHandler;
import org.apache.fop.image.ImageArea;
import org.apache.fop.image.FopImage;
import org.apache.fop.apps.FOPException;
import org.apache.fop.fo.properties.*;
import org.apache.fop.layout.*;
import org.apache.fop.datatypes.*;
import org.apache.fop.svg.PathPoint;
import org.apache.fop.pdf.*;
import org.apache.fop.layout.*;
import org.apache.fop.image.*;

import org.w3c.dom.*;
import org.w3c.dom.svg.*;
import org.w3c.dom.css.*;
import org.w3c.dom.svg.SVGLength;

import org.apache.fop.dom.svg.*;
import org.apache.fop.dom.svg.SVGRectElementImpl;
import org.apache.fop.dom.svg.SVGTextElementImpl;
import org.apache.fop.dom.svg.SVGLineElementImpl;
import org.apache.fop.dom.svg.SVGArea;

// Java
import java.io.IOException;
import java.io.StringWriter;
import java.util.Enumeration;
import java.awt.Rectangle;
import java.util.Vector;
import java.util.Hashtable;

/**
 * Renderer that renders SVG to PDF
 */
public class SVGRenderer {

    /** the PDF Document being created */
    protected PDFDocument pdfDoc;

    protected FontState fontState;

    /** the /Resources object of the PDF document being created */
    //	protected PDFResources pdfResources;

    /** the current stream to add PDF commands to */
    StringWriter currentStream = new StringWriter();

    /** the current (internal) font name */
    protected String currentFontName;

    /** the current font size in millipoints */
    protected int currentFontSize;

    /** the current vertical position in millipoints from bottom */
    protected int currentYPosition = 0;

    /** the current horizontal position in millipoints from left */
    protected int currentXPosition = 0;

    /** the current colour for use in svg */
    private PDFColor currentColour = new PDFColor(0, 0, 0);

    /**
     * create the SVG renderer
     */
    public SVGRenderer(FontState fs, PDFDocument doc, String font,
                       int size, int xpos, int ypos) {
        pdfDoc = doc;
        currentFontName = font;
        currentFontSize = size;
        currentYPosition = ypos;
        currentXPosition = xpos;
        fontState = fs;
    }

    public String getString() {
        return currentStream.toString();
    }

    /**
     * Renders an SVG element in an SVG document.
     * This renders each of the child elements.
     */
    protected void renderSVG(SVGSVGElement svg, int x, int y) {
        NodeList nl = svg.getChildNodes();
        for (int count = 0; count < nl.getLength(); count++) {
            Node n = nl.item(count);
            if (n instanceof SVGElement) {
                renderElement((SVGElement) n, x, y);
            }
        }
    }

    public void renderGArea(SVGGElement area, int posx, int posy) {
        NodeList nl = area.getChildNodes();
        for (int count = 0; count < nl.getLength(); count++) {
            Node n = nl.item(count);
            if (n instanceof SVGElement) {
                renderElement((SVGElement) n, posx, posy);
            }
        }
    }

    /**
     * Handles the SVG switch element.
     * The switch determines which of its child elements should be rendered
     * according to the required extensions, required features and system language.
     */
    protected void handleSwitchElement(int posx, int posy,
                                       SVGSwitchElement ael) {
        SVGStringList relist = ael.getRequiredExtensions();
        SVGStringList rflist = ael.getRequiredFeatures();
        SVGStringList sllist = ael.getSystemLanguage();
        NodeList nl = ael.getChildNodes();
        choices:
        for (int count = 0; count < nl.getLength(); count++) {
            org.w3c.dom.Node n = nl.item(count);
            // only render the first child that has a valid
            // test data
            if (n instanceof GraphicElement) {
                GraphicElement graphic = (GraphicElement) n;
                SVGStringList grelist = graphic.getRequiredExtensions();
                // if null it evaluates to true
                if (grelist != null) {
                    if (grelist.getNumberOfItems() == 0) {
                        if ((relist != null) &&
                                relist.getNumberOfItems() != 0) {
                            continue choices;
                        }
                    }
                    for (int i = 0; i < grelist.getNumberOfItems(); i++) {
                        String str = (String) grelist.getItem(i);
                        if (relist == null) {
                            // use default extension set
                            // currently no extensions are supported
                            //							if(!(str.equals("http:// ??"))) {
                            continue choices;
                            //							}
                        } else {
                        }
                    }
                }
                SVGStringList grflist = graphic.getRequiredFeatures();
                if (grflist != null) {
                    if (grflist.getNumberOfItems() == 0) {
                        if ((rflist != null) &&
                                rflist.getNumberOfItems() != 0) {
                            continue choices;
                        }
                    }
                    for (int i = 0; i < grflist.getNumberOfItems(); i++) {
                        String str = (String) grflist.getItem(i);
                        if (rflist == null) {
                            // use default feature set
                            if (!(str.equals("org.w3c.svg.static") ||
                                    str.equals("org.w3c.dom.svg.all"))) {
                                continue choices;
                            }
                        } else {
                            boolean found = false;
                            for (int j = 0;
                                    j < rflist.getNumberOfItems(); j++) {
                                if (rflist.getItem(j).equals(str)) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found)
                                continue choices;
                        }
                    }
                }
                SVGStringList gsllist = graphic.getSystemLanguage();
                if (gsllist != null) {
                    if (gsllist.getNumberOfItems() == 0) {
                        if ((sllist != null) &&
                                sllist.getNumberOfItems() != 0) {
                            continue choices;
                        }
                    }
                    for (int i = 0; i < gsllist.getNumberOfItems(); i++) {
                        String str = (String) gsllist.getItem(i);
                        if (sllist == null) {
                            // use default feature set
                            if (!(str.equals("en"))) {
                                continue choices;
                            }
                        } else {
                            boolean found = false;
                            for (int j = 0;
                                    j < sllist.getNumberOfItems(); j++) {
                                if (sllist.getItem(j).equals(str)) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found)
                                continue choices;
                        }
                    }
                }
                renderElement((SVGElement) n, posx, posy);
                // only render the first valid one
                break;
            }
        }
    }

    /**
     * add a line to the current stream
     *
     * @param x1 the start x location in millipoints
     * @param y1 the start y location in millipoints
     * @param x2 the end x location in millipoints
     * @param y2 the end y location in millipoints
     * @param th the thickness in millipoints
     * @param r the red component
     * @param g the green component
     * @param b the blue component
     */
    protected void addLine(float x1, float y1, float x2, float y2,
                           DrawingInstruction di) {
        String str;
        PDFNumber pdfNumber = new PDFNumber();
        str = "" + pdfNumber.doubleOut(x1) + " " + pdfNumber.doubleOut(y1) + " m " + pdfNumber.doubleOut(x2) + " " + pdfNumber.doubleOut(y2) + " l";
        if (di != null && di.fill)
            currentStream.write(str + " f\n"); // ??
        currentStream.write(str + " S\n");
    }

    /**
     * Add an SVG circle
     * Uses bezier curves to approximate the shape of a circle.
     */
    protected void addCircle(float cx, float cy, float r,
                             DrawingInstruction di) {
        PDFNumber pdfNumber = new PDFNumber();
        String str;
        str = "" + pdfNumber.doubleOut(cx) + " " + pdfNumber.doubleOut((cy - r)) + " m\n" + "" +
              pdfNumber.doubleOut((cx + 21 * r / 40f)) + " " + pdfNumber.doubleOut((cy - r)) + " " +
              pdfNumber.doubleOut((cx + r)) + " " + pdfNumber.doubleOut((cy - 21 * r / 40f)) + " " +
              pdfNumber.doubleOut((cx + r)) + " " + pdfNumber.doubleOut(cy) + " c\n" + "" + pdfNumber.doubleOut((cx + r)) + " " +
              pdfNumber.doubleOut((cy + 21 * r / 40f)) + " " + pdfNumber.doubleOut((cx + 21 * r / 40f)) +
              " " + pdfNumber.doubleOut((cy + r)) + " " + pdfNumber.doubleOut(cx) + " " + pdfNumber.doubleOut((cy + r)) + " c\n" +
              "" + pdfNumber.doubleOut((cx - 21 * r / 40f)) + " " + pdfNumber.doubleOut((cy + r)) + " " +
              pdfNumber.doubleOut(cx - r) + " " + pdfNumber.doubleOut(cy + 21 * r / 40f) + " " +
              pdfNumber.doubleOut(cx - r) + " " + pdfNumber.doubleOut(cy) + " c\n" + "" + pdfNumber.doubleOut(cx - r) + " " +
              pdfNumber.doubleOut(cy - 21 * r / 40f) + " " + pdfNumber.doubleOut(cx - 21 * r / 40f) +
              " " + pdfNumber.doubleOut(cy - r) + " " + pdfNumber.doubleOut(cx) + " " + pdfNumber.doubleOut(cy - r) + " c\n";

        currentStream.write(str);
        doDrawing(di);
    }

    /**
     * Add an SVG ellips
     * Uses bezier curves to approximate the shape of an ellipse.
     */
    protected void addEllipse(float cx, float cy, float rx, float ry,
                              DrawingInstruction di) {
        PDFNumber pdfNumber = new PDFNumber();
        String str;
        str = "" + pdfNumber.doubleOut(cx) + " " + pdfNumber.doubleOut(cy - ry) + " m\n" + "" +
              pdfNumber.doubleOut(cx + 21 * rx / 40f) + " " + pdfNumber.doubleOut(cy - ry) + " " +
              pdfNumber.doubleOut(cx + rx) + " " + pdfNumber.doubleOut(cy - 21 * ry / 40f) + " " +
              pdfNumber.doubleOut(cx + rx) + " " + pdfNumber.doubleOut(cy) + " c\n" + "" + pdfNumber.doubleOut(cx + rx) + " " +
              pdfNumber.doubleOut(cy + 21 * ry / 40f) + " " + pdfNumber.doubleOut(cx + 21 * rx / 40f) +
              " " + pdfNumber.doubleOut(cy + ry) + " " + pdfNumber.doubleOut(cx) + " " + pdfNumber.doubleOut(cy + ry) +
              " c\n" + "" + pdfNumber.doubleOut(cx - 21 * rx / 40f) + " " + pdfNumber.doubleOut(cy + ry) +
              " " + pdfNumber.doubleOut(cx - rx) + " " + pdfNumber.doubleOut(cy + 21 * ry / 40f) + " " +
              pdfNumber.doubleOut(cx - rx) + " " + pdfNumber.doubleOut(cy) + " c\n" + "" + pdfNumber.doubleOut(cx - rx) + " " +
              pdfNumber.doubleOut(cy - 21 * ry / 40f) + " " + pdfNumber.doubleOut(cx - 21 * rx / 40f) +
              " " + pdfNumber.doubleOut(cy - ry) + " " + pdfNumber.doubleOut(cx) + " " + pdfNumber.doubleOut(cy - ry) + " c\n";
        currentStream.write(str);
        doDrawing(di);
    }

    /**
     * add an SVG rectangle to the current stream.
     * If there are curved edges then these are rendered using bezier curves.
     *
     * @param x the x position of left edge
     * @param y the y position of top edge
     * @param w the width
     * @param h the height
     * @param rx the x radius curved edge
     * @param ry the y radius curved edge
     */
    protected void addRect(float x, float y, float w, float h,
                           float rx, float ry, DrawingInstruction di) {
        PDFNumber pdfNumber = new PDFNumber();
        String str = "";
        if (rx == 0.0 && ry == 0.0) {
            str = "" + pdfNumber.doubleOut(x) + " " + pdfNumber.doubleOut(y) + " " + pdfNumber.doubleOut(w) + " " + pdfNumber.doubleOut(h) + " re\n";
        } else {
            if (ry == 0.0)
                ry = rx;
            if (rx > w / 2.0f)
                rx = w / 2.0f;
            if (ry > h / 2.0f)
                ry = h / 2.0f;
            str = "" + pdfNumber.doubleOut(x + rx) + " " + pdfNumber.doubleOut(y) + " m\n";
            str += "" + pdfNumber.doubleOut(x + w - rx) + " " + pdfNumber.doubleOut(y) + " l\n";
            str += "" + pdfNumber.doubleOut(x + w - 19 * rx / 40) + " " + pdfNumber.doubleOut(y) + " " +
                   pdfNumber.doubleOut(x + w) + " " + pdfNumber.doubleOut(y + 19 * ry / 40) + " " +
                   pdfNumber.doubleOut(x + w) + " " + pdfNumber.doubleOut(y + ry) + " c\n";
            str += "" + pdfNumber.doubleOut(x + w) + " " + pdfNumber.doubleOut(y + h - ry) + " l\n";
            str += "" + pdfNumber.doubleOut(x + w) + " " + pdfNumber.doubleOut(y + h - 19 * ry / 40) + " " +
                   pdfNumber.doubleOut(x + w - 19 * rx / 40) + " " + pdfNumber.doubleOut(y + h) + " " +
                   pdfNumber.doubleOut(x + w - rx) + " " + pdfNumber.doubleOut(y + h) + " c\n";
            str += "" + pdfNumber.doubleOut(x + rx) + " " + pdfNumber.doubleOut(y + h) + " l\n";
            str += "" + pdfNumber.doubleOut(x + 19 * rx / 40) + " " + pdfNumber.doubleOut(y + h) + " " + pdfNumber.doubleOut(x) +
                   " " + pdfNumber.doubleOut(y + h - 19 * ry / 40) + " " + pdfNumber.doubleOut(x) + " " +
                   pdfNumber.doubleOut(y + h - ry) + " c\n";
            str += "" + pdfNumber.doubleOut(x) + " " + pdfNumber.doubleOut(y + ry) + " l\n";
            str += "" + pdfNumber.doubleOut(x) + " " + pdfNumber.doubleOut(y + 19 * ry / 40) + " " +
                   pdfNumber.doubleOut(x + 19 * rx / 40) + " " + pdfNumber.doubleOut(y) + " " + pdfNumber.doubleOut(x + rx) +
                   " " + pdfNumber.doubleOut(y) + " c\n";
        }
        currentStream.write(str);
        doDrawing(di);
    }

    /**
     * Adds an SVG path to the current streem.
     * An SVG path is made up of a list of drawing instructions that are rendered
     * out in order.
     * Arcs don't work.
     */
    protected void addPath(Vector points, int posx, int posy,
                           DrawingInstruction di) {
        PDFNumber pdfNumber = new PDFNumber();
        SVGPathSegImpl pathmoveto = null;
        float lastx = 0;
        float lasty = 0;
        float lastmovex = 0;
        float lastmovey = 0;
        float[] cxs;
        float tempx;
        float tempy;
        float lastcx = 0;
        float lastcy = 0;
        for (Enumeration e = points.elements(); e.hasMoreElements();) {
            SVGPathSegImpl pc = (SVGPathSegImpl) e.nextElement();
            float[] vals = pc.getValues();
            switch (pc.getPathSegType()) {
                case SVGPathSeg.PATHSEG_MOVETO_ABS:
                    pathmoveto = pc;
                    lastx = vals[0];
                    lasty = vals[1];
                    currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " m\n");
                    lastcx = 0;
                    lastcy = 0;
                    lastmovex = lastx;
                    lastmovey = lasty;
                    break;
                case SVGPathSeg.PATHSEG_MOVETO_REL:
                    // the test cases seem to interprete this command differently
                    // it seems if there is an 'm' then the current path is closed
                    // then the point is move to a place relative to the point
                    // after doing the close
                    if (pathmoveto == null) {
                        lastx += vals[0];
                        lasty += vals[1];
                        pathmoveto = pc;
                        currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " m\n");
                    } else {
                        lastx += vals[0];
                        lasty += vals[1];
                        pathmoveto = pc;
                        currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " l\n");
                    }
                    lastmovex = lastx;
                    lastmovey = lasty;
                    lastcx = 0;
                    lastcy = 0;
                    break;
                case SVGPathSeg.PATHSEG_LINETO_ABS:
                    lastx = vals[0];
                    lasty = vals[1];
                    currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " l\n");
                    lastcx = 0;
                    lastcy = 0;
                    break;
                case SVGPathSeg.PATHSEG_LINETO_REL:
                    lastx += vals[0];
                    lasty += vals[1];
                    currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " l\n");
                    lastcx = 0;
                    lastcy = 0;
                    break;
                case SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:
                    lasty = vals[0];
                    currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " l\n");
                    lastcx = 0;
                    lastcy = 0;
                    break;
                case SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:
                    lasty += vals[0];
                    currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " l\n");
                    lastcx = 0;
                    lastcy = 0;
                    break;
                case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:
                    lastx = vals[0];
                    currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " l\n");
                    lastcx = 0;
                    lastcy = 0;
                    break;
                case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:
                    lastx += vals[0];
                    currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " l\n");
                    lastcx = 0;
                    lastcy = 0;
                    break;
                case SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:
                    lastx = vals[4];
                    lasty = vals[5];
                    lastcx = vals[2];
                    lastcy = vals[3];
                    currentStream.write(pdfNumber.doubleOut(vals[0]) + " " + pdfNumber.doubleOut(vals[1]) +
                                        " " + pdfNumber.doubleOut(vals[2]) + " " + pdfNumber.doubleOut(vals[3]) + " " +
                                        pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " c\n");
                    break;
                case SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:
                    currentStream.write(pdfNumber.doubleOut(vals[0] + lastx) + " " +
                                        pdfNumber.doubleOut(vals[1] + lasty) + " " +
                                        pdfNumber.doubleOut(vals[2] + lastx) + " " +
                                        pdfNumber.doubleOut(vals[3] + lasty) + " " +
                                        pdfNumber.doubleOut(vals[4] + lastx) + " " +
                                        pdfNumber.doubleOut(vals[5] + lasty) + " c\n");
                    lastcx = vals[2] + lastx;
                    lastcy = vals[3] + lasty;
                    lastx += vals[4];
                    lasty += vals[5];
                    break;
                case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
                    if (lastcx == 0) {
                        lastcx = lastx;
                    }
                    if (lastcy == 0) {
                        lastcy = lasty;
                    }
                    lastcx = lastx + (lastx - lastcx);
                    lastcy = lasty + (lasty - lastcy);
                    lastx = vals[2];
                    lasty = vals[3];
                    currentStream.write(pdfNumber.doubleOut(lastcx) + " " + pdfNumber.doubleOut(lastcy) + " " +
                                        pdfNumber.doubleOut(vals[0]) + " " + pdfNumber.doubleOut(vals[1]) + " " +
                                        pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " c\n");
                    lastcx = vals[0];
                    lastcy = vals[1];
                    break;
                case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
                    if (lastcx == 0) {
                        lastcx = lastx;
                    }
                    if (lastcy == 0) {
                        lastcy = lasty;
                    }
                    lastcx = lastx + (lastx - lastcx);
                    lastcy = lasty + (lasty - lastcy);
                    currentStream.write(pdfNumber.doubleOut(lastcx) + " " + pdfNumber.doubleOut(lastcy) + " " +
                                        pdfNumber.doubleOut(vals[0] + lastx) + " " +
                                        pdfNumber.doubleOut(vals[1] + lasty) + " " +
                                        pdfNumber.doubleOut(vals[2] + lastx) + " " +
                                        pdfNumber.doubleOut(vals[3] + lasty) + " c\n");
                    lastcx = (vals[0] + lastx);
                    lastcy = (vals[1] + lasty);
                    lastx += vals[2];
                    lasty += vals[3];
                    break;
                case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:
                    if (lastcx == 0) {
                        lastcx = lastx;
                    }
                    if (lastcy == 0) {
                        lastcy = lasty;
                    }
                    tempx = lastx;
                    tempy = lasty;
                    lastx = vals[2];
                    lasty = vals[3];
                    currentStream.write(pdfNumber.doubleOut(vals[0]) + " " + pdfNumber.doubleOut(vals[1]) + " " +
                                        pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " y\n");
                    cxs = calculateLastControl(tempx, tempy, lastx, lasty, -tempx + vals[0], -tempy + vals[1]);
                    lastcx = cxs[0];
                    lastcy = cxs[1];
                    break;
                case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:
                    if (lastcx == 0) {
                        lastcx = lastx;
                    }
                    if (lastcy == 0) {
                        lastcy = lasty;
                    }
                    currentStream.write(pdfNumber.doubleOut(vals[0] + lastx) + " " + pdfNumber.doubleOut(vals[1] + lasty) + " " +
                                        pdfNumber.doubleOut(vals[2] + lastx) + " " +
                                        pdfNumber.doubleOut(vals[3] + lasty) + " y\n");
                    cxs = calculateLastControl(lastx, lasty, lastx + vals[2], lasty + vals[3], vals[0], vals[1]);
                    lastcx = cxs[0];
                    lastcy = cxs[1];
                    lastx += vals[2];
                    lasty += vals[3];
                    break;
                case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
                    if (lastcx == 0) {
                        lastcx = lastx;
                    }
                    if (lastcy == 0) {
                        lastcy = lasty;
                    }
                    tempx = lastx;
                    tempy = lasty;
                    lastcx = lastx + (lastx - lastcx);
                    lastcy = lasty + (lasty - lastcy);
                    lastx = vals[0];
                    lasty = vals[1];
                    currentStream.write(pdfNumber.doubleOut(lastcx) + " " + pdfNumber.doubleOut(lastcy) + " " +
                                        pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " y\n");
                    cxs = calculateLastControl(tempx, tempy, lastx, lasty, -tempx + lastcx, -tempy + lastcy);
                    lastcx = cxs[0];
                    lastcy = cxs[1];
                    break;
                case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
                    if (lastcx == 0) {
                        lastcx = lastx;
                    }
                    if (lastcy == 0) {
                        lastcy = lasty;
                    }
                    lastcx = lastx + (lastx - lastcx);
                    lastcy = lasty + (lasty - lastcy);
                    currentStream.write(pdfNumber.doubleOut(lastcx) + " " + pdfNumber.doubleOut(lastcy) + " " +
                                        pdfNumber.doubleOut(vals[0] + lastx) + " " +
                                        pdfNumber.doubleOut(vals[1] + lasty) + " y\n");
                    cxs = calculateLastControl(lastx, lasty, lastx + vals[0], lasty + vals[1], -lastx + lastcx, -lasty + lastcy);
                    lastcx = cxs[0];
                    lastcy = cxs[1];
                    lastx += vals[0];
                    lasty += vals[1];
                    break;
                    // get angle between the two points
                    // then get angle of points to centre (ie. both points are on the
                    // apogee and perigee of the ellipse)
                    // then work out the direction from flags
                case SVGPathSeg.PATHSEG_ARC_ABS:
                    {
/*                        double rx = vals[0];
                        double ry = vals[1];
                        double theta = vals[2];
                        boolean largearcflag = (vals[3] == 1.0);
                        boolean sweepflag = (vals[4] == 1.0);

                        double angle = Math.atan((vals[6] - lasty) /
                                                 (vals[5] - lastx));
                        double relangle = Math.acos(rx /
                                                    Math.sqrt((vals[6] - lasty) *
                                                              (vals[6] - lasty) +
                                                              (vals[5] - lastx) * (vals[5] - lastx)));
                        double absangle = angle + relangle;
                        // change sign depending on flags
                        double contrx1;
                        double contry1;
                        double contrx2;
                        double contry2;
                        if (largearcflag) {
                            if (sweepflag) {
                                contrx1 = lastx - rx * Math.cos(absangle);
                                contry1 = lasty + rx * Math.sin(absangle);
                                contrx2 = vals[5] + ry * Math.cos(absangle);
                                contry2 = vals[6] + ry * Math.sin(absangle);
                            } else {
                                contrx1 = lastx - rx * Math.cos(absangle);
                                contry1 = lasty - rx * Math.sin(absangle);
                                contrx2 = vals[5] + ry * Math.cos(absangle);
                                contry2 = vals[6] - ry * Math.sin(absangle);
                            }
                        } else {
                            if (sweepflag) {
                                contrx1 = lastx + rx * Math.cos(absangle);
                                contry1 = lasty + rx * Math.sin(absangle);
                                contrx2 = contrx1;
                                contry2 = contry1;
                            } else {
                                contrx1 = lastx + ry * Math.cos(absangle);
                                contry1 = lasty - ry * Math.sin(absangle);
                                contrx2 = contrx1;
                                contry2 = contry1;
                            }
                        }

                        double cx = lastx;
                        double cy = lasty;
                        currentStream.write(pdfNumber.doubleOut(contrx1) + " " + pdfNumber.doubleOut(contry1) +
                                            " " + pdfNumber.doubleOut(contrx2) + " " + pdfNumber.doubleOut(contry2) + " " +
                                            pdfNumber.doubleOut(vals[5]) + " " + pdfNumber.doubleOut(vals[6]) + " c\n");
                        lastcx = 0; //??
                        lastcy = 0; //??
                        lastx = vals[5];
                        lasty = vals[6];*/
                    }
                    break;
                case SVGPathSeg.PATHSEG_ARC_REL:
                    {
/*                        double rx = vals[0];
                        double ry = vals[1];
                        double theta = vals[2];
                        boolean largearcflag = (vals[3] == 1.0);
                        boolean sweepflag = (vals[4] == 1.0);

                        double angle = Math.atan(vals[6] / vals[5]);
                        double relangle = Math.atan(ry / rx);
                        //	    				System.out.println((theta * Math.PI / 180f) + ":" + relangle + ":" + largearcflag + ":" + sweepflag);
                        double absangle = (theta * Math.PI / 180f);//angle + relangle;
                        // change sign depending on flags
                        double contrx1;
                        double contry1;
                        double contrx2;
                        double contry2;
                        if (largearcflag) {
                            // in a large arc we need to do at least 2 and a bit
                            // segments or curves.
                            if (sweepflag) {
                                contrx1 = lastx + rx * Math.cos(absangle);
                                contry1 = lasty + rx * Math.sin(absangle);
                                contrx2 = lastx + vals[5] +
                                          ry * Math.cos(absangle);
                                contry2 = lasty + vals[6] -
                                          ry * Math.sin(absangle);
                            } else {
                                contrx1 = lastx + rx * Math.sin(absangle);
                                contry1 = lasty + rx * Math.cos(absangle);
                                contrx2 = lastx + vals[5] +
                                          ry * Math.cos(absangle);
                                contry2 = lasty + vals[6] +
                                          ry * Math.sin(absangle);
                            }
                        } else {
                            // only need at most two segments
                            if (sweepflag) {
                                contrx1 = lastx + rx * Math.cos(absangle);
                                contry1 = lasty - rx * Math.sin(absangle);
                                contrx2 = contrx1;
                                contry2 = contry1;
                            } else {
                                contrx1 = lastx - ry * Math.cos(absangle);
                                contry1 = lasty + ry * Math.sin(absangle);
                                contrx2 = contrx1;
                                contry2 = contry1;
                            }
                        }
                        //System.out.println(contrx1 + ":" + contry1 + ":" + contrx2 + ":" + contry2);

                        double cx = lastx;
                        double cy = lasty;
                        currentStream.write(pdfNumber.doubleOut(contrx1) + " " + pdfNumber.doubleOut(contry1) +
                                            " " + pdfNumber.doubleOut(contrx2) + " " + pdfNumber.doubleOut(contry2) + " " +
                                            pdfNumber.doubleOut(vals[5] + lastx) + " " +
                                            pdfNumber.doubleOut(vals[6] + lasty) + " c\n");

                        lastcx = 0; //??
                        lastcy = 0; //??
                        lastx += vals[5];
                        lasty += vals[6];*/
                    }
                    break;
                case SVGPathSeg.PATHSEG_CLOSEPATH:
                    currentStream.write("h\n");
                    pathmoveto = null;
                    lastx = lastmovex;
                    lasty = lastmovey;
                    break;
            }
        }
        doDrawing(di);
    }

    /**
     * Calculate the last control point for a bezier curve.
     * This is used to find the last control point for a curve where
     * only the first control point is specified.
     * The control point is a reflection of the first control point
     * which results in an even smooth curve. The curve is symmetrical.
     */
    protected float[] calculateLastControl(float x1, float y1, float x2, float y2, float relx, float rely)
    {
        float vals[] = new float[2];
        relx = -relx;
        rely = -rely;
        float dist = (float)Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
        float costheta = (float)(x2 - x1) / dist;
        float sinetheta = (float)(y2 - y1) / dist;
        float temp = relx;
        relx = relx * costheta + rely * sinetheta;
        rely = -temp * sinetheta + rely * costheta;
        relx = -relx;
        temp = relx;
        relx = relx * costheta - rely * sinetheta;
        rely = temp * sinetheta + rely * costheta;
        vals[0] = x2 - relx;
        vals[1] = y2 - rely;
        return vals;
    }

    /**
     * Adds an SVG polyline or polygon.
     * A polygon is merely a closed polyline.
     * This is made up from a set of points that straight lines are drawn between.
     */
    protected void addPolyline(Vector points, DrawingInstruction di,
                               boolean close) {
        PDFNumber pdfNumber = new PDFNumber();
        PathPoint pc;
        float lastx = 0;
        float lasty = 0;
        Enumeration e = points.elements();
        if (e.hasMoreElements()) {
            pc = (PathPoint) e.nextElement();
            lastx = pc.x;
            lasty = pc.y;
            currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " m\n");
        }
        while (e.hasMoreElements()) {
            pc = (PathPoint) e.nextElement();
            lastx = pc.x;
            lasty = pc.y;
            currentStream.write(pdfNumber.doubleOut(lastx) + " " + pdfNumber.doubleOut(lasty) + " l\n");
        }
        if (close)
            currentStream.write("h\n");
        doDrawing(di);
    }

    /**
     * Writes the drawing instruction out to the current stream
     * depending on what type of drawing is required.
     */
    protected void doDrawing(DrawingInstruction di) {
        if (di == null) {
            currentStream.write("S\n");
        } else {
            if (di.fill) {
                if (di.stroke) {
                    if (!di.nonzero)
                        currentStream.write("B*\n");
                    else
                        currentStream.write("B\n");
                } else {
                    if (!di.nonzero)
                        currentStream.write("f*\n");
                    else
                        currentStream.write("f\n");
                }
            } else {
                //				if(di.stroke)
                currentStream.write("S\n");
            }
        }
    }

    /**
     * Renders an svg image to the current stream.
     * This uses the FopImageFactory to load the image and then renders it.
     */
    public void renderImage(String href, float x, float y, float width,
                            float height) {
        try {
            if (href.indexOf(":") == -1) {
                href = "file:" + href;
            }
            FopImage img = FopImageFactory.Make(href);
            PDFNumber pdfNumber = new PDFNumber();
            if (img instanceof SVGImage) {
                SVGSVGElement svg =
                  ((SVGImage) img).getSVGDocument().getRootElement();
                currentStream.write("q\n" + pdfNumber.doubleOut(width /
                                    svg.getWidth().getBaseVal().getValue()) + " 0 0 " +
                                    pdfNumber.doubleOut(height /
                                    svg.getHeight().getBaseVal().getValue()) + " 0 0 cm\n");
                renderSVG(svg, (int) x * 1000, (int) y * 1000);
                currentStream.write("Q\n");
                //				renderSVG(svg);
            } else if (img != null) {
                int xObjectNum = this.pdfDoc.addImage(img);
                currentStream.write("q\n1 0 0 -1 0 " +
                                    pdfNumber.doubleOut(2 * y + height) + " cm\n" + pdfNumber.doubleOut(width) + " 0 0 " +
                                    pdfNumber.doubleOut(height) + " " + pdfNumber.doubleOut(x) + " " + pdfNumber.doubleOut(y) + " cm\n" + "/Im" +
                                    xObjectNum + " Do\nQ\n");
                //				img.close();
            }
        } catch (Exception e) {
            MessageHandler.errorln("could not add image to SVG: " + href);
        }
    }

    /**
     * A symbol has a viewbox and preserve aspect ratio.
     */
    protected void renderSymbol(SVGSymbolElement symbol, int x, int y) {
        NodeList nl = symbol.getChildNodes();
        for (int count = 0; count < nl.getLength(); count++) {
            Node n = nl.item(count);
            if (n instanceof SVGElement) {
                renderElement((SVGElement) n, x, y);
            }
        }
    }

    /**
     * Handles the construction of an SVG gradient.
     * This gets the gradient element and creates the pdf info
     * in the pdf document.
     * The type of gradient is determined by what class the gradient element is.
     */
    protected void handleGradient(String sp, DrawingInstruction di,
                                  boolean fill, SVGElement area) {
        // should be a url to a gradient
        String url = (String) sp;
        if (url.startsWith("url(")) {
            String address;
            int b1 = url.indexOf("(");
            int b2 = url.indexOf(")");
            address = url.substring(b1 + 1, b2);
            SVGElement gi = null;
            gi = locateDef(address, area);
            if (gi instanceof SVGLinearGradientElement) {
                SVGLinearGradientElement linear =
                  (SVGLinearGradientElement) gi;
                handleLinearGradient(linear, di, fill, area);
            } else if (gi instanceof SVGRadialGradientElement) {
                SVGRadialGradientElement radial =
                  (SVGRadialGradientElement) gi;
                handleRadialGradient(radial, di, fill, area);
            } else if (gi instanceof SVGPatternElement) {
                SVGPatternElement pattern = (SVGPatternElement) gi;
                handlePattern(pattern, di, fill, area);
            } else {
                MessageHandler.errorln("WARNING Invalid fill reference :" +
                                   gi + ":" + address);
            }
        }
    }

    protected void handlePattern(SVGPatternElement pattern,
                                 DrawingInstruction di, boolean fill, SVGElement area) {
        SVGAnimatedLength x, y, width, height;
        short pattUnits = SVGUnitTypes.SVG_UNIT_TYPE_UNKNOWN;
        NodeList stops = null;
        x = pattern.getX();
        y = pattern.getY();
        width = pattern.getWidth();
        height = pattern.getHeight();
        NodeList nl = pattern.getChildNodes();
        SVGPatternElement ref = (SVGPatternElement) locateDef(
                                  pattern.getHref().getBaseVal(), pattern);
        while (ref != null) {
            if (x == null) {
                x = ref.getX();
                pattUnits = ref.getPatternUnits().getBaseVal();
            }
            if (y == null) {
                y = ref.getY();
            }
            if (width == null) {
                width = ref.getWidth();
            }
            if (height == null) {
                height = ref.getHeight();
            }
            if (nl.getLength() == 0) {
                nl = ref.getChildNodes();
            }
            ref = (SVGPatternElement) locateDef(
                    ref.getHref().getBaseVal(), ref);
        }
        if (x == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 0);
            x = new SVGAnimatedLengthImpl(length);
        }
        if (y == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 0);
            y = new SVGAnimatedLengthImpl(length);
        }
        if (width == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 1);
            width = new SVGAnimatedLengthImpl(length);
        }
        if (height == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 1);
            height = new SVGAnimatedLengthImpl(length);
        }

        StringWriter realStream = currentStream;
        currentStream = new StringWriter();

        currentStream.write("q\n");
        // this makes the pattern the right way up, since it is outside the original
        // transform around the whole svg document
        currentStream.write("1 0 0 -1 0 " +
                            height.getBaseVal().getValue() + " cm\n");
        for (int count = 0; count < nl.getLength(); count++) {
            Node n = nl.item(count);
            if (n instanceof SVGElement) {
                renderElement((SVGElement) n, 0, 0);
            }
        }
        currentStream.write("Q\n");

        double xval = x.getBaseVal().getValue() + currentXPosition / 1000f;
        double yval = -y.getBaseVal().getValue() + currentYPosition / 1000f;
        if (area instanceof GraphicElement) {
            SVGRect bbox = ((GraphicElement) area).getBBox();
            if (bbox != null) {
                //		        xval += bbox.getX();
                //		        yval -= bbox.getY();
            }
        }
        double widthval = width.getBaseVal().getValue();
        double heightval = height.getBaseVal().getValue();
        Vector bbox = new Vector();
        bbox.addElement(new Double(0));
        bbox.addElement(new Double(0));
        bbox.addElement(new Double(widthval));
        bbox.addElement(new Double(heightval));
        Vector translate = new Vector();
        // combine with pattern transform
        translate.addElement(new Double(1));
        translate.addElement(new Double(0));
        translate.addElement(new Double(0));
        translate.addElement(new Double(1));
        translate.addElement(new Double(xval));
        translate.addElement(new Double(yval));
        // need to handle PDFResources
        PDFPattern myPat =
          this.pdfDoc.makePattern(1, null, 1, 1, bbox, widthval,
                                  heightval, translate, null, currentStream.getBuffer());

        currentStream = realStream;
        currentStream.write(myPat.getColorSpaceOut(fill));
        if (fill)
            di.fill = true;
        else
            di.stroke = true;
    }

    protected void handleLinearGradient(
      SVGLinearGradientElement linear, DrawingInstruction di,
      boolean fill, SVGElement area) {
        // first get all the gradient values
        // if values not present follow the href
        // the gradient units will be where the vals are specified
        // the spread method will be where there are stop elements
        SVGAnimatedLength ax1, ax2, ay1, ay2;
        short spread = SVGGradientElement.SVG_SPREADMETHOD_UNKNOWN;
        short gradUnits = SVGUnitTypes.SVG_UNIT_TYPE_UNKNOWN;
        NodeList stops = null;
        ax1 = linear.getX1();
        ax2 = linear.getX2();
        ay1 = linear.getY1();
        ay2 = linear.getY2();
        stops = linear.getChildNodes();
        SVGLinearGradientElement ref = (SVGLinearGradientElement) locateDef(
                                         linear.getHref().getBaseVal(), linear);
        while (ref != null) {
            if (ax1 == null) {
                ax1 = ref.getX1();
                gradUnits = ref.getGradientUnits().getBaseVal();
            }
            if (ax2 == null) {
                ax2 = ref.getX2();
            }
            if (ay1 == null) {
                ay1 = ref.getY1();
            }
            if (ay2 == null) {
                ay2 = ref.getY2();
            }
            if (stops.getLength() == 0) {
                stops = ref.getChildNodes();
            }
            ref = (SVGLinearGradientElement) locateDef(
                    ref.getHref().getBaseVal(), ref);
        }
        if (ax1 == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 0);
            ax1 = new SVGAnimatedLengthImpl(length);
        }
        if (ax2 == null) {
            // if x2 is not specified then it should be 100%
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 1);
            ax2 = new SVGAnimatedLengthImpl(length);
        }
        if (ay1 == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 0);
            ay1 = new SVGAnimatedLengthImpl(length);
        }
        if (ay2 == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 0);
            ay2 = new SVGAnimatedLengthImpl(length);
        }
        SVGAnimatedTransformList an = linear.getGradientTransform();
        SVGMatrix transform = null;
        if(an != null)
            transform = an.getBaseVal().consolidate().getMatrix();
        Vector theCoords = null;
        if (gradUnits == SVGUnitTypes.SVG_UNIT_TYPE_UNKNOWN)
            gradUnits = linear.getGradientUnits().getBaseVal();
        // spread: pad (normal), reflect, repeat
        spread = linear.getSpreadMethod().getBaseVal();
        if (gradUnits == SVGUnitTypes.SVG_UNIT_TYPE_USERSPACEONUSE) {
            if (area instanceof SVGTransformable) {
                SVGTransformable tf = (SVGTransformable) area;
                double x1, y1, x2, y2;
                x1 = ax1.getBaseVal().getValue();
                y1 = -ay1.getBaseVal().getValue();
                x2 = ax2.getBaseVal().getValue();
                y2 = -ay2.getBaseVal().getValue();
                SVGMatrix matrix = tf.getScreenCTM();
                if(transform != null)
                    matrix = matrix.multiply(transform);
                double oldx = x1;
                x1 = matrix.getA() * x1 + matrix.getC() * y1 +
                     matrix.getE();
                y1 = matrix.getB() * oldx + matrix.getD() * y1 -
                     matrix.getF();
                oldx = x2;
                x2 = matrix.getA() * x2 + matrix.getC() * y2 +
                     matrix.getE();
                y2 = matrix.getB() * oldx + matrix.getD() * y2 -
                     matrix.getF();
                theCoords = new Vector();
                if (spread == SVGGradientElement.SVG_SPREADMETHOD_REFLECT) {
                } else if (spread ==
                    SVGGradientElement.SVG_SPREADMETHOD_REFLECT) {
                } else {
                    theCoords.addElement(
                      new Double(currentXPosition / 1000f + x1));
                    theCoords.addElement(
                      new Double(currentYPosition / 1000f + y1));
                    theCoords.addElement(
                      new Double(currentXPosition / 1000f + x2));
                    theCoords.addElement(
                      new Double(currentYPosition / 1000f + y2));
                }
            }
        } else if (area instanceof GraphicElement) {
            SVGRect rect = ((GraphicElement) area).getBBox();
            if (rect != null) {
                theCoords = new Vector();
                SVGLength val;
                val = ax1.getBaseVal();
                if (val.getUnitType() ==
                        SVGLength.SVG_LENGTHTYPE_PERCENTAGE || gradUnits ==
                        SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                    theCoords.addElement(
                      new Double(currentXPosition / 1000f +
                                 rect.getX() +
                                 val.getValue() * rect.getWidth()));
                } else {
                    theCoords.addElement(
                      new Double(currentXPosition / 1000f +
                                 val.getValue()));
                }
                val = ay1.getBaseVal();
                if (val.getUnitType() ==
                        SVGLength.SVG_LENGTHTYPE_PERCENTAGE || gradUnits ==
                        SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                    theCoords.addElement(
                      new Double(currentYPosition / 1000f -
                                 rect.getY() -
                                 val.getValue() * rect.getHeight()));
                } else {
                    theCoords.addElement(
                      new Double(currentYPosition / 1000f -
                                 val.getValue()));
                }
                val = ax2.getBaseVal();
                if (val.getUnitType() ==
                        SVGLength.SVG_LENGTHTYPE_PERCENTAGE || gradUnits ==
                        SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                    theCoords.addElement(
                      new Double(currentXPosition / 1000f +
                                 rect.getX() +
                                 val.getValue() * rect.getWidth()));
                } else {
                    theCoords.addElement(
                      new Double(currentXPosition / 1000f +
                                 val.getValue()));
                }
                val = ay2.getBaseVal();
                if (val.getUnitType() ==
                        SVGLength.SVG_LENGTHTYPE_PERCENTAGE || gradUnits ==
                        SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                    theCoords.addElement(
                      new Double(currentYPosition / 1000f -
                                 rect.getY() -
                                 val.getValue() * rect.getHeight()));
                } else {
                    theCoords.addElement(
                      new Double(currentYPosition / 1000f -
                                 val.getValue()));
                }
            }
        }
        if (theCoords == null) {
            theCoords = new Vector();
            theCoords.addElement( new Double(currentXPosition / 1000f +
                                             ax1.getBaseVal().getValue()));
            theCoords.addElement( new Double(currentYPosition / 1000f -
                                             ay1.getBaseVal().getValue()));
            theCoords.addElement( new Double(currentXPosition / 1000f +
                                             ax2.getBaseVal().getValue()));
            theCoords.addElement( new Double(currentYPosition / 1000f -
                                             ay2.getBaseVal().getValue()));
        }

        Vector theExtend = new Vector();
        theExtend.addElement(new Boolean(true));
        theExtend.addElement(new Boolean(true));

        Vector theDomain = new Vector();
        theDomain.addElement(new Double(0));
        theDomain.addElement(new Double(1));

        Vector theEncode = new Vector();
        theEncode.addElement(new Double(0));
        theEncode.addElement(new Double(1));
        theEncode.addElement(new Double(0));
        theEncode.addElement(new Double(1));

        Vector theBounds = new Vector();
        theBounds.addElement(new Double(0));
        theBounds.addElement(new Double(1));

        Vector theFunctions = new Vector();

        NodeList nl = stops;
        Vector someColors = new Vector();
        float lastoffset = 0;
        Vector lastVector = null;
        SVGStopElementImpl stop;
        if (nl.getLength() == 0) {
            // the color should be "none"
            if (fill)
                di.fill = false;
            else
                di.stroke = false;
            return;
        } else if (nl.getLength() == 1) {
            stop = (SVGStopElementImpl) nl.item(0);
            CSSValue cv = stop.getPresentationAttribute("stop-color");
            if (cv == null) {
                // maybe using color
                cv = stop.getPresentationAttribute("color");
            }
            if (cv == null) {
                // problems
                MessageHandler.errorln("no stop-color or color in stop element");
                return;
            }
            PDFColor color = new PDFColor(0, 0, 0);
            if (cv != null &&
                    cv.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) cv).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_RGBCOLOR) {
                    RGBColor col =
                      ((CSSPrimitiveValue) cv).getRGBColorValue();
                    CSSPrimitiveValue val;
                    val = col.getRed();
                    float red = val.getFloatValue(
                                  CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getGreen();
                    float green = val.getFloatValue(
                                    CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getBlue();
                    float blue = val.getFloatValue(
                                   CSSPrimitiveValue.CSS_NUMBER);
                    color = new PDFColor(red, green, blue);
                }
            }
            currentStream.write(color.getColorSpaceOut(fill));
            if (fill)
                di.fill = true;
            else
                di.stroke = true;
            return;
        }
        for (int count = 0; count < nl.getLength(); count++) {
            stop = (SVGStopElementImpl) nl.item(count);
            CSSValue cv = stop.getPresentationAttribute("stop-color");
            if (cv == null) {
                // maybe using color
                cv = stop.getPresentationAttribute("color");
            }
            if (cv == null) {
                // problems
                MessageHandler.errorln("no stop-color or color in stop element");
                continue;
            }
            PDFColor color = new PDFColor(0, 0, 0);
            if (cv != null &&
                    cv.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) cv).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_RGBCOLOR) {
                    RGBColor col =
                      ((CSSPrimitiveValue) cv).getRGBColorValue();
                    CSSPrimitiveValue val;
                    val = col.getRed();
                    float red = val.getFloatValue(
                                  CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getGreen();
                    float green = val.getFloatValue(
                                    CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getBlue();
                    float blue = val.getFloatValue(
                                   CSSPrimitiveValue.CSS_NUMBER);
                    color = new PDFColor(red, green, blue);
                    currentColour = color;
                }
            }
            float offset = stop.getOffset().getBaseVal();
            Vector colVector = color.getVector();
            // create bounds from last to offset
            if (lastVector != null) {
                Vector theCzero = lastVector;
                Vector theCone = colVector;
                PDFFunction myfunc =
                  this.pdfDoc.makeFunction(2, theDomain, null,
                                           theCzero, theCone, 1.0);
                theFunctions.addElement(myfunc);
            }
            lastoffset = offset;
            lastVector = colVector;
            someColors.addElement(color);
        }
        ColorSpace aColorSpace = new ColorSpace(ColorSpace.DEVICE_RGB);
        /*				PDFFunction myfunky = this.pdfDoc.makeFunction(3,
         			theDomain, null,
         			theFunctions, null,
         			theEncode);
         		PDFShading myShad = null;
         		myShad = this.pdfDoc.makeShading(
         			2, aColorSpace,
         			null, null, false,
         			theCoords, null, myfunky,theExtend);
         		PDFPattern myPat = this.pdfDoc.makePattern(2, myShad, null, null, null);*/
        PDFPattern myPat = this.pdfDoc.createGradient(false, aColorSpace,
                           someColors, null, theCoords);
        currentStream.write(myPat.getColorSpaceOut(fill));
        if (fill)
            di.fill = true;
        else
            di.stroke = true;
    }

    protected void handleRadialGradient(
      SVGRadialGradientElement radial, DrawingInstruction di,
      boolean fill, SVGElement area) {
        // first get all the gradient values
        // if values not present follow the href
        // the gradient units will be where the vals are specified
        SVGAnimatedLength acx, acy, ar, afx, afy;
        short gradUnits = radial.getGradientUnits().getBaseVal();
        NodeList stops = null;
        acx = radial.getCx();
        acy = radial.getCy();
        ar = radial.getR();
        afx = radial.getFx();
        afy = radial.getFy();
        stops = radial.getChildNodes();
        SVGRadialGradientElement ref = (SVGRadialGradientElement) locateDef(
                                         radial.getHref().getBaseVal(), radial);
        while (ref != null) {
            if (acx == null) {
                acx = ref.getCx();
                gradUnits = ref.getGradientUnits().getBaseVal();
            }
            if (acy == null) {
                acy = ref.getCy();
            }
            if (ar == null) {
                ar = ref.getR();
            }
            if (afx == null) {
                afx = ref.getFx();
            }
            if (afy == null) {
                afy = ref.getFy();
            }
            if (stops.getLength() == 0) {
                stops = ref.getChildNodes();
            }
            ref = (SVGRadialGradientElement) locateDef(
                    ref.getHref().getBaseVal(), ref);
        }
        if (acx == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 0.5f);
            acx = new SVGAnimatedLengthImpl(length);
        }
        if (acy == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 0.5f);
            acy = new SVGAnimatedLengthImpl(length);
        }
        if (ar == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 1);
            ar = new SVGAnimatedLengthImpl(length);
        }
        if (afx == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 0.5f);
            afx = new SVGAnimatedLengthImpl(length);
        }
        if (afy == null) {
            SVGLength length = new SVGLengthImpl();
            length.newValueSpecifiedUnits(
              SVGLength.SVG_LENGTHTYPE_PERCENTAGE, 0.5f);
            afy = new SVGAnimatedLengthImpl(length);
        }
        ColorSpace aColorSpace = new ColorSpace(ColorSpace.DEVICE_RGB);
        org.w3c.dom.NodeList nl = stops;
        SVGStopElementImpl stop;
        if (nl.getLength() == 0) {
            // the color should be "none"
            if (fill)
                di.fill = false;
            else
                di.stroke = false;
            return;
        } else if (nl.getLength() == 1) {
            stop = (SVGStopElementImpl) nl.item(0);
            CSSValue cv = stop.getPresentationAttribute("stop-color");
            if (cv == null) {
                // maybe using color
                cv = stop.getPresentationAttribute("color");
            }
            if (cv == null) {
                // problems
                MessageHandler.errorln("no stop-color or color in stop element");
                return;
            }
            PDFColor color = new PDFColor(0, 0, 0);
            if (cv != null &&
                    cv.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) cv).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_RGBCOLOR) {
                    RGBColor col =
                      ((CSSPrimitiveValue) cv).getRGBColorValue();
                    CSSPrimitiveValue val;
                    val = col.getRed();
                    float red = val.getFloatValue(
                                  CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getGreen();
                    float green = val.getFloatValue(
                                    CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getBlue();
                    float blue = val.getFloatValue(
                                   CSSPrimitiveValue.CSS_NUMBER);
                    color = new PDFColor(red, green, blue);
                }
            }
            currentStream.write(color.getColorSpaceOut(fill));
            if (fill)
                di.fill = true;
            else
                di.stroke = true;
            return;
        }
        Hashtable table = null;
        Vector someColors = new Vector();
        Vector theCoords = null;
        Vector theBounds = new Vector();
        // the coords should be relative to the current object
        // check value types, eg. %
        if (gradUnits == SVGUnitTypes.SVG_UNIT_TYPE_USERSPACEONUSE) {
            if (area instanceof SVGTransformable) {
                SVGTransformable tf = (SVGTransformable) area;
                double x1, y1, x2, y2;
                x1 = acx.getBaseVal().getValue();
                y1 = -acy.getBaseVal().getValue();
                x2 = afx.getBaseVal().getValue();
                y2 = -afy.getBaseVal().getValue();
                SVGMatrix matrix = tf.getScreenCTM();
                double oldx = x1;
                x1 = matrix.getA() * x1 + matrix.getB() * y1 +
                     matrix.getE();
                y1 = matrix.getC() * oldx + matrix.getD() * y1 +
                     matrix.getF();
                oldx = x2;
                x2 = matrix.getA() * x2 + matrix.getB() * y2 +
                     matrix.getE();
                y2 = matrix.getC() * oldx + matrix.getD() * y2 +
                     matrix.getF();
                theCoords = new Vector();
                //				if(spread == SVGGradientElement.SVG_SPREADMETHOD_REFLECT) {
                //				} else if(spread== SVGGradientElement.SVG_SPREADMETHOD_REFLECT) {
                //				} else {
                theCoords.addElement(
                  new Double(currentXPosition / 1000f + x1));
                // the y val needs to be adjust by 2 * R * rotation
                // depending on if this value is from an x or y coord
                // before transformation
                theCoords.addElement(
                  new Double(currentYPosition / 1000f - y1 +
                             (matrix.getC() - matrix.getD()) * 2 *
                             ar.getBaseVal().getValue()));
                theCoords.addElement(new Double(0));
                theCoords.addElement(
                  new Double(currentXPosition / 1000f + x2));
                theCoords.addElement(
                  new Double(currentYPosition / 1000f - y2 +
                             (matrix.getC() - matrix.getD()) * 2 *
                             ar.getBaseVal().getValue()));
                theCoords.addElement(
                  new Double(ar.getBaseVal().getValue()));
                //				}
            }
        } else if (gradUnits ==
            SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX &&
            area instanceof GraphicElement) {
            SVGRect rect = ((GraphicElement) area).getBBox();
            if (rect != null) {
                theCoords = new Vector();
                SVGLength val;
                val = acx.getBaseVal();
                if (val.getUnitType() ==
                        SVGLength.SVG_LENGTHTYPE_PERCENTAGE || gradUnits ==
                        SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                    theCoords.addElement(
                      new Double(currentXPosition / 1000f +
                                 rect.getX() +
                                 val.getValue() * rect.getWidth()));
                } else {
                    theCoords.addElement(
                      new Double(currentXPosition / 1000f +
                                 val.getValue()));
                }
                val = acy.getBaseVal();
                if (val.getUnitType() ==
                        SVGLength.SVG_LENGTHTYPE_PERCENTAGE || gradUnits ==
                        SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                    theCoords.addElement(
                      new Double(currentYPosition / 1000f -
                                 rect.getY() -
                                 val.getValue() * rect.getHeight()));
                } else {
                    theCoords.addElement(
                      new Double(currentYPosition / 1000f -
                                 val.getValue()));
                }
                theCoords.addElement(new Double(0));
                val = afx.getBaseVal();
                if (val.getUnitType() ==
                        SVGLength.SVG_LENGTHTYPE_PERCENTAGE || gradUnits ==
                        SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                    theCoords.addElement(
                      new Double(currentXPosition / 1000f +
                                 rect.getX() +
                                 val.getValue() * rect.getWidth()));
                } else {
                    theCoords.addElement(
                      new Double(currentXPosition / 1000f +
                                 val.getValue()));
                }
                val = afy.getBaseVal();
                if (val.getUnitType() ==
                        SVGLength.SVG_LENGTHTYPE_PERCENTAGE || gradUnits ==
                        SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                    theCoords.addElement(
                      new Double(currentYPosition / 1000f -
                                 rect.getY() -
                                 val.getValue() * rect.getHeight()));
                } else {
                    theCoords.addElement(
                      new Double(currentYPosition / 1000f -
                                 val.getValue()));
                }
                val = ar.getBaseVal();
                if (val.getUnitType() ==
                        SVGLength.SVG_LENGTHTYPE_PERCENTAGE || gradUnits ==
                        SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                    theCoords.addElement(
                      new Double(val.getValue() * rect.getHeight()));
                } else {
                    theCoords.addElement(new Double(val.getValue()));
                }
            }
        }
        if (theCoords == null) {
            // percentage values are expressed according to the viewport.
            SVGElement vp =
              ((GraphicElement) area).getNearestViewportElement();
            if (area instanceof GraphicElement) {
                SVGRect rect = ((GraphicElement) area).getBBox();
                if (rect != null) {
                    theCoords = new Vector();
                    SVGLength val = acx.getBaseVal();
                    if (val.getUnitType() ==
                            SVGLength.SVG_LENGTHTYPE_PERCENTAGE ||
                            gradUnits ==
                            SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                        theCoords.addElement(
                          new Double(currentXPosition / 1000f +
                                     rect.getX() +
                                     val.getValue() * rect.getWidth()));
                    } else {
                        theCoords.addElement(
                          new Double(currentXPosition / 1000f +
                                     val.getValue()));
                    }
                    val = acy.getBaseVal();
                    if (val.getUnitType() ==
                            SVGLength.SVG_LENGTHTYPE_PERCENTAGE ||
                            gradUnits ==
                            SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                        theCoords.addElement(
                          new Double(currentYPosition / 1000f -
                                     rect.getY() -
                                     val.getValue() * rect.getHeight()));
                    } else {
                        theCoords.addElement(
                          new Double(currentYPosition / 1000f -
                                     val.getValue()));
                    }
                    theCoords.addElement(new Double(0));
                    val = afx.getBaseVal();
                    if (val.getUnitType() ==
                            SVGLength.SVG_LENGTHTYPE_PERCENTAGE ||
                            gradUnits ==
                            SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                        theCoords.addElement(
                          new Double(currentXPosition / 1000f +
                                     rect.getX() +
                                     val.getValue() * rect.getWidth()));
                    } else {
                        theCoords.addElement(
                          new Double(currentXPosition / 1000f +
                                     val.getValue()));
                    }
                    val = afy.getBaseVal();
                    if (val.getUnitType() ==
                            SVGLength.SVG_LENGTHTYPE_PERCENTAGE ||
                            gradUnits ==
                            SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                        theCoords.addElement(
                          new Double(currentYPosition / 1000f -
                                     rect.getY() -
                                     val.getValue() * rect.getHeight()));
                    } else {
                        theCoords.addElement(
                          new Double(currentYPosition / 1000f -
                                     val.getValue()));
                    }
                    val = ar.getBaseVal();
                    if (val.getUnitType() ==
                            SVGLength.SVG_LENGTHTYPE_PERCENTAGE ||
                            gradUnits ==
                            SVGUnitTypes.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
                        theCoords.addElement( new Double(val.getValue() *
                                                         rect.getHeight()));
                    } else {
                        theCoords.addElement(new Double(val.getValue()));
                    }
                }
            }
        }
        if (theCoords == null) {
            theCoords = new Vector();
            theCoords.addElement( new Double(currentXPosition / 1000f +
                                             acx.getBaseVal().getValue()));
            theCoords.addElement( new Double(currentYPosition / 1000f -
                                             acy.getBaseVal().getValue()));
            theCoords.addElement(new Double(0));
            theCoords.addElement( new Double(currentXPosition / 1000f +
                                             afx.getBaseVal().getValue())); // Fx
            theCoords.addElement(
              new Double(currentYPosition / 1000f -
                         afy.getBaseVal().getValue())); // Fy
            theCoords.addElement(
              new Double(ar.getBaseVal().getValue()));
        }
        float lastoffset = 0;
        for (int count = 0; count < nl.getLength(); count++) {
            stop = (SVGStopElementImpl) nl.item(count);
            CSSValue cv = stop.getPresentationAttribute("stop-color");
            if (cv == null) {
                // maybe using color
                cv = stop.getPresentationAttribute("color");
            }
            if (cv == null) {
                // problems
                MessageHandler.errorln("no stop-color or color in stop element");
                continue;
            }
            PDFColor color = new PDFColor(0, 0, 0);
            if (cv != null &&
                    cv.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) cv).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_RGBCOLOR) {
                    RGBColor col =
                      ((CSSPrimitiveValue) cv).getRGBColorValue();
                    CSSPrimitiveValue val;
                    val = col.getRed();
                    float red = val.getFloatValue(
                                  CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getGreen();
                    float green = val.getFloatValue(
                                    CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getBlue();
                    float blue = val.getFloatValue(
                                   CSSPrimitiveValue.CSS_NUMBER);
                    color = new PDFColor(red, green, blue);
                }
            }
            float offset = stop.getOffset().getBaseVal();
            // create bounds from last to offset
            lastoffset = offset;
            someColors.addElement(color);
        }
        PDFPattern myPat = this.pdfDoc.createGradient(true, aColorSpace,
                           someColors, theBounds, theCoords);

        currentStream.write(myPat.getColorSpaceOut(fill));
        if (fill)
            di.fill = true;
        else
            di.stroke = true;
    }

    /*
     * This sets up the style for drawing objects.
     * Should only set style for elements that have changes.
     *
     */
    // need mask drawing
    class DrawingInstruction {
        boolean stroke = false;
        boolean nonzero = false; // non-zero fill rule "f*", "B*" operator
        boolean fill = false;
        int linecap = 0; // butt
        int linejoin = 0; // miter
        int miterwidth = 8;
    }
    protected DrawingInstruction applyStyle(SVGElement area,
                                            SVGStylable style) {
        DrawingInstruction di = new DrawingInstruction();
        CSSValue sp;
        sp = style.getPresentationAttribute("fill");
        if (sp != null) {
            if (sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) sp).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_RGBCOLOR) {
                    RGBColor col =
                      ((CSSPrimitiveValue) sp).getRGBColorValue();
                    CSSPrimitiveValue val;
                    val = col.getRed();
                    float red = val.getFloatValue(
                                  CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getGreen();
                    float green = val.getFloatValue(
                                    CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getBlue();
                    float blue = val.getFloatValue(
                                   CSSPrimitiveValue.CSS_NUMBER);
                    PDFColor fillColour = new PDFColor(red, green, blue);
                    currentColour = fillColour;
                    currentStream.write(fillColour.getColorSpaceOut(true));
                    di.fill = true;
                } else if ( ((CSSPrimitiveValue) sp).getPrimitiveType() ==
                    CSSPrimitiveValue.CSS_URI) {
                    // gradient
                    String str = ((CSSPrimitiveValue) sp).getCssText();
                    handleGradient(str, di, true, area);
                } else if ( ((CSSPrimitiveValue) sp).getPrimitiveType() ==
                    CSSPrimitiveValue.CSS_STRING) {
                    String str = ((CSSPrimitiveValue) sp).getCssText();
                    if (str.equals("none")) {
                        di.fill = false;
                    } else if (str.equals("currentColor")) {
                        currentStream.write(
                          currentColour.getColorSpaceOut(true));
                        di.fill = true;
                        //			    	} else {
                        //				    	handleGradient(str, true, area);
                    }
                }
            }
        } else {
            PDFColor fillColour = new PDFColor(0, 0, 0);
            currentStream.write(fillColour.getColorSpaceOut(true));
        }
        sp = style.getPresentationAttribute("fill-rule");
        if (sp != null) {
            if (sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) sp).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_STRING) {
                    if (sp.getCssText().equals("nonzero")) {
                        di.nonzero = true;
                    }
                }
            }
        } else {
        }
        sp = style.getPresentationAttribute("stroke");
        if (sp != null) {
            if (sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) sp).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_RGBCOLOR) {
                    RGBColor col =
                      ((CSSPrimitiveValue) sp).getRGBColorValue();
                    CSSPrimitiveValue val;
                    val = col.getRed();
                    float red = val.getFloatValue(
                                  CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getGreen();
                    float green = val.getFloatValue(
                                    CSSPrimitiveValue.CSS_NUMBER);
                    val = col.getBlue();
                    float blue = val.getFloatValue(
                                   CSSPrimitiveValue.CSS_NUMBER);
                    PDFColor fillColour = new PDFColor(red, green, blue);
                    currentStream.write(
                      fillColour.getColorSpaceOut(false));
                    di.stroke = true;
                } else if ( ((CSSPrimitiveValue) sp).getPrimitiveType() ==
                    CSSPrimitiveValue.CSS_URI) {
                    // gradient
                    String str = ((CSSPrimitiveValue) sp).getCssText();
                    handleGradient(str, di, false, area);
                } else if ( ((CSSPrimitiveValue) sp).getPrimitiveType() ==
                    CSSPrimitiveValue.CSS_STRING) {
                    String str = ((CSSPrimitiveValue) sp).getCssText();
                    if (str.equals("none")) {
                        di.stroke = false;
                        //			    	} else {
                        //				    	handleGradient(str, false, area);
                    }
                }
            }
        } else {
            PDFColor fillColour = new PDFColor(0, 0, 0);
            currentStream.write(fillColour.getColorSpaceOut(false));
        }
        sp = style.getPresentationAttribute("stroke-linecap");
        if (sp != null) {
            if (sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) sp).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_STRING) {
                    String str = sp.getCssText();
                    // butt, round ,square
                    if (str.equals("butt")) {
                        currentStream.write(0 + " J\n");
                    } else if (str.equals("round")) {
                        currentStream.write(1 + " J\n");
                    } else if (str.equals("square")) {
                        currentStream.write(2 + " J\n");
                    }
                }
            }
        } else {
        }
        sp = style.getPresentationAttribute("stroke-linejoin");
        if (sp != null) {
            if (sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) sp).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_STRING) {
                    String str = sp.getCssText();
                    if (str.equals("miter")) {
                        currentStream.write(0 + " j\n");
                    } else if (str.equals("round")) {
                        currentStream.write(1 + " j\n");
                    } else if (str.equals("bevel")) {
                        currentStream.write(2 + " j\n");
                    }
                }
            }
        } else {
        }
        sp = style.getPresentationAttribute("stroke-miterlimit");
        if (sp != null) {
            if (sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                float width;
                width = ((CSSPrimitiveValue) sp).getFloatValue(
                          CSSPrimitiveValue.CSS_PT);
                PDFNumber pdfNumber = new PDFNumber();
                currentStream.write(pdfNumber.doubleOut(width) + " M\n");
            }
        } else {
        }
        sp = style.getPresentationAttribute("stroke-width");
        if (sp != null) {
            if (sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                float width;
                width = ((CSSPrimitiveValue) sp).getFloatValue(
                          CSSPrimitiveValue.CSS_PT);
                PDFNumber pdfNumber = new PDFNumber();
                currentStream.write(pdfNumber.doubleOut(width) + " w\n");
            }
        }
        sp = style.getPresentationAttribute("stroke-dasharray");
        if (sp != null) {
            if (sp.getValueType() == CSSValue.CSS_VALUE_LIST) {
                currentStream.write("[ ");
                CSSValueList list = (CSSValueList) sp;
                for (int count = 0; count < list.getLength(); count++) {
                    CSSValue val = list.item(count);
                    if (val.getValueType() ==
                            CSSValue.CSS_PRIMITIVE_VALUE) {
                        currentStream.write(
                          ((CSSPrimitiveValue) val).getFloatValue(
                            CSSPrimitiveValue.CSS_NUMBER) + " ");
                    }
                }
                currentStream.write("] ");
                sp = style.getPresentationAttribute("stroke-dashoffset");
                if (sp != null && sp.getValueType() ==
                        CSSValue.CSS_PRIMITIVE_VALUE) {
                    currentStream.write(
                      ((CSSPrimitiveValue) sp).getFloatValue(
                        CSSPrimitiveValue.CSS_NUMBER) + " d\n");
                } else {
                    currentStream.write("0 d\n");
                }
            }
        }
        sp = style.getPresentationAttribute("clip-path");
        if (sp != null) {
            String clipurl;
            if (sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) sp).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_URI) {
                    clipurl = ((CSSPrimitiveValue) sp).getCssText();
                    if (clipurl.startsWith("url(")) {
                        int b1 = clipurl.indexOf("(");
                        int b2 = clipurl.indexOf(")");
                        clipurl = clipurl.substring(b1 + 1, b2);
                    }
                    // get def of mask and set mask
                    SVGElement graph = null;
                    graph = locateDef(clipurl, area);
                    if (graph != null) {
                        MessageHandler.logln("clip path: " + graph);
                        // render the clip path elements and make it the clip
                        // renderElement(svgarea, graph, posx, posy);
                    }
                }
            }
        }
        sp = style.getPresentationAttribute("mask");
        if (sp != null) {
            String maskurl;
            if (sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) sp).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_URI) {
                    maskurl = ((CSSPrimitiveValue) sp).getCssText();
                    //					System.out.println("mask: " + maskurl);
                    // get def of mask and set mask
                    if (maskurl.startsWith("url(")) {
                        int b1 = maskurl.indexOf("(");
                        int b2 = maskurl.indexOf(")");
                        maskurl = maskurl.substring(b1 + 1, b2);
                    }
                    SVGElement graph = null;
                    graph = locateDef(maskurl, area);
                    if (graph != null) {
                        MessageHandler.logln("mask: " + graph);
                        //						SVGElement parent = graph.getGraphicParent();
                        //						graph.setParent(area);
                        //						renderElement(svgarea, graph, posx, posy);
                        //						graph.setParent(parent);
                    }
                }
            }
        }
        return di;
    }

    protected void applyTransform(SVGAnimatedTransformList trans) {
        PDFNumber pdfNumber = new PDFNumber();
        SVGTransformList list = trans.getBaseVal();
        for (int count = 0; count < list.getNumberOfItems(); count++) {
            SVGMatrix matrix =
              ((SVGTransform) list.getItem(count)).getMatrix();
            currentStream.write(pdfNumber.doubleOut(matrix.getA()) +
                                " " + pdfNumber.doubleOut(matrix.getB()) + " " +
                                pdfNumber.doubleOut(matrix.getC()) + " " +
                                pdfNumber.doubleOut(matrix.getD()) + " " +
                                pdfNumber.doubleOut(matrix.getE()) + " " +
                                pdfNumber.doubleOut(matrix.getF()) + " cm\n");
        }
    }

    /**
     * Main rendering selection.
     * This applies any transform and style and then calls the appropriate
     * rendering method depending on the type of element.
     */
    public void renderElement(SVGElement area, int posx, int posy) {
        int x = posx;
        int y = posy;
        //		CSSStyleDeclaration style = null;
        //		if(area instanceof SVGStylable)
        //			style = ((SVGStylable)area).getStyle();
        DrawingInstruction di = null;

        currentStream.write("q\n");
        if (area instanceof SVGTransformable) {
            SVGTransformable tf = (SVGTransformable) area;
            SVGAnimatedTransformList trans = tf.getTransform();
            if (trans != null) {
                applyTransform(trans);
            }
        }

        if (area instanceof SVGStylable) {
            di = applyStyle(area, (SVGStylable) area);
        }

        if (area instanceof SVGRectElement) {
            SVGRectElement rg = (SVGRectElement) area;
            float rectx = rg.getX().getBaseVal().getValue();
            float recty = rg.getY().getBaseVal().getValue();
            float rx = rg.getRx().getBaseVal().getValue();
            float ry = rg.getRy().getBaseVal().getValue();
            float rw = rg.getWidth().getBaseVal().getValue();
            float rh = rg.getHeight().getBaseVal().getValue();
            addRect(rectx, recty, rw, rh, rx, ry, di);
        } else if (area instanceof SVGLineElement) {
            SVGLineElement lg = (SVGLineElement) area;
            float x1 = lg.getX1().getBaseVal().getValue();
            float y1 = lg.getY1().getBaseVal().getValue();
            float x2 = lg.getX2().getBaseVal().getValue();
            float y2 = lg.getY2().getBaseVal().getValue();
            addLine(x1, y1, x2, y2, di);
        } else if (area instanceof SVGTextElementImpl) {
            //			currentStream.add("q\n");
            //			currentStream.add(1 + " " + 0 + " " + 0 + " " + 1 + " " + 0 + " " + 0 + " cm\n");
            currentStream.write("BT\n");
            renderText((SVGTextElementImpl) area, 0, 0, di);
            currentStream.write("ET\n");
            //			currentStream.add("Q\n");
        } else if (area instanceof SVGCircleElement) {
            SVGCircleElement cg = (SVGCircleElement) area;
            float cx = cg.getCx().getBaseVal().getValue();
            float cy = cg.getCy().getBaseVal().getValue();
            float r = cg.getR().getBaseVal().getValue();
            addCircle(cx, cy, r, di);
        } else if (area instanceof SVGEllipseElement) {
            SVGEllipseElement cg = (SVGEllipseElement) area;
            float cx = cg.getCx().getBaseVal().getValue();
            float cy = cg.getCy().getBaseVal().getValue();
            float rx = cg.getRx().getBaseVal().getValue();
            float ry = cg.getRy().getBaseVal().getValue();
            addEllipse(cx, cy, rx, ry, di);
        } else if (area instanceof SVGPathElementImpl) {
            addPath(((SVGPathElementImpl) area).pathElements, posx,
                    posy, di);
        } else if (area instanceof SVGPolylineElementImpl) {
            addPolyline(((SVGPolylineElementImpl) area).points, di, false);
        } else if (area instanceof SVGPolygonElementImpl) {
            addPolyline(((SVGPolygonElementImpl) area).points, di, true);
        } else if (area instanceof SVGGElementImpl) {
            renderGArea((SVGGElementImpl) area, x, y);
        } else if (area instanceof SVGUseElementImpl) {
            SVGUseElementImpl ug = (SVGUseElementImpl) area;
            String ref = ug.link;
            //			ref = ref.substring(1, ref.length());
            SVGElement graph = null;
            graph = locateDef(ref, ug);
            if (graph != null) {
                // probably not the best way to do this, should be able
                // to render without the style being set.
                //				SVGElement parent = graph.getGraphicParent();
                //				graph.setParent(area);
                // need to clip (if necessary) to the use area
                // the style of the linked element is as if it was
                // a direct descendant of the use element.

                // scale to the viewBox

                if (graph instanceof SVGSymbolElement) {
                    currentStream.write("q\n");
                    SVGSymbolElement symbol = (SVGSymbolElement) graph;
                    SVGRect view = symbol.getViewBox().getBaseVal();
                    float usex = ug.getX().getBaseVal().getValue();
                    float usey = ug.getY().getBaseVal().getValue();
                    float usewidth = ug.getWidth().getBaseVal().getValue();
                    float useheight =
                      ug.getHeight().getBaseVal().getValue();
                    float scaleX;
                    float scaleY;
                    scaleX = usewidth / view.getWidth();
                    scaleY = useheight / view.getHeight();
                    currentStream.write(usex + " " + usey + " m\n");
                    currentStream.write((usex + usewidth) + " " +
                                        usey + " l\n");
                    currentStream.write((usex + usewidth) + " " +
                                        (usey + useheight) + " l\n");
                    currentStream.write(usex + " " +
                                        (usey + useheight) + " l\n");
                    currentStream.write("h\n");
                    currentStream.write("W\n");
                    currentStream.write("n\n");
                    currentStream.write(scaleX + " 0 0 " + scaleY +
                                        " " + usex + " " + usey + " cm\n");
                    renderSymbol(symbol, posx, posy);
                    currentStream.write("Q\n");
                } else {
                    renderElement(graph, posx, posy);
                }
                //				graph.setParent(parent);
            }
            else {
                MessageHandler.logln("Use Element: " + ref + " not found");
            }
        } else if (area instanceof SVGImageElementImpl) {
            SVGImageElementImpl ig = (SVGImageElementImpl) area;
            renderImage(ig.link, ig.x, ig.y, ig.width, ig.height);
        } else if (area instanceof SVGSVGElement) {
            currentStream.write("q\n");
            SVGSVGElement svgel = (SVGSVGElement) area;
            float svgx = 0;
            if (svgel.getX() != null)
                svgx = svgel.getX().getBaseVal().getValue();
            float svgy = 0;
            if (svgel.getY() != null)
                svgy = svgel.getY().getBaseVal().getValue();
            currentStream.write(1 + " 0 0 " + 1 + " " + svgx + " " +
                                svgy + " cm\n");
            renderSVG(svgel, (int)(x + 1000 * svgx),
                      (int)(y + 1000 * svgy));
            currentStream.write("Q\n");
            //		} else if (area instanceof SVGSymbolElement) {
            // 'symbol' element is not rendered (except by 'use')
        } else if (area instanceof SVGAElement) {
            SVGAElement ael = (SVGAElement) area;
            org.w3c.dom.NodeList nl = ael.getChildNodes();
            for (int count = 0; count < nl.getLength(); count++) {
                org.w3c.dom.Node n = nl.item(count);
                if (n instanceof SVGElement) {
                    if (n instanceof GraphicElement) {
                        SVGRect rect = ((GraphicElement) n).getBBox();
                        if (rect != null) {
                            /*							currentAnnotList = this.pdfDoc.makeAnnotList();
                             							currentPage.setAnnotList(currentAnnotList);
                             							String dest = linkSet.getDest();
                             							int linkType = linkSet.getLinkType();
                             							currentAnnotList.addLink(
                             								this.pdfDoc.makeLink(lrect.getRectangle(), dest, linkType));
                             							currentAnnotList = null;
                             */ }
                    }
                    renderElement((SVGElement) n, posx, posy);
                }
            }
        } else if (area instanceof SVGSwitchElement) {
            handleSwitchElement(posx, posy, (SVGSwitchElement) area);
        }
        // should be done with some cleanup code, so only
        // required values are reset.
        currentStream.write("Q\n");
    }

    /**
     * Todo: underline, linethrough, textpath
     */
    public void renderText(SVGTextElementImpl tg, float x, float y,
                           DrawingInstruction di) {
        SVGTextRenderer str = new SVGTextRenderer(fontState, tg, x, y);
        if (di.fill) {
            if (di.stroke) {
                currentStream.write("2 Tr\n");
            } else {
                currentStream.write("0 Tr\n");
            }
        } else if (di.stroke) {
            currentStream.write("1 Tr\n");
        }
        str.renderText(tg);
    }

    /**
     * Adds an svg string to the output.
     * This handles the escaping of special pdf chars and deals with
     * whitespace.
     */
    protected float addSVGStr(FontState fs, float currentX, String str,
                              boolean spacing) {
        boolean inbetween = false;
        boolean addedspace = false;
        StringBuffer pdf = new StringBuffer();
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            if (ch > 127) {
                pdf = pdf.append("\\");
                pdf = pdf.append(Integer.toOctalString((int) ch));
                currentX += fs.width(ch) / 1000f;
                inbetween = true;
                addedspace = false;
            } else {
                switch (ch) {
                    case '(' :
                        pdf = pdf.append("\\(");
                        currentX += fs.width(ch) / 1000f;
                        inbetween = true;
                        addedspace = false;
                        break;
                    case ')' :
                        pdf = pdf.append("\\)");
                        currentX += fs.width(ch) / 1000f;
                        inbetween = true;
                        addedspace = false;
                        break;
                    case '\\' :
                        pdf = pdf.append("\\\\");
                        currentX += fs.width(ch) / 1000f;
                        inbetween = true;
                        addedspace = false;
                        break;
                    case '\t':
                    case ' ':
                        if (spacing) {
                            pdf = pdf.append(' ');
                            currentX += fs.width(' ') / 1000f;
                        } else {
                            if (inbetween && !addedspace) {
                                addedspace = true;
                                pdf = pdf.append(' ');
                                currentX += fs.width(' ') / 1000f;
                            }
                        }
                        break;
                    case '\n':
                    case '\r':
                        if (spacing) {
                            pdf = pdf.append(' ');
                            currentX += fs.width(' ') / 1000f;
                        }
                        break;
                    default:
                        addedspace = false;
                        pdf = pdf.append(ch);
                        currentX += fs.width(ch) / 1000f;
                        inbetween = true;
                        break;
                }
            }
        }
        currentStream.write(pdf.toString());
        return currentX;
    }

    /**
     * Locates a defined element in an svg document.
     * Either gets the element defined by its "id" in the current
     * SVGDocument, or if the uri reference is to an external
     * document it loads the document and returns the element.
     */
    protected SVGElement locateDef(String ref, SVGElement currentElement) {
        int pos;
        ref = ref.trim();
        pos = ref.indexOf("#");
        if (pos == 0) {
            // local doc
            Document doc = currentElement.getOwnerDocument();
            Element ele =
              doc.getElementById(ref.substring(1, ref.length()));
            if (ele instanceof SVGElement) {
                return (SVGElement) ele;
            }
        } else if (pos != -1) {
            String href = ref.substring(0, pos);
            if (href.indexOf(":") == -1) {
                href = "file:" + href;
            }
            try {
                // this is really only to get a cached svg image
                FopImage img = FopImageFactory.Make(href);
                if (img instanceof SVGImage) {
                    SVGDocument doc = ((SVGImage) img).getSVGDocument();
                    Element ele = doc.getElementById(
                                    ref.substring(pos + 1, ref.length()));
                    if (ele instanceof SVGElement) {
                        return (SVGElement) ele;
                    }
                }
            } catch (Exception e) {
                MessageHandler.errorln(e.toString());
            }
        }
        return null;
    }

    /**
     * This class is used to handle the rendering of svg text.
     * This is so that it can deal with the recursive rendering
     * of text markup, while keeping track of the state and position.
     */
    class SVGTextRenderer {
        FontState fs;
        String transstr;
        float currentX;
        float currentY;
        float baseX;
        float baseY;
        SVGMatrix matrix;
        float x;
        float y;

        SVGTextRenderer(FontState fontState, SVGTextElementImpl tg,
                        float x, float y) {
            fs = fontState;

            PDFNumber pdfNumber = new PDFNumber();
            SVGTransformList trans = tg.getTransform().getBaseVal();
            matrix = trans.consolidate().getMatrix();
            transstr = (pdfNumber.doubleOut(matrix.getA()) + " " +
                        pdfNumber.doubleOut(matrix.getB()) + " " +
                        pdfNumber.doubleOut(matrix.getC()) + " " +
                        pdfNumber.doubleOut(-matrix.getD()) + " ");
            this.x = x;
            this.y = y;
        }

        void renderText(SVGTextElementImpl te) {
            DrawingInstruction di = applyStyle(te, te);
            if (di.fill) {
                if (di.stroke) {
                    currentStream.write("2 Tr\n");
                } else {
                    currentStream.write("0 Tr\n");
                }
            } else if (di.stroke) {
                currentStream.write("1 Tr\n");
            }
            updateFont(te, fs);

            float tx = te.x;
            float ty = te.y;
            currentX = x + tx;
            currentY = y + ty;
            baseX = currentX;
            baseY = currentY;
            NodeList nodel = te.getChildNodes();
            //		Vector list = te.textList;
            for (int count = 0; count < nodel.getLength(); count++) {
                Object o = nodel.item(count);
                applyStyle(te, te);
                if (o instanceof CharacterData) {
                    String str = ((CharacterData) o).getData();
                    currentStream.write(transstr +
                                        (currentX + matrix.getE()) + " " +
                                        (baseY + matrix.getF()) + " Tm " + "(");
                    boolean spacing = "preserve".equals(te.getXMLspace());
                    currentX = addSVGStr(fs, currentX, str, spacing);
                    currentStream.write(") Tj\n");
                } else if (o instanceof SVGTextPathElementImpl) {
                    SVGTextPathElementImpl tpg = (SVGTextPathElementImpl) o;
                    String ref = tpg.str;
                    SVGElement graph = null;
                    graph = locateDef(ref, tpg);
                    if (graph instanceof SVGPathElementImpl) {
                        // probably not the best way to do this, should be able
                        // to render without the style being set.
                        //					GraphicImpl parent = graph.getGraphicParent();
                        //					graph.setParent(tpg);
                        // set text path??
                        // how should this work
                        //					graph.setParent(parent);
                    }
                } else if (o instanceof SVGTRefElementImpl) {
                    SVGTRefElementImpl trg = (SVGTRefElementImpl) o;
                    String ref = trg.ref;
                    SVGElement element = locateDef(ref, trg);
                    if (element instanceof SVGTextElementImpl) {
                        //					GraphicImpl parent = graph.getGraphicParent();
                        //					graph.setParent(trg);
                        SVGTextElementImpl tele =
                          (SVGTextElementImpl) element;
                        // the style should be from tele, but it needs to be placed as a child
                        // of trg to work
                        di = applyStyle(trg, trg);
                        if (di.fill) {
                            if (di.stroke) {
                                currentStream.write("2 Tr\n");
                            } else {
                                currentStream.write("0 Tr\n");
                            }
                        } else if (di.stroke) {
                            currentStream.write("1 Tr\n");
                        }
                        boolean changed = false;
                        FontState oldfs = fs;
                        changed = updateFont(te, fs);
                        NodeList nl = tele.getChildNodes();
                        boolean spacing =
                          "preserve".equals(trg.getXMLspace());
                        renderTextNodes(spacing, nl,
                                        trg.getX().getBaseVal(),
                                        trg.getY().getBaseVal(),
                                        trg.getDx().getBaseVal(),
                                        trg.getDy().getBaseVal());

                        if (changed) {
                            fs = oldfs;
                            currentStream.write("/" +
                                                fs.getFontName() + " " +
                                                fs.getFontSize() / 1000f + " Tf\n");
                        }
                        //					graph.setParent(parent);
                    }
                } else if (o instanceof SVGTSpanElementImpl) {
                    SVGTSpanElementImpl tsg = (SVGTSpanElementImpl) o;
                    applyStyle(tsg, tsg);
                    boolean changed = false;
                    FontState oldfs = fs;
                    changed = updateFont(tsg, fs);
                    boolean spacing = "preserve".equals(tsg.getXMLspace());
                    renderTextNodes(spacing, tsg.getChildNodes(),
                                    tsg.getX().getBaseVal(),
                                    tsg.getY().getBaseVal(),
                                    tsg.getDx().getBaseVal(),
                                    tsg.getDy().getBaseVal());

                    //				currentX += fs.width(' ') / 1000f;
                    if (changed) {
                        fs = oldfs;
                        currentStream.write("/" + fs.getFontName() +
                                            " " + fs.getFontSize() / 1000f + " Tf\n");
                    }
                } else {
                    MessageHandler.errorln("Error: unknown text element " + o);
                }
            }
        }

        void renderTextNodes(boolean spacing, NodeList nl,
                             SVGLengthList xlist, SVGLengthList ylist,
                             SVGLengthList dxlist, SVGLengthList dylist) {
            boolean inbetween = false;
            boolean addedspace = false;
            int charPos = 0;
            float xpos = currentX;
            float ypos = currentY;

            for (int count = 0; count < nl.getLength(); count++) {
                Node n = nl.item(count);
                if (n instanceof CharacterData) {
                    StringBuffer pdf = new StringBuffer();
                    String str = ((CharacterData) n).getData();
                    for (int i = 0; i < str.length(); i++) {
                        char ch = str.charAt(i);
                        xpos = currentX;
                        ypos = currentY;
                        if (ylist.getNumberOfItems() > charPos) {
                            ypos = baseY + (ylist.getItem(charPos)).
                                   getValue();
                        }
                        if (dylist.getNumberOfItems() > charPos) {
                            ypos = ypos + (dylist.getItem(charPos)).
                                   getValue();
                        }
                        if (xlist.getNumberOfItems() > charPos) {
                            xpos = baseX + (xlist.getItem(charPos)).
                                   getValue();
                        }
                        if (dxlist.getNumberOfItems() > charPos) {
                            xpos = xpos + (dxlist.getItem(charPos)).
                                   getValue();
                        }
                        if (ch > 127) {
                            pdf = pdf.append(transstr +
                                             (xpos + matrix.getE()) + " " +
                                             (ypos + matrix.getF()) + " Tm " +
                                             "(" + "\\" +
                                             Integer.toOctalString((int) ch) +
                                             ") Tj\n");
                            currentX = xpos + fs.width(ch) / 1000f;
                            currentY = ypos;
                            charPos++;
                            inbetween = true;
                            addedspace = false;
                        } else {
                            switch (ch) {
                                case '(' :
                                    pdf = pdf.append(transstr +
                                                     (xpos + matrix.getE()) +
                                                     " " + (ypos +
                                                            matrix.getF()) + " Tm " +
                                                     "(" + "\\(" + ") Tj\n");
                                    currentX = xpos + fs.width(ch) / 1000f;
                                    currentY = ypos;
                                    charPos++;
                                    inbetween = true;
                                    addedspace = false;
                                    break;
                                case ')' :
                                    pdf = pdf.append(transstr +
                                                     (xpos + matrix.getE()) +
                                                     " " + (ypos +
                                                            matrix.getF()) + " Tm " +
                                                     "(" + "\\)" + ") Tj\n");
                                    currentX = xpos + fs.width(ch) / 1000f;
                                    currentY = ypos;
                                    charPos++;
                                    inbetween = true;
                                    addedspace = false;
                                    break;
                                case '\\' :
                                    pdf = pdf.append(transstr +
                                                     (xpos + matrix.getE()) +
                                                     " " + (ypos +
                                                            matrix.getF()) + " Tm " +
                                                     "(" + "\\\\" + ") Tj\n");
                                    currentX = xpos + fs.width(ch) / 1000f;
                                    currentY = ypos;
                                    charPos++;
                                    inbetween = true;
                                    addedspace = false;
                                    break;
                                case '\t':
                                case ' ':
                                    if (spacing) {
                                        currentX = xpos + fs.width(' ') /
                                                   1000f;
                                        currentY = ypos;
                                        charPos++;
                                    } else {
                                        if (inbetween && !addedspace) {
                                            addedspace = true;
                                            currentX = xpos + fs.width(' ')
                                                       / 1000f;
                                            currentY = ypos;
                                            charPos++;
                                        }
                                    }
                                    break;
                                case '\n':
                                case '\r':
                                    if (spacing) {
                                        currentX = xpos + fs.width(' ') /
                                                   1000f;
                                        currentY = ypos;
                                        charPos++;
                                    }
                                    break;
                                default:
                                    addedspace = false;
                                    pdf = pdf.append(transstr +
                                                     (xpos + matrix.getE()) +
                                                     " " + (ypos +
                                                            matrix.getF()) + " Tm " +
                                                     "(" + ch + ") Tj\n");
                                    currentX = xpos + fs.width(ch) / 1000f;
                                    currentY = ypos;
                                    charPos++;
                                    inbetween = true;
                                    break;
                            }
                        }
                        currentStream.write(pdf.toString());
                    }
                }
            }
        }

        protected boolean updateFont(SVGStylable style, FontState fs) {
            boolean changed = false;
            String fontFamily = fs.getFontFamily();
            CSSValue sp = style.getPresentationAttribute("font-family");
            if (sp != null &&
                    sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) sp).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_STRING) {
                    fontFamily = sp.getCssText();
                }
            }
            if (!fontFamily.equals(fs.getFontFamily())) {
                changed = true;
            }
            String fontStyle = fs.getFontStyle();
            sp = style.getPresentationAttribute("font-style");
            if (sp != null &&
                    sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) sp).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_STRING) {
                    fontStyle = sp.getCssText();
                }
            }
            if (!fontStyle.equals(fs.getFontStyle())) {
                changed = true;
            }
            String fontWeight = fs.getFontWeight();
            sp = style.getPresentationAttribute("font-weight");
            if (sp != null &&
                    sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                if (((CSSPrimitiveValue) sp).getPrimitiveType() ==
                        CSSPrimitiveValue.CSS_STRING) {
                    fontWeight = sp.getCssText();
                }
            }
            if (!fontWeight.equals(fs.getFontWeight())) {
                changed = true;
            }
            float newSize = fs.getFontSize() / 1000f;
            sp = style.getPresentationAttribute("font-size");
            if (sp != null &&
                    sp.getValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
                //		    if(((CSSPrimitiveValue)sp).getPrimitiveType() == CSSPrimitiveValue.CSS_NUMBER) {
                newSize = ((CSSPrimitiveValue) sp).getFloatValue(
                            CSSPrimitiveValue.CSS_PT);
                //		    }
            }
            if (fs.getFontSize() / 1000f != newSize) {
                changed = true;
            }
            if (changed) {
                try {
                    fs = new FontState(fs.getFontInfo(), fontFamily,
                                       fontStyle, fontWeight, (int)(newSize * 1000));
                } catch (Exception fope) {
                }
                this.fs = fs;

                currentStream.write("/" + fs.getFontName() + " " +
                                    newSize + " Tf\n");
            } else {
                if (!currentFontName.equals(fs.getFontName()) ||
                        currentFontSize != fs.getFontSize()) {
                    //				currentFontName = fs.getFontName();
                    //				currentFontSize = fs.getFontSize();
                    currentStream.write("/" + fs.getFontName() + " " +
                                        (fs.getFontSize() / 1000) + " Tf\n");
                }
            }
            return changed;
        }
    }
}
