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 && fontFamily != null) 232 { 233 font = FontSystem.createFont(fontFamily, fontStyle, fontWeight, (int)fontSize); 234// addShapeSysFont(addShape, font, fontFamily, fontSize, letterSpacing, cursor); 235// return; 236 } 237 238 if (font == null) 239 { 240 font = FontSystem.createFont("Serif", fontStyle, fontWeight, fontStyle); 241 } 242 243// FontFace fontFace = font.getFontFace(); 244// int ascent = fontFace.getAscent(); 245// float fontScale = fontSize / (float) ascent; 246 247 AffineTransform xform = new AffineTransform(); 248 249// strokeWidthScalar = 1f / fontScale; 250 251 float cursorX = (float)cursor.getX(); 252 float cursorY = (float)cursor.getY(); 253 254// int i = 0; 255 256 String drawText = this.text; 257 drawText = drawText.trim(); 258 for (int i = 0; i < drawText.length(); i++) 259 { 260 if (x != null && i < x.length) 261 { 262 cursorX = x[i]; 263 } else if (dx != null && i < dx.length) 264 { 265 cursorX += dx[i]; 266 } 267 268 if (y != null && i < y.length) 269 { 270 cursorY = y[i]; 271 } else if (dy != null && i < dy.length) 272 { 273 cursorY += dy[i]; 274 } 275 // i++; 276 277 xform.setToIdentity(); 278 xform.setToTranslation(cursorX, cursorY); 279// xform.scale(fontScale, fontScale); 280 if (rotate != null) 281 { 282 xform.rotate(rotate[i]); 283 } 284 285 String unicode = drawText.substring(i, i + 1); 286 MissingGlyph glyph = font.getGlyph(unicode); 287 288 Shape path = glyph.getPath(); 289 if (path != null) 290 { 291 path = xform.createTransformedShape(path); 292 addShape.append(path, false); 293 } 294 295// cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing; 296 cursorX += glyph.getHorizAdvX() + letterSpacing; 297 } 298 299 //Save final draw point so calling method knows where to begin next 300 // text draw 301 cursor.setLocation(cursorX, cursorY); 302 strokeWidthScalar = 1f; 303 } 304 305// private void addShapeSysFont(GeneralPath addShape, Font font, 306// String fontFamily, float fontSize, float letterSpacing, Point2D cursor) 307// { 308// 309// java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize); 310// 311// FontRenderContext frc = new FontRenderContext(null, true, true); 312// String renderText = this.text.trim(); 313// 314// AffineTransform xform = new AffineTransform(); 315// 316// float cursorX = (float)cursor.getX(); 317// float cursorY = (float)cursor.getY(); 318//// int i = 0; 319// for (int i = 0; i < renderText.length(); i++) 320// { 321// if (x != null && i < x.length) 322// { 323// cursorX = x[i]; 324// } else if (dx != null && i < dx.length) 325// { 326// cursorX += dx[i]; 327// } 328// 329// if (y != null && i < y.length) 330// { 331// cursorY = y[i]; 332// } else if (dy != null && i < dy.length) 333// { 334// cursorY += dy[i]; 335// } 336//// i++; 337// 338// xform.setToIdentity(); 339// xform.setToTranslation(cursorX, cursorY); 340// if (rotate != null) 341// { 342// xform.rotate(rotate[Math.min(i, rotate.length - 1)]); 343// } 344// 345//// String unicode = renderText.substring(i, i + 1); 346// GlyphVector textVector = sysFont.createGlyphVector(frc, renderText.substring(i, i + 1)); 347// Shape glyphOutline = textVector.getGlyphOutline(0); 348// GlyphMetrics glyphMetrics = textVector.getGlyphMetrics(0); 349// 350// glyphOutline = xform.createTransformedShape(glyphOutline); 351// addShape.append(glyphOutline, false); 352// 353// 354//// cursorX += fontScale * glyph.getHorizAdvX() + letterSpacing; 355// cursorX += glyphMetrics.getAdvance() + letterSpacing; 356// } 357// 358// cursor.setLocation(cursorX, cursorY); 359// } 360 361 @Override 362 public void render(Graphics2D g) throws SVGException 363 { 364 float cursorX = 0; 365 float cursorY = 0; 366 367 if (x != null) 368 { 369 cursorX = x[0]; 370 cursorY = y[0]; 371 } else if (dx != null) 372 { 373 cursorX += dx[0]; 374 cursorY += dy[0]; 375 } 376 377 StyleAttribute sty = new StyleAttribute(); 378 379 String fontFamily = null; 380 if (getPres(sty.setName("font-family"))) 381 { 382 fontFamily = sty.getStringValue(); 383 } 384 385 386 float fontSize = 12f; 387 if (getPres(sty.setName("font-size"))) 388 { 389 fontSize = sty.getFloatValueWithUnits(); 390 } 391 392 //Get font 393 Font font = diagram.getUniverse().getFont(fontFamily); 394 if (font == null) 395 { 396 System.err.println("Could not load font"); 397 java.awt.Font sysFont = new java.awt.Font(fontFamily, java.awt.Font.PLAIN, (int) fontSize); 398 renderSysFont(g, sysFont); 399 return; 400 } 401 402 403 FontFace fontFace = font.getFontFace(); 404 int ascent = fontFace.getAscent(); 405 float fontScale = fontSize / (float) ascent; 406 407 AffineTransform oldXform = g.getTransform(); 408 AffineTransform xform = new AffineTransform(); 409 410 strokeWidthScalar = 1f / fontScale; 411 412 int posPtr = 1; 413 414 for (int i = 0; i < text.length(); i++) 415 { 416 xform.setToTranslation(cursorX, cursorY); 417 xform.scale(fontScale, fontScale); 418 g.transform(xform); 419 420 String unicode = text.substring(i, i + 1); 421 MissingGlyph glyph = font.getGlyph(unicode); 422 423 Shape path = glyph.getPath(); 424 if (path != null) 425 { 426 renderShape(g, path); 427 } else 428 { 429 glyph.render(g); 430 } 431 432 if (x != null && posPtr < x.length) 433 { 434 cursorX = x[posPtr]; 435 cursorY = y[posPtr++]; 436 } else if (dx != null && posPtr < dx.length) 437 { 438 cursorX += dx[posPtr]; 439 cursorY += dy[posPtr++]; 440 } 441 442 cursorX += fontScale * glyph.getHorizAdvX(); 443 444 g.setTransform(oldXform); 445 } 446 447 strokeWidthScalar = 1f; 448 } 449 450 protected void renderSysFont(Graphics2D g, java.awt.Font font) throws SVGException 451 { 452 float cursorX = 0; 453 float cursorY = 0; 454 455 FontRenderContext frc = g.getFontRenderContext(); 456 457 Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY); 458 renderShape(g, textShape); 459 Rectangle2D rect = font.getStringBounds(text, frc); 460 cursorX += (float) rect.getWidth(); 461 } 462 463 @Override 464 public Shape getShape() 465 { 466 return null; 467 //return shapeToParent(tspanShape); 468 } 469 470 @Override 471 public Rectangle2D getBoundingBox() 472 { 473 return null; 474 //return boundsToParent(tspanShape.getBounds2D()); 475 } 476 477 /** 478 * Updates all attributes in this diagram associated with a time event. Ie, 479 * all attributes with track information. 480 * 481 * @return - true if this node has changed state as a result of the time 482 * update 483 */ 484 @Override 485 public boolean updateTime(double curTime) throws SVGException 486 { 487 //Tspan does not change 488 return false; 489 } 490 491 public String getText() 492 { 493 return text; 494 } 495 496 public void setText(String text) 497 { 498 this.text = text; 499 } 500}