001/* 002 * SVG Salamander 003 * Copyright (c) 2004, Mark McKay 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or 007 * without modification, are permitted provided that the following 008 * conditions are met: 009 * 010 * - Redistributions of source code must retain the above 011 * copyright notice, this list of conditions and the following 012 * disclaimer. 013 * - Redistributions in binary form must reproduce the above 014 * copyright notice, this list of conditions and the following 015 * disclaimer in the documentation and/or other materials 016 * provided with the distribution. 017 * 018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 029 * OF THE POSSIBILITY OF SUCH DAMAGE. 030 * 031 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other 032 * projects can be found at http://www.kitfox.com 033 * 034 * Created on January 26, 2004, 1:56 AM 035 */ 036package com.kitfox.svg; 037 038import com.kitfox.svg.xml.StyleAttribute; 039import java.awt.Graphics2D; 040import java.awt.Shape; 041import java.awt.font.FontRenderContext; 042import java.awt.font.GlyphMetrics; 043import java.awt.font.GlyphVector; 044import java.awt.geom.AffineTransform; 045import java.awt.geom.GeneralPath; 046import java.awt.geom.Rectangle2D; 047 048/** 049 * @author Mark McKay 050 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 051 */ 052public class Tspan extends ShapeElement 053{ 054 055 public static final String TAG_NAME = "tspan"; 056 float[] x = null; 057 float[] y = null; 058 float[] dx = null; 059 float[] dy = null; 060 float[] rotate = null; 061 private String text = ""; 062 float cursorX; 063 float cursorY; 064 065// Shape tspanShape; 066 /** 067 * Creates a new instance of Stop 068 */ 069 public Tspan() 070 { 071 } 072 073 public String getTagName() 074 { 075 return TAG_NAME; 076 } 077 078 public float getCursorX() 079 { 080 return cursorX; 081 } 082 083 public float getCursorY() 084 { 085 return cursorY; 086 } 087 088 public void setCursorX(float cursorX) 089 { 090 this.cursorX = cursorX; 091 } 092 093 public void setCursorY(float cursorY) 094 { 095 this.cursorY = cursorY; 096 } 097 /* 098 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) 099 { 100 //Load style string 101 super.loaderStartElement(helper, attrs, parent); 102 103 String x = attrs.getValue("x"); 104 String y = attrs.getValue("y"); 105 String dx = attrs.getValue("dx"); 106 String dy = attrs.getValue("dy"); 107 String rotate = attrs.getValue("rotate"); 108 109 if (x != null) this.x = XMLParseUtil.parseFloatList(x); 110 if (y != null) this.y = XMLParseUtil.parseFloatList(y); 111 if (dx != null) this.dx = XMLParseUtil.parseFloatList(dx); 112 if (dy != null) this.dy = XMLParseUtil.parseFloatList(dy); 113 if (rotate != null) 114 { 115 this.rotate = XMLParseUtil.parseFloatList(rotate); 116 for (int i = 0; i < this.rotate.length; i++) 117 this.rotate[i] = (float)Math.toRadians(this.rotate[i]); 118 } 119 } 120 */ 121 122 /** 123 * Called during load process to add text scanned within a tag 124 */ 125 public void loaderAddText(SVGLoaderHelper helper, String text) 126 { 127 this.text += text; 128 } 129 130 protected void build() throws SVGException 131 { 132 super.build(); 133 134 StyleAttribute sty = new StyleAttribute(); 135 136 if (getPres(sty.setName("x"))) 137 { 138 x = sty.getFloatList(); 139 } 140 141 if (getPres(sty.setName("y"))) 142 { 143 y = sty.getFloatList(); 144 } 145 146 if (getPres(sty.setName("dx"))) 147 { 148 dx = sty.getFloatList(); 149 } 150 151 if (getPres(sty.setName("dy"))) 152 { 153 dy = sty.getFloatList(); 154 } 155 156 if (getPres(sty.setName("rotate"))) 157 { 158 rotate = sty.getFloatList(); 159 for (int i = 0; i < this.rotate.length; i++) 160 { 161 rotate[i] = (float) Math.toRadians(this.rotate[i]); 162 } 163 164 } 165 } 166 167 public void addShape(GeneralPath addShape) throws SVGException 168 { 169 if (x != null) 170 { 171 cursorX = x[0]; 172 } else if (dx != null) 173 { 174 cursorX += dx[0]; 175 } 176 177 if (y != null) 178 { 179 cursorY = y[0]; 180 } else if (dy != null) 181 { 182 cursorY += dy[0]; 183 } 184 185 StyleAttribute sty = new StyleAttribute(); 186 187 String fontFamily = null; 188 if (getStyle(sty.setName("font-family"))) 189 { 190 fontFamily = sty.getStringValue(); 191 } 192 193 194 float fontSize = 12f; 195 if (getStyle(sty.setName("font-size"))) 196 { 197 fontSize = sty.getFloatValueWithUnits(); 198 } 199 200 float letterSpacing = 0; 201 if (getStyle(sty.setName("letter-spacing"))) 202 { 203 letterSpacing = sty.getFloatValueWithUnits(); 204 } 205 206 207 //Get font 208 Font font = diagram.getUniverse().getFont(fontFamily); 209 if (font == null) 210 { 211 addShapeSysFont(addShape, font, fontFamily, fontSize, letterSpacing); 212 return; 213 } 214 215 FontFace fontFace = font.getFontFace(); 216 int ascent = fontFace.getAscent(); 217 float fontScale = fontSize / (float) ascent; 218 219 AffineTransform xform = new AffineTransform(); 220 221 strokeWidthScalar = 1f / fontScale; 222 223 int posPtr = 1; 224 225 for (int i = 0; i < text.length(); i++) 226 { 227 xform.setToIdentity(); 228 xform.setToTranslation(cursorX, cursorY); 229 xform.scale(fontScale, fontScale); 230 if (rotate != null) 231 { 232 xform.rotate(rotate[posPtr]); 233 } 234 235 String unicode = text.substring(i, i + 1); 236 MissingGlyph glyph = font.getGlyph(unicode); 237 238 Shape path = glyph.getPath(); 239 if (path != null) 240 { 241 path = xform.createTransformedShape(path); 242 addShape.append(path, false); 243 } 244 245 if (x != null && posPtr < x.length) 246 { 247 cursorX = x[posPtr]; 248 cursorY = y[posPtr++]; 249 } else if (dx != null && posPtr < dx.length) 250 { 251 cursorX += dx[posPtr]; 252 cursorY += dy[posPtr++]; 253 } 254 255 cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing; 256 } 257 258 strokeWidthScalar = 1f; 259 } 260 261 private void addShapeSysFont(GeneralPath addShape, Font font, 262 String fontFamily, float fontSize, float letterSpacing) 263 { 264 java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize); 265 266 FontRenderContext frc = new FontRenderContext(null, true, true); 267 GlyphVector textVector = sysFont.createGlyphVector(frc, text); 268 269 AffineTransform xform = new AffineTransform(); 270 271 int posPtr = 1; 272 for (int i = 0; i < text.length(); i++) 273 { 274 xform.setToIdentity(); 275 xform.setToTranslation(cursorX + i * letterSpacing, cursorY); 276 if (rotate != null) 277 { 278 xform.rotate(rotate[Math.min(i, rotate.length - 1)]); 279 } 280 281 String unicode = text.substring(i, i + 1); 282 Shape glyphOutline = textVector.getGlyphOutline(i); 283 GlyphMetrics glyphMetrics = textVector.getGlyphMetrics(i); 284 285 glyphOutline = xform.createTransformedShape(glyphOutline); 286 addShape.append(glyphOutline, false); 287 288 if (x != null && posPtr < x.length) 289 { 290 cursorX = x[posPtr]; 291 cursorY = y[posPtr++]; 292 } else if (dx != null && posPtr < dx.length) 293 { 294 cursorX += dx[posPtr]; 295 cursorY += dy[posPtr++]; 296 } 297 } 298 } 299 300 public void render(Graphics2D g) throws SVGException 301 { 302 if (x != null) 303 { 304 cursorX = x[0]; 305 cursorY = y[0]; 306 } else if (dx != null) 307 { 308 cursorX += dx[0]; 309 cursorY += dy[0]; 310 } 311 312 StyleAttribute sty = new StyleAttribute(); 313 314 String fontFamily = null; 315 if (getPres(sty.setName("font-family"))) 316 { 317 fontFamily = sty.getStringValue(); 318 } 319 320 321 float fontSize = 12f; 322 if (getPres(sty.setName("font-size"))) 323 { 324 fontSize = sty.getFloatValueWithUnits(); 325 } 326 327 //Get font 328 Font font = diagram.getUniverse().getFont(fontFamily); 329 if (font == null) 330 { 331 System.err.println("Could not load font"); 332 java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize); 333 renderSysFont(g, sysFont); 334 return; 335 } 336 337 338 FontFace fontFace = font.getFontFace(); 339 int ascent = fontFace.getAscent(); 340 float fontScale = fontSize / (float) ascent; 341 342 AffineTransform oldXform = g.getTransform(); 343 AffineTransform xform = new AffineTransform(); 344 345 strokeWidthScalar = 1f / fontScale; 346 347 int posPtr = 1; 348 349 for (int i = 0; i < text.length(); i++) 350 { 351 xform.setToTranslation(cursorX, cursorY); 352 xform.scale(fontScale, fontScale); 353 g.transform(xform); 354 355 String unicode = text.substring(i, i + 1); 356 MissingGlyph glyph = font.getGlyph(unicode); 357 358 Shape path = glyph.getPath(); 359 if (path != null) 360 { 361 renderShape(g, path); 362 } else 363 { 364 glyph.render(g); 365 } 366 367 if (x != null && posPtr < x.length) 368 { 369 cursorX = x[posPtr]; 370 cursorY = y[posPtr++]; 371 } else if (dx != null && posPtr < dx.length) 372 { 373 cursorX += dx[posPtr]; 374 cursorY += dy[posPtr++]; 375 } 376 377 cursorX += fontScale * glyph.getHorizAdvX(); 378 379 g.setTransform(oldXform); 380 } 381 382 strokeWidthScalar = 1f; 383 } 384 385 protected void renderSysFont(Graphics2D g, java.awt.Font font) throws SVGException 386 { 387 int posPtr = 1; 388 FontRenderContext frc = g.getFontRenderContext(); 389 390 Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY); 391 renderShape(g, textShape); 392 Rectangle2D rect = font.getStringBounds(text, frc); 393 cursorX += (float) rect.getWidth(); 394 } 395 396 public Shape getShape() 397 { 398 return null; 399 //return shapeToParent(tspanShape); 400 } 401 402 public Rectangle2D getBoundingBox() 403 { 404 return null; 405 //return boundsToParent(tspanShape.getBounds2D()); 406 } 407 408 /** 409 * Updates all attributes in this diagram associated with a time event. Ie, 410 * all attributes with track information. 411 * 412 * @return - true if this node has changed state as a result of the time 413 * update 414 */ 415 public boolean updateTime(double curTime) throws SVGException 416 { 417 //Tspan does not change 418 return false; 419 } 420 421 public String getText() 422 { 423 return text; 424 } 425 426 public void setText(String text) 427 { 428 this.text = text; 429 } 430}