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.Point2D; 047import java.awt.geom.Rectangle2D; 048 049/** 050 * @author Mark McKay 051 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 052 */ 053public class Tspan extends ShapeElement 054{ 055 056 public static final String TAG_NAME = "tspan"; 057 float[] x = null; 058 float[] y = null; 059 float[] dx = null; 060 float[] dy = null; 061 float[] rotate = null; 062 private String text = ""; 063// float cursorX; 064// float cursorY; 065 066// Shape tspanShape; 067 /** 068 * Creates a new instance of Stop 069 */ 070 public Tspan() 071 { 072 } 073 074 public String getTagName() 075 { 076 return TAG_NAME; 077 } 078 079// public float getCursorX() 080// { 081// return cursorX; 082// } 083// 084// public float getCursorY() 085// { 086// return cursorY; 087// } 088// 089// public void setCursorX(float cursorX) 090// { 091// this.cursorX = cursorX; 092// } 093// 094// public void setCursorY(float cursorY) 095// { 096// this.cursorY = cursorY; 097// } 098 /* 099 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) 100 { 101 //Load style string 102 super.loaderStartElement(helper, attrs, parent); 103 104 String x = attrs.getValue("x"); 105 String y = attrs.getValue("y"); 106 String dx = attrs.getValue("dx"); 107 String dy = attrs.getValue("dy"); 108 String rotate = attrs.getValue("rotate"); 109 110 if (x != null) this.x = XMLParseUtil.parseFloatList(x); 111 if (y != null) this.y = XMLParseUtil.parseFloatList(y); 112 if (dx != null) this.dx = XMLParseUtil.parseFloatList(dx); 113 if (dy != null) this.dy = XMLParseUtil.parseFloatList(dy); 114 if (rotate != null) 115 { 116 this.rotate = XMLParseUtil.parseFloatList(rotate); 117 for (int i = 0; i < this.rotate.length; i++) 118 this.rotate[i] = (float)Math.toRadians(this.rotate[i]); 119 } 120 } 121 */ 122 123 /** 124 * Called during load process to add text scanned within a tag 125 */ 126 public void loaderAddText(SVGLoaderHelper helper, String text) 127 { 128 this.text += text; 129 } 130 131 protected void build() throws SVGException 132 { 133 super.build(); 134 135 StyleAttribute sty = new StyleAttribute(); 136 137 if (getPres(sty.setName("x"))) 138 { 139 x = sty.getFloatList(); 140 } 141 142 if (getPres(sty.setName("y"))) 143 { 144 y = sty.getFloatList(); 145 } 146 147 if (getPres(sty.setName("dx"))) 148 { 149 dx = sty.getFloatList(); 150 } 151 152 if (getPres(sty.setName("dy"))) 153 { 154 dy = sty.getFloatList(); 155 } 156 157 if (getPres(sty.setName("rotate"))) 158 { 159 rotate = sty.getFloatList(); 160 for (int i = 0; i < this.rotate.length; i++) 161 { 162 rotate[i] = (float) Math.toRadians(this.rotate[i]); 163 } 164 165 } 166 } 167 168 public void appendToShape(GeneralPath addShape, Point2D cursor) throws SVGException 169 { 170// if (x != null) 171// { 172// cursorX = x[0]; 173// } else if (dx != null) 174// { 175// cursorX += dx[0]; 176// } 177// 178// if (y != null) 179// { 180// cursorY = y[0]; 181// } else if (dy != null) 182// { 183// cursorY += dy[0]; 184// } 185 186 StyleAttribute sty = new StyleAttribute(); 187 188 String fontFamily = null; 189 if (getStyle(sty.setName("font-family"))) 190 { 191 fontFamily = sty.getStringValue(); 192 } 193 194 195 float fontSize = 12f; 196 if (getStyle(sty.setName("font-size"))) 197 { 198 fontSize = sty.getFloatValueWithUnits(); 199 } 200 201 float letterSpacing = 0; 202 if (getStyle(sty.setName("letter-spacing"))) 203 { 204 letterSpacing = sty.getFloatValueWithUnits(); 205 } 206 207 208 //Get font 209 Font font = diagram.getUniverse().getFont(fontFamily); 210 if (font == null) 211 { 212 addShapeSysFont(addShape, font, fontFamily, fontSize, letterSpacing, cursor); 213 return; 214 } 215 216 FontFace fontFace = font.getFontFace(); 217 int ascent = fontFace.getAscent(); 218 float fontScale = fontSize / (float) ascent; 219 220 AffineTransform xform = new AffineTransform(); 221 222 strokeWidthScalar = 1f / fontScale; 223 224 float cursorX = (float)cursor.getX(); 225 float cursorY = (float)cursor.getY(); 226 227// int i = 0; 228 229 String drawText = this.text; 230 drawText = drawText.trim(); 231 for (int i = 0; i < drawText.length(); i++) 232 { 233 if (x != null && i < x.length) 234 { 235 cursorX = x[i]; 236 } else if (dx != null && i < dx.length) 237 { 238 cursorX += dx[i]; 239 } 240 241 if (y != null && i < y.length) 242 { 243 cursorY = y[i]; 244 } else if (dy != null && i < dy.length) 245 { 246 cursorY += dy[i]; 247 } 248 // i++; 249 250 xform.setToIdentity(); 251 xform.setToTranslation(cursorX, cursorY); 252 xform.scale(fontScale, fontScale); 253 if (rotate != null) 254 { 255 xform.rotate(rotate[i]); 256 } 257 258 String unicode = drawText.substring(i, i + 1); 259 MissingGlyph glyph = font.getGlyph(unicode); 260 261 Shape path = glyph.getPath(); 262 if (path != null) 263 { 264 path = xform.createTransformedShape(path); 265 addShape.append(path, false); 266 } 267 268 cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing; 269 } 270 271 //Save final draw point so calling method knows where to begin next 272 // text draw 273 cursor.setLocation(cursorX, cursorY); 274 strokeWidthScalar = 1f; 275 } 276 277 private void addShapeSysFont(GeneralPath addShape, Font font, 278 String fontFamily, float fontSize, float letterSpacing, Point2D cursor) 279 { 280 281 java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize); 282 283 FontRenderContext frc = new FontRenderContext(null, true, true); 284 String renderText = this.text.trim(); 285 286 AffineTransform xform = new AffineTransform(); 287 288 float cursorX = (float)cursor.getX(); 289 float cursorY = (float)cursor.getY(); 290// int i = 0; 291 for (int i = 0; i < renderText.length(); i++) 292 { 293 if (x != null && i < x.length) 294 { 295 cursorX = x[i]; 296 } else if (dx != null && i < dx.length) 297 { 298 cursorX += dx[i]; 299 } 300 301 if (y != null && i < y.length) 302 { 303 cursorY = y[i]; 304 } else if (dy != null && i < dy.length) 305 { 306 cursorY += dy[i]; 307 } 308// i++; 309 310 xform.setToIdentity(); 311 xform.setToTranslation(cursorX, cursorY); 312 if (rotate != null) 313 { 314 xform.rotate(rotate[Math.min(i, rotate.length - 1)]); 315 } 316 317// String unicode = renderText.substring(i, i + 1); 318 GlyphVector textVector = sysFont.createGlyphVector(frc, renderText.substring(i, i + 1)); 319 Shape glyphOutline = textVector.getGlyphOutline(0); 320 GlyphMetrics glyphMetrics = textVector.getGlyphMetrics(0); 321 322 glyphOutline = xform.createTransformedShape(glyphOutline); 323 addShape.append(glyphOutline, false); 324 325 326// cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing; 327 cursorX += glyphMetrics.getAdvance() + letterSpacing; 328 } 329 330 cursor.setLocation(cursorX, cursorY); 331 } 332 333 public void render(Graphics2D g) throws SVGException 334 { 335 float cursorX = 0; 336 float cursorY = 0; 337 338 if (x != null) 339 { 340 cursorX = x[0]; 341 cursorY = y[0]; 342 } else if (dx != null) 343 { 344 cursorX += dx[0]; 345 cursorY += dy[0]; 346 } 347 348 StyleAttribute sty = new StyleAttribute(); 349 350 String fontFamily = null; 351 if (getPres(sty.setName("font-family"))) 352 { 353 fontFamily = sty.getStringValue(); 354 } 355 356 357 float fontSize = 12f; 358 if (getPres(sty.setName("font-size"))) 359 { 360 fontSize = sty.getFloatValueWithUnits(); 361 } 362 363 //Get font 364 Font font = diagram.getUniverse().getFont(fontFamily); 365 if (font == null) 366 { 367 System.err.println("Could not load font"); 368 java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize); 369 renderSysFont(g, sysFont); 370 return; 371 } 372 373 374 FontFace fontFace = font.getFontFace(); 375 int ascent = fontFace.getAscent(); 376 float fontScale = fontSize / (float) ascent; 377 378 AffineTransform oldXform = g.getTransform(); 379 AffineTransform xform = new AffineTransform(); 380 381 strokeWidthScalar = 1f / fontScale; 382 383 int posPtr = 1; 384 385 for (int i = 0; i < text.length(); i++) 386 { 387 xform.setToTranslation(cursorX, cursorY); 388 xform.scale(fontScale, fontScale); 389 g.transform(xform); 390 391 String unicode = text.substring(i, i + 1); 392 MissingGlyph glyph = font.getGlyph(unicode); 393 394 Shape path = glyph.getPath(); 395 if (path != null) 396 { 397 renderShape(g, path); 398 } else 399 { 400 glyph.render(g); 401 } 402 403 if (x != null && posPtr < x.length) 404 { 405 cursorX = x[posPtr]; 406 cursorY = y[posPtr++]; 407 } else if (dx != null && posPtr < dx.length) 408 { 409 cursorX += dx[posPtr]; 410 cursorY += dy[posPtr++]; 411 } 412 413 cursorX += fontScale * glyph.getHorizAdvX(); 414 415 g.setTransform(oldXform); 416 } 417 418 strokeWidthScalar = 1f; 419 } 420 421 protected void renderSysFont(Graphics2D g, java.awt.Font font) throws SVGException 422 { 423 float cursorX = 0; 424 float cursorY = 0; 425 426 int posPtr = 1; 427 FontRenderContext frc = g.getFontRenderContext(); 428 429 Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY); 430 renderShape(g, textShape); 431 Rectangle2D rect = font.getStringBounds(text, frc); 432 cursorX += (float) rect.getWidth(); 433 } 434 435 public Shape getShape() 436 { 437 return null; 438 //return shapeToParent(tspanShape); 439 } 440 441 public Rectangle2D getBoundingBox() 442 { 443 return null; 444 //return boundsToParent(tspanShape.getBounds2D()); 445 } 446 447 /** 448 * Updates all attributes in this diagram associated with a time event. Ie, 449 * all attributes with track information. 450 * 451 * @return - true if this node has changed state as a result of the time 452 * update 453 */ 454 public boolean updateTime(double curTime) throws SVGException 455 { 456 //Tspan does not change 457 return false; 458 } 459 460 public String getText() 461 { 462 return text; 463 } 464 465 public void setText(String text) 466 { 467 this.text = text; 468 } 469}