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.util.FontSystem; 039import com.kitfox.svg.xml.StyleAttribute; 040import java.awt.Graphics2D; 041import java.awt.Shape; 042import java.awt.font.FontRenderContext; 043import java.awt.geom.AffineTransform; 044import java.awt.geom.GeneralPath; 045import java.awt.geom.Point2D; 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 @Override 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 @Override 127 public void loaderAddText(SVGLoaderHelper helper, String text) 128 { 129 this.text += text; 130 } 131 132 @Override 133 protected void build() throws SVGException 134 { 135 super.build(); 136 137 StyleAttribute sty = new StyleAttribute(); 138 139 if (getPres(sty.setName("x"))) 140 { 141 x = sty.getFloatList(); 142 } 143 144 if (getPres(sty.setName("y"))) 145 { 146 y = sty.getFloatList(); 147 } 148 149 if (getPres(sty.setName("dx"))) 150 { 151 dx = sty.getFloatList(); 152 } 153 154 if (getPres(sty.setName("dy"))) 155 { 156 dy = sty.getFloatList(); 157 } 158 159 if (getPres(sty.setName("rotate"))) 160 { 161 rotate = sty.getFloatList(); 162 for (int i = 0; i < this.rotate.length; i++) 163 { 164 rotate[i] = (float) Math.toRadians(this.rotate[i]); 165 } 166 167 } 168 } 169 170 public void appendToShape(GeneralPath addShape, Point2D cursor) throws SVGException 171 { 172 StyleAttribute sty = new StyleAttribute(); 173 174 String fontFamily = null; 175 if (getStyle(sty.setName("font-family"))) 176 { 177 fontFamily = sty.getStringValue(); 178 } 179 180 181 float fontSize = 12f; 182 if (getStyle(sty.setName("font-size"))) 183 { 184 fontSize = sty.getFloatValueWithUnits(); 185 } 186 187 float letterSpacing = 0; 188 if (getStyle(sty.setName("letter-spacing"))) 189 { 190 letterSpacing = sty.getFloatValueWithUnits(); 191 } 192 193 int fontStyle = 0; 194 if (getStyle(sty.setName("font-style"))) 195 { 196 String s = sty.getStringValue(); 197 if ("normal".equals(s)) 198 { 199 fontStyle = Text.TXST_NORMAL; 200 } else if ("italic".equals(s)) 201 { 202 fontStyle = Text.TXST_ITALIC; 203 } else if ("oblique".equals(s)) 204 { 205 fontStyle = Text.TXST_OBLIQUE; 206 } 207 } else 208 { 209 fontStyle = Text.TXST_NORMAL; 210 } 211 212 int fontWeight = 0; 213 if (getStyle(sty.setName("font-weight"))) 214 { 215 String s = sty.getStringValue(); 216 if ("normal".equals(s)) 217 { 218 fontWeight = Text.TXWE_NORMAL; 219 } else if ("bold".equals(s)) 220 { 221 fontWeight = Text.TXWE_BOLD; 222 } 223 } else 224 { 225 fontWeight = Text.TXWE_NORMAL; 226 } 227 228 229 //Get font 230 Font font = diagram.getUniverse().getFont(fontFamily); 231 if (font == null) 232 { 233 font = FontSystem.createFont(fontFamily, fontStyle, fontWeight, (int)fontSize); 234// addShapeSysFont(addShape, font, fontFamily, fontSize, letterSpacing, cursor); 235// return; 236 } 237 238// FontFace fontFace = font.getFontFace(); 239// int ascent = fontFace.getAscent(); 240// float fontScale = fontSize / (float) ascent; 241 242 AffineTransform xform = new AffineTransform(); 243 244// strokeWidthScalar = 1f / fontScale; 245 246 float cursorX = (float)cursor.getX(); 247 float cursorY = (float)cursor.getY(); 248 249// int i = 0; 250 251 String drawText = this.text; 252 drawText = drawText.trim(); 253 for (int i = 0; i < drawText.length(); i++) 254 { 255 if (x != null && i < x.length) 256 { 257 cursorX = x[i]; 258 } else if (dx != null && i < dx.length) 259 { 260 cursorX += dx[i]; 261 } 262 263 if (y != null && i < y.length) 264 { 265 cursorY = y[i]; 266 } else if (dy != null && i < dy.length) 267 { 268 cursorY += dy[i]; 269 } 270 // i++; 271 272 xform.setToIdentity(); 273 xform.setToTranslation(cursorX, cursorY); 274// xform.scale(fontScale, fontScale); 275 if (rotate != null) 276 { 277 xform.rotate(rotate[i]); 278 } 279 280 String unicode = drawText.substring(i, i + 1); 281 MissingGlyph glyph = font.getGlyph(unicode); 282 283 Shape path = glyph.getPath(); 284 if (path != null) 285 { 286 path = xform.createTransformedShape(path); 287 addShape.append(path, false); 288 } 289 290// cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing; 291 cursorX += glyph.getHorizAdvX() + letterSpacing; 292 } 293 294 //Save final draw point so calling method knows where to begin next 295 // text draw 296 cursor.setLocation(cursorX, cursorY); 297 strokeWidthScalar = 1f; 298 } 299 300// private void addShapeSysFont(GeneralPath addShape, Font font, 301// String fontFamily, float fontSize, float letterSpacing, Point2D cursor) 302// { 303// 304// java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize); 305// 306// FontRenderContext frc = new FontRenderContext(null, true, true); 307// String renderText = this.text.trim(); 308// 309// AffineTransform xform = new AffineTransform(); 310// 311// float cursorX = (float)cursor.getX(); 312// float cursorY = (float)cursor.getY(); 313//// int i = 0; 314// for (int i = 0; i < renderText.length(); i++) 315// { 316// if (x != null && i < x.length) 317// { 318// cursorX = x[i]; 319// } else if (dx != null && i < dx.length) 320// { 321// cursorX += dx[i]; 322// } 323// 324// if (y != null && i < y.length) 325// { 326// cursorY = y[i]; 327// } else if (dy != null && i < dy.length) 328// { 329// cursorY += dy[i]; 330// } 331//// i++; 332// 333// xform.setToIdentity(); 334// xform.setToTranslation(cursorX, cursorY); 335// if (rotate != null) 336// { 337// xform.rotate(rotate[Math.min(i, rotate.length - 1)]); 338// } 339// 340//// String unicode = renderText.substring(i, i + 1); 341// GlyphVector textVector = sysFont.createGlyphVector(frc, renderText.substring(i, i + 1)); 342// Shape glyphOutline = textVector.getGlyphOutline(0); 343// GlyphMetrics glyphMetrics = textVector.getGlyphMetrics(0); 344// 345// glyphOutline = xform.createTransformedShape(glyphOutline); 346// addShape.append(glyphOutline, false); 347// 348// 349//// cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing; 350// cursorX += glyphMetrics.getAdvance() + letterSpacing; 351// } 352// 353// cursor.setLocation(cursorX, cursorY); 354// } 355 356 @Override 357 public void render(Graphics2D g) throws SVGException 358 { 359 float cursorX = 0; 360 float cursorY = 0; 361 362 if (x != null) 363 { 364 cursorX = x[0]; 365 cursorY = y[0]; 366 } else if (dx != null) 367 { 368 cursorX += dx[0]; 369 cursorY += dy[0]; 370 } 371 372 StyleAttribute sty = new StyleAttribute(); 373 374 String fontFamily = null; 375 if (getPres(sty.setName("font-family"))) 376 { 377 fontFamily = sty.getStringValue(); 378 } 379 380 381 float fontSize = 12f; 382 if (getPres(sty.setName("font-size"))) 383 { 384 fontSize = sty.getFloatValueWithUnits(); 385 } 386 387 //Get font 388 Font font = diagram.getUniverse().getFont(fontFamily); 389 if (font == null) 390 { 391 System.err.println("Could not load font"); 392 java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize); 393 renderSysFont(g, sysFont); 394 return; 395 } 396 397 398 FontFace fontFace = font.getFontFace(); 399 int ascent = fontFace.getAscent(); 400 float fontScale = fontSize / (float) ascent; 401 402 AffineTransform oldXform = g.getTransform(); 403 AffineTransform xform = new AffineTransform(); 404 405 strokeWidthScalar = 1f / fontScale; 406 407 int posPtr = 1; 408 409 for (int i = 0; i < text.length(); i++) 410 { 411 xform.setToTranslation(cursorX, cursorY); 412 xform.scale(fontScale, fontScale); 413 g.transform(xform); 414 415 String unicode = text.substring(i, i + 1); 416 MissingGlyph glyph = font.getGlyph(unicode); 417 418 Shape path = glyph.getPath(); 419 if (path != null) 420 { 421 renderShape(g, path); 422 } else 423 { 424 glyph.render(g); 425 } 426 427 if (x != null && posPtr < x.length) 428 { 429 cursorX = x[posPtr]; 430 cursorY = y[posPtr++]; 431 } else if (dx != null && posPtr < dx.length) 432 { 433 cursorX += dx[posPtr]; 434 cursorY += dy[posPtr++]; 435 } 436 437 cursorX += fontScale * glyph.getHorizAdvX(); 438 439 g.setTransform(oldXform); 440 } 441 442 strokeWidthScalar = 1f; 443 } 444 445 protected void renderSysFont(Graphics2D g, java.awt.Font font) throws SVGException 446 { 447 float cursorX = 0; 448 float cursorY = 0; 449 450 FontRenderContext frc = g.getFontRenderContext(); 451 452 Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY); 453 renderShape(g, textShape); 454 Rectangle2D rect = font.getStringBounds(text, frc); 455 cursorX += (float) rect.getWidth(); 456 } 457 458 @Override 459 public Shape getShape() 460 { 461 return null; 462 //return shapeToParent(tspanShape); 463 } 464 465 @Override 466 public Rectangle2D getBoundingBox() 467 { 468 return null; 469 //return boundsToParent(tspanShape.getBounds2D()); 470 } 471 472 /** 473 * Updates all attributes in this diagram associated with a time event. Ie, 474 * all attributes with track information. 475 * 476 * @return - true if this node has changed state as a result of the time 477 * update 478 */ 479 @Override 480 public boolean updateTime(double curTime) throws SVGException 481 { 482 //Tspan does not change 483 return false; 484 } 485 486 public String getText() 487 { 488 return text; 489 } 490 491 public void setText(String text) 492 { 493 this.text = text; 494 } 495}