/* * 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 com.kitfox.svg.xml.StyleAttribute; import java.awt.*; import java.awt.font.*; import java.awt.geom.*; import java.util.*; import java.util.regex.*; //import org.apache.batik.ext.awt.geom.ExtendedGeneralPath; /** * @author Mark McKay * @author Mark McKay */ public class Text extends ShapeElement { float x = 0; float y = 0; AffineTransform transform = null; String fontFamily; float fontSize; //List of strings and tspans containing the content of this node LinkedList content = new LinkedList(); Shape textShape; public static final int TXAN_START = 0; public static final int TXAN_MIDDLE = 1; public static final int TXAN_END = 2; int textAnchor = TXAN_START; public static final int TXST_NORMAL = 0; public static final int TXST_ITALIC = 1; public static final int TXST_OBLIQUE = 2; int fontStyle; public static final int TXWE_NORMAL = 0; public static final int TXWE_BOLD = 1; public static final int TXWE_BOLDER = 2; public static final int TXWE_LIGHTER = 3; public static final int TXWE_100 = 4; public static final int TXWE_200 = 5; public static final int TXWE_300 = 6; public static final int TXWE_400 = 7; public static final int TXWE_500 = 8; public static final int TXWE_600 = 9; public static final int TXWE_700 = 10; public static final int TXWE_800 = 11; public static final int TXWE_900 = 12; int fontWeight; /** Creates a new instance of Stop */ public Text() { } public void appendText(String text) { content.addLast(text); } public void appendTspan(Tspan tspan) throws SVGElementException { super.loaderAddChild(null, tspan); content.addLast(tspan); // tspan.setParent(this); } /** * Discard cached information */ public void rebuild() throws SVGException { build(); } public java.util.List getContent() { return content; } /* 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 transform = attrs.getValue("transform"); this.x = XMLParseUtil.parseFloat(x); this.y = XMLParseUtil.parseFloat(y); } */ /** * Called after the start element but before the end element to indicate * each child tag that has been processed */ public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException { super.loaderAddChild(helper, child); content.addLast(child); } /** * Called during load process to add text scanned within a tag */ public void loaderAddText(SVGLoaderHelper helper, String text) { Matcher matchWs = Pattern.compile("\\s*").matcher(text); if (!matchWs.matches()) content.addLast(text); } public void build() throws SVGException { super.build(); StyleAttribute sty = new StyleAttribute(); if (getPres(sty.setName("x"))) x = sty.getFloatValueWithUnits(); if (getPres(sty.setName("y"))) y = sty.getFloatValueWithUnits(); if (getStyle(sty.setName("font-family"))) fontFamily = sty.getStringValue(); else fontFamily = "Sans Serif"; if (getStyle(sty.setName("font-size"))) fontSize = sty.getFloatValueWithUnits(); else fontSize = 12f; if (getStyle(sty.setName("font-style"))) { String s = sty.getStringValue(); if ("normal".equals(s)) { fontStyle = TXST_NORMAL; } else if ("italic".equals(s)) { fontStyle = TXST_ITALIC; } else if ("oblique".equals(s)) { fontStyle = TXST_OBLIQUE; } } else fontStyle = TXST_NORMAL; if (getStyle(sty.setName("font-weight"))) { String s = sty.getStringValue(); if ("normal".equals(s)) { fontWeight = TXWE_NORMAL; } else if ("bold".equals(s)) { fontWeight = TXWE_BOLD; } } else fontWeight = TXWE_BOLD; if (getStyle(sty.setName("text-anchor"))) { String s = sty.getStringValue(); if (s.equals("middle")) textAnchor = TXAN_MIDDLE; else if (s.equals("end")) textAnchor = TXAN_END; else textAnchor = TXAN_START; } else textAnchor = TXAN_START; //text anchor //text-decoration //text-rendering buildFont(); } protected void buildFont() throws SVGException { int style; switch (fontStyle) { case TXST_ITALIC: style = java.awt.Font.ITALIC; break; default: style = java.awt.Font.PLAIN; break; } int weight; switch (fontWeight) { case TXWE_BOLD: case TXWE_BOLDER: weight = java.awt.Font.BOLD; break; default: weight = java.awt.Font.PLAIN; break; } //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, style | weight, (int)fontSize); buildSysFont(sysFont); return; } // font = new java.awt.Font(font.getFamily(), style | weight, font.getSize()); // Area textArea = new Area(); GeneralPath textPath = new GeneralPath(); textShape = textPath; float cursorX = x, cursorY = y; FontFace fontFace = font.getFontFace(); //int unitsPerEm = fontFace.getUnitsPerEm(); int ascent = fontFace.getAscent(); float fontScale = fontSize / (float)ascent; // AffineTransform oldXform = g.getTransform(); AffineTransform xform = new AffineTransform(); for (Iterator it = content.iterator(); it.hasNext();) { Object obj = it.next(); if (obj instanceof String) { String text = (String)obj; if (text != null) { text = text.trim(); } strokeWidthScalar = 1f / fontScale; for (int i = 0; i < text.length(); i++) { xform.setToIdentity(); 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) { path = xform.createTransformedShape(path); textPath.append(path, false); } // else glyph.render(g); cursorX += fontScale * glyph.getHorizAdvX(); // g.setTransform(oldXform); } strokeWidthScalar = 1f; } else if (obj instanceof Tspan) { Tspan tspan = (Tspan)obj; xform.setToIdentity(); xform.setToTranslation(cursorX, cursorY); xform.scale(fontScale, fontScale); // tspan.setCursorX(cursorX); // tspan.setCursorY(cursorY); Shape tspanShape = tspan.getShape(); tspanShape = xform.createTransformedShape(tspanShape); textPath.append(tspanShape, false); // tspan.render(g); // cursorX = tspan.getCursorX(); // cursorY = tspan.getCursorY(); } } switch (textAnchor) { case TXAN_MIDDLE: { AffineTransform at = new AffineTransform(); at.translate(-textPath.getBounds2D().getWidth() / 2, 0); textPath.transform(at); break; } case TXAN_END: { AffineTransform at = new AffineTransform(); at.translate(-textPath.getBounds2D().getWidth(), 0); textPath.transform(at); break; } } } private void buildSysFont(java.awt.Font font) throws SVGException { GeneralPath textPath = new GeneralPath(); textShape = textPath; float cursorX = x, cursorY = y; // FontMetrics fm = g.getFontMetrics(font); FontRenderContext frc = new FontRenderContext(null, true, true); // FontFace fontFace = font.getFontFace(); //int unitsPerEm = fontFace.getUnitsPerEm(); // int ascent = fm.getAscent(); // float fontScale = fontSize / (float)ascent; // AffineTransform oldXform = g.getTransform(); AffineTransform xform = new AffineTransform(); for (Iterator it = content.iterator(); it.hasNext();) { Object obj = it.next(); if (obj instanceof String) { String text = (String)obj; Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY); textPath.append(textShape, false); // renderShape(g, textShape); // g.drawString(text, cursorX, cursorY); Rectangle2D rect = font.getStringBounds(text, frc); cursorX += (float)rect.getWidth(); } else if (obj instanceof Tspan) { /* Tspan tspan = (Tspan)obj; xform.setToIdentity(); xform.setToTranslation(cursorX, cursorY); Shape tspanShape = tspan.getShape(); tspanShape = xform.createTransformedShape(tspanShape); textArea.add(new Area(tspanShape)); cursorX += tspanShape.getBounds2D().getWidth(); */ Tspan tspan = (Tspan)obj; tspan.setCursorX(cursorX); tspan.setCursorY(cursorY); tspan.addShape(textPath); cursorX = tspan.getCursorX(); cursorY = tspan.getCursorY(); } } switch (textAnchor) { case TXAN_MIDDLE: { AffineTransform at = new AffineTransform(); at.translate(-textPath.getBounds2D().getWidth() / 2, 0); textPath.transform(at); break; } case TXAN_END: { AffineTransform at = new AffineTransform(); at.translate(-textPath.getBounds2D().getWidth(), 0); textPath.transform(at); break; } } } public void render(Graphics2D g) throws SVGException { beginLayer(g); renderShape(g, textShape); finishLayer(g); } public Shape getShape() { return shapeToParent(textShape); } public Rectangle2D getBoundingBox() throws SVGException { return boundsToParent(includeStrokeInBounds(textShape.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 */ public boolean updateTime(double curTime) throws SVGException { // if (trackManager.getNumTracks() == 0) return false; boolean changeState = super.updateTime(curTime); //Get current values for parameters StyleAttribute sty = new StyleAttribute(); boolean shapeChange = false; if (getPres(sty.setName("x"))) { float newVal = sty.getFloatValueWithUnits(); if (newVal != x) { x = newVal; shapeChange = true; } } if (getPres(sty.setName("y"))) { float newVal = sty.getFloatValueWithUnits(); if (newVal != y) { y = newVal; shapeChange = true; } } if (getPres(sty.setName("font-family"))) { String newVal = sty.getStringValue(); if (!newVal.equals(fontFamily)) { fontFamily = newVal; shapeChange = true; } } if (getPres(sty.setName("font-size"))) { float newVal = sty.getFloatValueWithUnits(); if (newVal != fontSize) { fontSize = newVal; shapeChange = true; } } if (getStyle(sty.setName("font-style"))) { String s = sty.getStringValue(); int newVal = fontStyle; if ("normal".equals(s)) { newVal = TXST_NORMAL; } else if ("italic".equals(s)) { newVal = TXST_ITALIC; } else if ("oblique".equals(s)) { newVal = TXST_OBLIQUE; } if (newVal != fontStyle) { fontStyle = newVal; shapeChange = true; } } if (getStyle(sty.setName("font-weight"))) { String s = sty.getStringValue(); int newVal = fontWeight; if ("normal".equals(s)) { newVal = TXWE_NORMAL; } else if ("bold".equals(s)) { newVal = TXWE_BOLD; } if (newVal != fontWeight) { fontWeight = newVal; shapeChange = true; } } if (shapeChange) { build(); // buildFont(); // return true; } return changeState || shapeChange; } }