/* * 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.font.GlyphMetrics; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import com.kitfox.svg.xml.StyleAttribute; /** * @author Mark McKay * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> */ 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 { // if (x != null) // { // cursorX = x[0]; // } else if (dx != null) // { // cursorX += dx[0]; // } // // if (y != null) // { // cursorY = y[0]; // } else if (dy != null) // { // cursorY += dy[0]; // } 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(); } // Get font Font font = diagram.getUniverse().getFont(fontFamily); if (font == null) { addShapeSysFont(addShape, null, fontFamily, fontSize, letterSpacing, cursor); return; } FontFace fontFace = font.getFontFace(); int ascent = fontFace.getAscent(); float fontScale = fontSize / ascent; AffineTransform xform = new AffineTransform(); strokeWidthScalar = 1f / fontScale; float cursorX = (float) cursor.getX(); float cursorY = (float) cursor.getY(); // int i = 0; 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]; } // i++; xform.setToIdentity(); xform.setToTranslation(cursorX, cursorY); xform.scale(fontScale, fontScale); 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 += fontScale * 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 / 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; } }