// Copyright 2000-2003 FreeHEP package org.freehep.graphicsio.svg; import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import java.awt.image.*; import java.awt.image.renderable.*; import java.io.*; import java.text.*; import java.util.*; import java.util.zip.*; import org.freehep.graphics2d.TagString; import org.freehep.graphicsio.AbstractVectorGraphicsIO; import org.freehep.graphicsio.PageConstants; import org.freehep.graphicsio.InfoConstants; import org.freehep.graphicsio.ImageConstants; import org.freehep.graphicsio.ImageGraphics2D; import org.freehep.graphicsio.font.FontUtilities; import org.freehep.util.UserProperties; import org.freehep.util.Value; import org.freehep.util.io.Base64OutputStream; import org.freehep.util.io.WriterOutputStream; import org.freehep.xml.util.XMLWriter; /** * This class implements the Scalable Vector Graphics output. * SVG specifications can be found at http://www.w3c.org/Graphics/SVG/ * * The current implementation is based on REC-SVG-20010904 * but can generate also files for the older specs CR-SVG-20000802, WD-SVG-20000303 * * @author Mark Donszelmann * @version $Id: SVGGraphics2D.java,v 1.16 2003/05/27 22:35:47 duns Exp $ */ public class SVGGraphics2D extends AbstractVectorGraphicsIO { public static final String VERSION_1_0 = "Version 1.0 (REC-SVG-20010904)"; public static final String VERSION_1_1 = "Version 1.1 (REC-SVG11-20030114)"; private static final String rootKey = SVGGraphics2D.class.getName(); public static final String TRANSPARENT = rootKey+"."+PageConstants.TRANSPARENT; public static final String BACKGROUND = rootKey+"."+PageConstants.BACKGROUND; public static final String BACKGROUND_COLOR = rootKey+"."+PageConstants.BACKGROUND_COLOR; public static final String VERSION = rootKey+".Version"; public static final String COMPRESS = rootKey+".Binary"; public static final String STYLABLE = rootKey+".Stylable"; public static final String IMAGE_SIZE = rootKey+"."+ImageConstants.IMAGE_SIZE; public static final String EXPORT_IMAGES = rootKey+".ExportImages"; public static final String EXPORT_SUFFIX = rootKey+".ExportSuffix"; public static final String WRITE_IMAGES_AS = rootKey+"."+ImageConstants.WRITE_IMAGES_AS; public static final String FOR = rootKey+"."+InfoConstants.FOR; public static final String TITLE = rootKey+"."+InfoConstants.TITLE; private static final UserProperties defaultProperties = new UserProperties(); static { defaultProperties.setProperty(TRANSPARENT, true); defaultProperties.setProperty(BACKGROUND, false); defaultProperties.setProperty(BACKGROUND_COLOR, Color.GRAY); defaultProperties.setProperty(VERSION, VERSION_1_0); defaultProperties.setProperty(COMPRESS, true); defaultProperties.setProperty(STYLABLE, true); defaultProperties.setProperty(IMAGE_SIZE, new Dimension(0, 0)); // ImageSize defaultProperties.setProperty(EXPORT_IMAGES, false); defaultProperties.setProperty(EXPORT_SUFFIX, "image"); defaultProperties.setProperty(WRITE_IMAGES_AS, ImageConstants.SMALLEST); defaultProperties.setProperty(FOR, ""); defaultProperties.setProperty(TITLE, ""); } public static Properties getDefaultProperties() { return defaultProperties; } public static void setDefaultProperties(Properties newProperties) { defaultProperties.setProperties(newProperties); } public static final String version = "$Revision: 1.16 $"; // shift to make draw routines draw in the middle private static final double bias = 0.5; // current filename including path private String filename; // The lowerleft and upper right points of the bounding box. private int bbx, bby, bbw, bbh; // The private writer used for this file. private OutputStream ros; private PrintWriter os; // table for gradients Hashtable gradients = new Hashtable(); // table for textures Hashtable textures = new Hashtable(); private Stack closeTags = new Stack(); private int imageNumber = 0; private Value clipNumber; private int currentClipNumber; private int width, height; /*================================================================================ | 1. Constructors & Factory Methods *================================================================================*/ public SVGGraphics2D(File file, Dimension size) throws IOException { this(new FileOutputStream(file), size); this.filename = file.getPath(); } public SVGGraphics2D(File file, Component component) throws IOException { this(new FileOutputStream(file), component); this.filename = file.getPath(); } public SVGGraphics2D(OutputStream os, Dimension size) throws IOException { super(size, false); init(os); width = size.width; height = size.height; } public SVGGraphics2D(OutputStream os, Component component) throws IOException { super(component, false); init(os); width = getSize().width; height = getSize().height; } private void init(OutputStream os) { this.ros = os; initProperties(getDefaultProperties()); this.filename = null; this.clipNumber = new Value().set(0); this.currentClipNumber = -1; } protected SVGGraphics2D(SVGGraphics2D graphics, boolean doRestoreOnDispose) { super(graphics, doRestoreOnDispose); // Now initialize the new object. filename = graphics.filename; os = graphics.os; bbx = graphics.bbx; bby = graphics.bby; bbw = graphics.bbw; bbh = graphics.bbh; gradients = graphics.gradients; textures = graphics.textures; clipNumber = graphics.clipNumber; currentClipNumber = -1; } /*================================================================================ | 2. Document Settings *================================================================================*/ /** * Get the bounding box for this image. */ public void setBoundingBox() { bbx = 0; bby = 0; Dimension size = getSize(); bbw = size.width; bbh = size.height; } /*================================================================================ | 3. Header, Trailer, Multipage & Comments *================================================================================*/ /*-------------------------------------------------------------------------------- | 3.1 Header & Trailer *--------------------------------------------------------------------------------*/ /** * Write out the header of this SVG file. */ public void writeHeader() throws IOException { ros = new BufferedOutputStream(ros); if (isProperty(COMPRESS)) ros = new GZIPOutputStream(ros); os = new PrintWriter(ros, true); // Do the bounding box calculation. setBoundingBox(); imageNumber = 0; os.println(""); if (getProperty(VERSION).equals(VERSION_1_0)) { os.println(""); } else if (getProperty(VERSION).equals(VERSION_1_1)) { // FIXME disabled for now } else { // FIXME experimental version } os.println(); int x = 0; int y = 0; Dimension size = getPropertyDimension(IMAGE_SIZE); int w = size.width; if (w <= 0) w = width; int h = size.height; if (h <= 0) h = height; os.println(""); closeTags.push(" "); os.println(""); os.println(XMLWriter.normalizeText(getProperty(TITLE))); os.println(""); String producer = getClass().getName(); if (!isDeviceIndependent()) { producer += " "+version.substring(1,version.length()-1); } os.println(""); os.println(""+XMLWriter.normalizeText(getProperty(TITLE))+""); os.println(""+XMLWriter.normalizeText(getCreator())+""); os.println(""+XMLWriter.normalizeText(producer)+""); os.println(""+XMLWriter.normalizeText(getProperty(FOR))+""); if (!isDeviceIndependent()) { os.println(""+ DateFormat. getDateTimeInstance(DateFormat.FULL, DateFormat.FULL). format(new Date())+""); } os.println(""); writeDefs(); writeSetup(); } private void writeDefs() throws IOException { // The defs are kept in a file SVGDefs.txt in the same area // as this class definition. It is simply copied into the // output file. os.println(""); copyResourceTo(this,"SVGDefs.txt", os); if (isProperty(STYLABLE)) { copyResourceTo(this, "SVGDefs-stylable.txt", os); } else { copyResourceTo(this, "SVGDefs-stylable.txt", os); } os.println("\n"); } private void writeSetup() throws IOException { os.println(""); setFont(getFont()); closeTags.push(" "); } public void writeBackground() throws IOException { if (isProperty(TRANSPARENT)) { setBackground(null); } else if (isProperty(BACKGROUND)) { setBackground(getPropertyColor(BACKGROUND_COLOR)); clearRect(0.0, 0.0, getSize().width, getSize().height); } else { setBackground(getComponent() != null ? getComponent().getBackground() : Color.WHITE); clearRect(0.0, 0.0, getSize().width, getSize().height); } } public void writeTrailer() throws IOException { writeGraphicsRestore(); } public void closeStream() throws IOException { os.close(); } /*================================================================================ | 4. Create *================================================================================*/ public Graphics create() { try { writeGraphicsSave(); } catch (IOException e) { handleException(e); } SVGGraphics2D tempGraphics = new SVGGraphics2D(this, true); // os.println(""); return tempGraphics; } public Graphics create(double x, double y, double width, double height) { try { writeGraphicsSave(); } catch (IOException e) { handleException(e); } SVGGraphics2D graphics = new SVGGraphics2D(this, true); // FIXME: All other drivers have a translate(x,y), clip(0,0,w,h) here os.println(""); // defaultStyle()+">"); graphics.closeTags.push(" "); return graphics; } protected void writeGraphicsSave() throws IOException { // not applicable } protected void writeGraphicsRestore() throws IOException { while(!closeTags.empty()) { os.println(closeTags.pop()); } } /*================================================================================ | 5. Drawing Methods *================================================================================*/ /* 5.1 shapes */ /* 5.1.1. lines, rectangles, round rectangles */ public void drawLine(double x1, double y1, double x2, double y2) { os.println(""); } public void drawRect(double x, double y, double width, double height) { os.println(""); } public void fillRect(double x, double y, double width, double height) { os.println(""); } public void drawRoundRect(double x, double y, double width, double height, double arcWidth, double arcHeight) { os.println(""); } public void fillRoundRect(double x, double y, double width, double height, double arcWidth, double arcHeight) { os.println(""); } /* 5.1.2. polylines, polygons */ public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { if (nPoints>1) { os.print(""); } } public void drawPolyline(double[] xPoints, double[] yPoints, int nPoints) { if (nPoints>1) { os.print(""); } } public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { if (nPoints>1) { os.print(""); } } public void drawPolygon(double[] xPoints, double[] yPoints, int nPoints) { if (nPoints>1) { os.print(""); } } public void drawPolygon(Polygon p) { drawPolygon(p.xpoints,p.ypoints,p.npoints); } public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { if (nPoints>1) { os.print(""); } } public void fillPolygon(double[] xPoints, double[] yPoints, int nPoints) { if (nPoints>1) { os.print(""); } } public void fillPolygon(Polygon p) { fillPolygon(p.xpoints,p.ypoints,p.npoints); } /* 5.1.3. ovals, arcs */ public void drawArc(double x, double y, double width, double height, double startAngle, double arcAngle) { double sa = startAngle * Math.PI / 180; double aa = arcAngle * Math.PI / 180; double rx = width/2; double ry = height/2; double x1 = x + bias + rx + rx*Math.cos(sa); double y1 = y + bias + ry - ry*Math.sin(sa); double x2 = x + bias + rx + rx*Math.cos(sa+aa); double y2 = y + bias + ry - ry*Math.sin(sa+aa); int large = (Math.abs(arcAngle) <= 180) ? 0 : 1; int sweep = (arcAngle > 0) ? 0 : 1; os.println(""); } public void fillArc(double x, double y, double width, double height, double startAngle, double arcAngle) { double sa = startAngle * Math.PI / 180; double aa = arcAngle * Math.PI / 180; double rx = width/2; double ry = height/2; double x1 = x + rx + rx*Math.cos(sa); double y1 = y + ry - ry*Math.sin(sa); double x2 = x + rx + rx*Math.cos(sa+aa); double y2 = y + ry - ry*Math.sin(sa+aa); int large = (Math.abs(arcAngle) <= 180) ? 0 : 1; int sweep = (arcAngle > 0) ? 0 : 1; os.println(""); } public void drawOval(double x, double y, double width, double height) { os.println(""); } public void fillOval(double x, double y, double width, double height) { os.println(""); } /* public void drawSymbol(double x, double y, double size, int symbol) { if (size>0) { // FIXME: the current viewer (Adobe beta2 4/00) ignores the ViewBox in the symbols // so we go for fixed size (100) symbols and do the scaling and translation ourselves... // we also leave out any placement! os.print(""); } } */ /* 5.1.4. shapes */ public void draw(Shape shape) { PathIterator path = shape.getPathIterator(null); os.println(""); writePath(path); os.println(" "); } public void fill(Shape shape) { PathIterator path = shape.getPathIterator(null); StringBuffer s = new StringBuffer(); s.append(color(null, getPaint())); if (path.getWindingRule()==PathIterator.WIND_EVEN_ODD) { s.append("fill-rule:evenodd;"); } else { s.append("fill-rule:nonzero;"); } os.println(""); writePath(path); os.println(" "); } public void fillAndDraw(Shape shape, Color fillColor) { PathIterator path = shape.getPathIterator(null); StringBuffer s = new StringBuffer(); if (fillColor != null) { s.append(color(getPaint(), fillColor)); if (path.getWindingRule()==PathIterator.WIND_EVEN_ODD) { s.append("fill-rule:evenodd;"); } else { s.append("fill-rule:nonzero;"); } } os.println(""); writePath(path); os.println(" "); } /* 5.2. Images */ public void copyArea(int x, int y, int width, int height, int dx, int dy) { writeWarning(getClass()+": copyArea(int, int, int, int, int, int) not implemented."); } protected void writeImage(RenderedImage image, AffineTransform xform, Color bkg) throws IOException { if ((xform != null) && !xform.isIdentity()) { os.println(""); } os.print(""); if ((xform != null) && !xform.isIdentity()) { os.println(" "); } } /* 5.3. Strings */ protected void writeString(String str, double x, double y) throws IOException { str = FontUtilities.getEncodedString(str, getFont().getName()); os.println(""); os.println(XMLWriter.normalizeText(str)); os.println(""); } public void drawString(String str, double x, double y, int horizontal, int vertical, boolean framed, Color frameColor, double frameWidth, boolean banner, Color bannerColor) { str = FontUtilities.getEncodedString(str, getFont().getName()); LineMetrics metrics = getFont().getLineMetrics(str, getFontRenderContext()); double w = getFont().getStringBounds(str, getFontRenderContext()).getWidth(); double h = metrics.getHeight(); double d = metrics.getDescent(); double adjustment = (getFont().getSize2D()*2)/10; double ny = getYalignment(y, h, d, vertical); double nx = getXalignment(x, w, horizontal); // Calculate the box size for the banner. double rx = nx-adjustment; double ry = ny-h+d-adjustment; double rw = w+2*adjustment; double rh = h+2*adjustment; if (banner) { Color color = getColor(); setColor(bannerColor); fillRect(rx, ry, rw, rh); setColor(color); } if (framed) { Color color = getColor(); setColor(frameColor); Stroke s = getStroke(); setLineWidth(frameWidth); drawRect(rx, ry, rw, rh); setColor(color); setStroke(s); } os.println(""); os.println(XMLWriter.normalizeText(str)); os.println(""); } public void drawString(TagString str, double x, double y, int horizontal, int vertical, boolean framed, Color frameColor, double frameWidth, boolean banner, Color bannerColor) { SVGTagHandler tagHandler = new SVGTagHandler(isProperty(STYLABLE), getFont(), getFontRenderContext()); double nx = getXalignment(x, tagHandler.stringWidth(str), horizontal); LineMetrics metrics = getFont().getLineMetrics(str.toString(), getFontRenderContext()); double w = tagHandler.stringWidth(str); double h = metrics.getHeight(); double d = metrics.getDescent(); double adjustment = (getFont().getSize2D()*2)/10; double ny = getYalignment(y, h, d, vertical); // Calculate the box size for the banner. double rx = nx-adjustment; double ry = ny-h+d-adjustment; double rw = w+2*adjustment; double rh = h+2*adjustment; if (banner) { Color color = getColor(); setColor(bannerColor); fillRect(rx, ry, rw, rh); setColor(color); } if (framed) { Color color = getColor(); setColor(frameColor); Stroke s = getStroke(); setLineWidth(frameWidth); drawRect(rx, ry, rw, rh); setColor(color); setStroke(s); } String string = tagHandler.parse(str); string = FontUtilities.getEncodedString(string, getFont().getName()); os.println(""); os.println(string); os.println(""); } public void drawString(AttributedCharacterIterator iterator, float x, float y) { writeWarning(getClass()+": drawString(AttributedCharacterIterator, float, float) not implemented."); } public void drawGlyphVector(GlyphVector g, float x, float y) { writeWarning(getClass()+": drawGlyphVector(GlyphVector, float, float) not implemented."); } /*================================================================================ | 6. Transformations *================================================================================*/ protected void writeTransform(AffineTransform transform) throws IOException { os.println(""); closeTags.push(" "); } /*================================================================================ | 7. Clipping *================================================================================*/ protected void writeClip(Rectangle2D r2d) throws IOException { writeClip((Shape)r2d); } protected void writeClip(Shape s) throws IOException { if (s == null) { currentClipNumber = -1; return; } PathIterator path = s.getPathIterator(null); currentClipNumber = clipNumber.getInt(); clipNumber.set(currentClipNumber+1); os.println(""); writePath(path); os.println(""); } /*================================================================================ | 8. Graphics State *================================================================================*/ /* 8.1. stroke/linewidth */ protected void writeWidth(float width) throws IOException { // width of 0 means thinnest line, which does not exist in SVG if (width == 0) width = 0.000001f; os.println(""); closeTags.push(" "); } protected void writeCap(int cap) throws IOException { os.print(""); closeTags.push(" "); } protected void writeJoin(int join) throws IOException { os.print(""); closeTags.push(" "); } protected void writeMiterLimit(float limit) throws IOException { os.println(""); closeTags.push(" "); } protected void writeDash(double[] dash, double phase) throws IOException { os.print(" 0) { for (int i=0; i 0) s.append(","); s.append(fixedPrecision(dash[i])); } s.append(";"); } else { s.append("none;"); } s.append("stroke-dashoffset:"+fixedPrecision(phase)); os.println(style(s.toString())+">"); closeTags.push(" "); } /* 8.2. paint/color */ public void setPaintMode() { writeWarning(getClass()+": setPaintMode() not implemented."); } public void setXORMode(Color c1) { writeWarning(getClass()+": setXORMode(Color) not implemented."); } protected void writePaint(Color c) throws IOException { // written with every draw } protected void writePaint(GradientPaint paint) throws IOException { if (gradients.get(paint) == null) { String name = "gradient-"+gradients.size(); gradients.put(paint, name); GradientPaint gp = (GradientPaint)paint; Point2D p1 = gp.getPoint1(); Point2D p2 = gp.getPoint2(); os.println(""); os.print(" "); os.println(" "); os.println(" "); os.println(" "); os.println(""); } os.println(""); closeTags.push(" "); } protected void writePaint(TexturePaint paint) throws IOException { if (textures.get(paint) == null) { String name = "texture-"+textures.size(); textures.put(paint, name); TexturePaint tp = (TexturePaint)paint; BufferedImage image = tp.getImage(); Rectangle2D rect = tp.getAnchorRect(); os.println(""); os.print(" "); writeImage(image, null, null); os.println(" "); os.println(""); } os.println(""); closeTags.push(" "); } protected void writePaint(Paint p) throws IOException { writeWarning(getClass()+": writePaint(Paint) not implemented for "+p.getClass()); } private static final Properties replaceFonts = new Properties(); static { replaceFonts.setProperty("Dialog", "sans-serif"); replaceFonts.setProperty("DialogInput", "sans-serif"); replaceFonts.setProperty("Serif", "serif"); replaceFonts.setProperty("SansSerif", "sans-serif"); replaceFonts.setProperty("Monospaced", "monospace"); replaceFonts.setProperty("Symbol", "serif"); replaceFonts.setProperty("ZapfDingbats", "serif"); replaceFonts.setProperty("TimesRoman", "serif"); replaceFonts.setProperty("Helvetica", "sans-serif"); replaceFonts.setProperty("Courier", "monospace"); } /* 8.3. font */ /** * Method sets the current font. This method makes a reasonable * guess for the desired SVG font since the names of the * actual SVG fonts is implementation dependent. * Currently, this tries to identify Helvetica, Times, Courier, * Symbol, and ZapfDingbats fonts. If all else fails, Helvetica * is used. */ public void setFont(Font font) { super.setFont(font); StringBuffer svgFont = new StringBuffer(); svgFont.append("font-family:"); String fontName = font.getName(); svgFont.append(replaceFonts.getProperty(fontName, fontName)); if (font.isBold()) { svgFont.append(";font-weight:bold"); } else { svgFont.append(";font-weight:normal"); } if (font.isItalic()) { svgFont.append(";font-style:italic"); } else { svgFont.append(";font-style:normal"); } int size = font.getSize(); svgFont.append(";font-size:"+size); os.println(""); closeTags.push(" "); } /*================================================================================ | 9. Auxiliary *================================================================================*/ public GraphicsConfiguration getDeviceConfiguration() { writeWarning(getClass()+": getDeviceConfiguration() not implemented."); return null; } public boolean hit(Rectangle rect, Shape s, boolean onStroke) { writeWarning(getClass()+": hit(Rectangle, Shape, boolean) not implemented."); return false; } public void writeComment(String s) throws IOException { os.println(""); } public String toString() { return "SVGGraphics2D"; } /*================================================================================ | 10. Private/Utility Methos *================================================================================*/ private String color(Paint stroke, Paint fill) { StringBuffer s = new StringBuffer(); s.append("stroke:"); if (stroke != null) { s.append(hexColor(stroke)); s.append(";stroke-opacity:"); s.append(alphaColor(stroke)); } else { s.append("none"); } s.append(";"); s.append("fill:"); if (fill != null) { s.append(hexColor(fill)); s.append(";fill-opacity:"); s.append(alphaColor(fill)); } else { s.append("none"); } s.append(";"); return s.toString(); } private float alphaColor(Paint p) { if (p instanceof Color) { return (float)(getPrintColor((Color)p).getAlpha()/255.0); } else if (p instanceof GradientPaint) { return 1.0f; } else if (p instanceof TexturePaint) { return 1.0f; } writeWarning(getClass()+": alphaColor() not implemented for "+p.getClass()+"."); return 1.0f; } private String hexColor(Paint p) { if (p instanceof Color) { return hexColor(getPrintColor((Color)p)); } else if (p instanceof GradientPaint) { return hexColor((GradientPaint)p); } else if (p instanceof TexturePaint) { return hexColor((TexturePaint)p); } writeWarning(getClass()+": hexColor() not implemented for "+p.getClass()+"."); return "#000000"; } private String hexColor(Color c) { String s1 = Integer.toHexString(c.getRed()); s1 = (s1.length() != 2) ? "0"+s1 : s1; String s2 = Integer.toHexString(c.getGreen()); s2 = (s2.length() != 2) ? "0"+s2 : s2; String s3 = Integer.toHexString(c.getBlue()); s3 = (s3.length() != 2) ? "0"+s3 : s3; return "#"+s1+s2+s3; } private String hexColor(GradientPaint p) { return "url(#"+gradients.get(p)+")"; } private String hexColor(TexturePaint p) { return "url(#"+textures.get(p)+")"; } /** * Write the path to the output file. */ private void writePath(PathIterator path) { double[] coords = new double[6]; double currentX = 0.; double currentY = 0.; os.print(""); } private String clipPath() { return (currentClipNumber < 0) ? "" : "clip-path=\"url(#clip"+currentClipNumber+")\" "; } private String defaultStyle() { // FIXME: should be currentWidth... return style(color(getPaint(), null)+ "stroke-width:1;"+ "stroke-linecap:square"); } private String style(String stylableString) { return style(isProperty(STYLABLE), stylableString); } static String style(boolean stylable, String stylableString) { if ((stylableString == null) || (stylableString.equals(""))) return ""; if (stylable) return "style=\""+stylableString+"\""; StringBuffer r = new StringBuffer(); StringTokenizer st1 = new StringTokenizer(stylableString, ";"); while (st1.hasMoreTokens()) { String s = st1.nextToken(); int colon = s.indexOf(':'); if (colon >= 0) { r.append(s.substring(0,colon)); r.append("=\""); r.append(s.substring(colon+1)); r.append("\" "); } } return r.toString(); } private String getAlignmentString(int horizontal, int vertical, LineMetrics metrics) { String textAnchor; switch(horizontal) { case TEXT_CENTER: textAnchor = "middle"; break; case TEXT_RIGHT: textAnchor = "end"; break; case TEXT_LEFT: default: textAnchor = "start"; break; } // FIXME not a very good job yet. For tagstrings this does not work very well. double alignmentBaseline; switch(vertical) { case TEXT_TOP: alignmentBaseline = -100*(metrics.getAscent()+metrics.getLeading())/metrics.getHeight(); break; case TEXT_CENTER: alignmentBaseline = -50*metrics.getAscent()/metrics.getHeight(); break; case TEXT_BOTTOM: alignmentBaseline = metrics.getDescent()/metrics.getHeight(); break; case TEXT_BASELINE: default: alignmentBaseline = 0; break; } return "text-anchor:"+textAnchor+";"+"baseline-shift:"+fixedPrecision(alignmentBaseline)+"%"; } }