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