/* * SVG Salamander * Copyright (c) 2004, Mark McKay * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * - 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. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS 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 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, 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. * * Mark McKay can be contacted at mark@kitfox.com. Salamander and other * projects can be found at http://www.kitfox.com * * Created on January 26, 2004, 1:56 AM */ package com.kitfox.svg; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import com.kitfox.svg.util.FontSystem; import com.kitfox.svg.xml.StyleAttribute; /** * @author Mark McKay * @author Mark McKay */ public class Tspan extends ShapeElement { public static final String TAG_NAME = "tspan"; float[] x = null; float[] y = null; float[] dx = null; float[] dy = null; float[] rotate = null; private String text = ""; // float cursorX; // float cursorY; // Shape tspanShape; /** * Creates a new instance of Stop */ public Tspan() { } @Override public String getTagName() { return TAG_NAME; } // public float getCursorX() // { // return cursorX; // } // // public float getCursorY() // { // return cursorY; // } // // public void setCursorX(float cursorX) // { // this.cursorX = cursorX; // } // // public void setCursorY(float cursorY) // { // this.cursorY = cursorY; // } /* public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) { //Load style string super.loaderStartElement(helper, attrs, parent); String x = attrs.getValue("x"); String y = attrs.getValue("y"); String dx = attrs.getValue("dx"); String dy = attrs.getValue("dy"); String rotate = attrs.getValue("rotate"); if (x != null) this.x = XMLParseUtil.parseFloatList(x); if (y != null) this.y = XMLParseUtil.parseFloatList(y); if (dx != null) this.dx = XMLParseUtil.parseFloatList(dx); if (dy != null) this.dy = XMLParseUtil.parseFloatList(dy); if (rotate != null) { this.rotate = XMLParseUtil.parseFloatList(rotate); for (int i = 0; i < this.rotate.length; i++) this.rotate[i] = (float)Math.toRadians(this.rotate[i]); } } */ /** * Called during load process to add text scanned within a tag */ @Override public void loaderAddText(SVGLoaderHelper helper, String text) { this.text += text; } @Override protected void build() throws SVGException { super.build(); StyleAttribute sty = new StyleAttribute(); if (getPres(sty.setName("x"))) { x = sty.getFloatList(); } if (getPres(sty.setName("y"))) { y = sty.getFloatList(); } if (getPres(sty.setName("dx"))) { dx = sty.getFloatList(); } if (getPres(sty.setName("dy"))) { dy = sty.getFloatList(); } if (getPres(sty.setName("rotate"))) { rotate = sty.getFloatList(); for (int i = 0; i < this.rotate.length; i++) { rotate[i] = (float) Math.toRadians(this.rotate[i]); } } } public void appendToShape(GeneralPath addShape, Point2D cursor) throws SVGException { StyleAttribute sty = new StyleAttribute(); String fontFamily = null; if (getStyle(sty.setName("font-family"))) { fontFamily = sty.getStringValue(); } float fontSize = 12f; if (getStyle(sty.setName("font-size"))) { fontSize = sty.getFloatValueWithUnits(); } float letterSpacing = 0; if (getStyle(sty.setName("letter-spacing"))) { letterSpacing = sty.getFloatValueWithUnits(); } int fontStyle = 0; if (getStyle(sty.setName("font-style"))) { String s = sty.getStringValue(); if ("normal".equals(s)) { fontStyle = Text.TXST_NORMAL; } else if ("italic".equals(s)) { fontStyle = Text.TXST_ITALIC; } else if ("oblique".equals(s)) { fontStyle = Text.TXST_OBLIQUE; } } else { fontStyle = Text.TXST_NORMAL; } int fontWeight = 0; if (getStyle(sty.setName("font-weight"))) { String s = sty.getStringValue(); if ("normal".equals(s)) { fontWeight = Text.TXWE_NORMAL; } else if ("bold".equals(s)) { fontWeight = Text.TXWE_BOLD; } } else { fontWeight = Text.TXWE_NORMAL; } //Get font Font font = diagram.getUniverse().getFont(fontFamily); if (font == null && fontFamily != null) { font = FontSystem.createFont(fontFamily, fontStyle, fontWeight, (int)fontSize); } if (font == null) { font = FontSystem.createFont("Serif", fontStyle, fontWeight, fontStyle); } AffineTransform xform = new AffineTransform(); float cursorX = (float)cursor.getX(); float cursorY = (float)cursor.getY(); String drawText = this.text; drawText = drawText.trim(); for (int i = 0; i < drawText.length(); i++) { if (x != null && i < x.length) { cursorX = x[i]; } else if (dx != null && i < dx.length) { cursorX += dx[i]; } if (y != null && i < y.length) { cursorY = y[i]; } else if (dy != null && i < dy.length) { cursorY += dy[i]; } xform.setToIdentity(); xform.setToTranslation(cursorX, cursorY); if (rotate != null) { xform.rotate(rotate[i]); } String unicode = drawText.substring(i, i + 1); MissingGlyph glyph = font.getGlyph(unicode); Shape path = glyph.getPath(); if (path != null) { path = xform.createTransformedShape(path); addShape.append(path, false); } cursorX += glyph.getHorizAdvX() + letterSpacing; } //Save final draw point so calling method knows where to begin next // text draw cursor.setLocation(cursorX, cursorY); strokeWidthScalar = 1f; } // private void addShapeSysFont(GeneralPath addShape, Font font, // String fontFamily, float fontSize, float letterSpacing, Point2D cursor) // { // // java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize); // // FontRenderContext frc = new FontRenderContext(null, true, true); // String renderText = this.text.trim(); // // AffineTransform xform = new AffineTransform(); // // float cursorX = (float)cursor.getX(); // float cursorY = (float)cursor.getY(); //// int i = 0; // for (int i = 0; i < renderText.length(); i++) // { // if (x != null && i < x.length) // { // cursorX = x[i]; // } else if (dx != null && i < dx.length) // { // cursorX += dx[i]; // } // // if (y != null && i < y.length) // { // cursorY = y[i]; // } else if (dy != null && i < dy.length) // { // cursorY += dy[i]; // } //// i++; // // xform.setToIdentity(); // xform.setToTranslation(cursorX, cursorY); // if (rotate != null) // { // xform.rotate(rotate[Math.min(i, rotate.length - 1)]); // } // //// String unicode = renderText.substring(i, i + 1); // GlyphVector textVector = sysFont.createGlyphVector(frc, renderText.substring(i, i + 1)); // Shape glyphOutline = textVector.getGlyphOutline(0); // GlyphMetrics glyphMetrics = textVector.getGlyphMetrics(0); // // glyphOutline = xform.createTransformedShape(glyphOutline); // addShape.append(glyphOutline, false); // // //// cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing; // cursorX += glyphMetrics.getAdvance() + letterSpacing; // } // // cursor.setLocation(cursorX, cursorY); // } @Override public void render(Graphics2D g) throws SVGException { float cursorX = 0; float cursorY = 0; if (x != null) { cursorX = x[0]; cursorY = y[0]; } else if (dx != null) { cursorX += dx[0]; cursorY += dy[0]; } StyleAttribute sty = new StyleAttribute(); String fontFamily = null; if (getPres(sty.setName("font-family"))) { fontFamily = sty.getStringValue(); } float fontSize = 12f; if (getPres(sty.setName("font-size"))) { fontSize = sty.getFloatValueWithUnits(); } //Get font Font font = diagram.getUniverse().getFont(fontFamily); if (font == null) { System.err.println("Could not load font"); java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize); renderSysFont(g, sysFont); return; } FontFace fontFace = font.getFontFace(); int ascent = fontFace.getAscent(); float fontScale = fontSize / (float) ascent; AffineTransform oldXform = g.getTransform(); AffineTransform xform = new AffineTransform(); strokeWidthScalar = 1f / fontScale; int posPtr = 1; for (int i = 0; i < text.length(); i++) { xform.setToTranslation(cursorX, cursorY); xform.scale(fontScale, fontScale); g.transform(xform); String unicode = text.substring(i, i + 1); MissingGlyph glyph = font.getGlyph(unicode); Shape path = glyph.getPath(); if (path != null) { renderShape(g, path); } else { glyph.render(g); } if (x != null && posPtr < x.length) { cursorX = x[posPtr]; cursorY = y[posPtr++]; } else if (dx != null && posPtr < dx.length) { cursorX += dx[posPtr]; cursorY += dy[posPtr++]; } cursorX += fontScale * glyph.getHorizAdvX(); g.setTransform(oldXform); } strokeWidthScalar = 1f; } protected void renderSysFont(Graphics2D g, java.awt.Font font) throws SVGException { float cursorX = 0; float cursorY = 0; FontRenderContext frc = g.getFontRenderContext(); Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY); renderShape(g, textShape); Rectangle2D rect = font.getStringBounds(text, frc); cursorX += (float) rect.getWidth(); } @Override public Shape getShape() { return null; //return shapeToParent(tspanShape); } @Override public Rectangle2D getBoundingBox() { return null; //return boundsToParent(tspanShape.getBounds2D()); } /** * Updates all attributes in this diagram associated with a time event. Ie, * all attributes with track information. * * @return - true if this node has changed state as a result of the time * update */ @Override public boolean updateTime(double curTime) throws SVGException { //Tspan does not change return false; } public String getText() { return text; } public void setText(String text) { this.text = text; } }