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.geom.AffineTransform; 043import java.awt.geom.GeneralPath; 044import java.awt.geom.Rectangle2D; 045import java.util.Iterator; 046import java.util.LinkedList; 047import java.util.regex.Matcher; 048import java.util.regex.Pattern; 049 050//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath; 051/** 052 * @author Mark McKay 053 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 054 */ 055public class Text extends ShapeElement 056{ 057 public static final String TAG_NAME = "text"; 058 059 float x = 0; 060 float y = 0; 061 AffineTransform transform = null; 062 String fontFamily; 063 float fontSize; 064 //List of strings and tspans containing the content of this node 065 LinkedList content = new LinkedList(); 066 Shape textShape; 067 public static final int TXAN_START = 0; 068 public static final int TXAN_MIDDLE = 1; 069 public static final int TXAN_END = 2; 070 int textAnchor = TXAN_START; 071 public static final int TXST_NORMAL = 0; 072 public static final int TXST_ITALIC = 1; 073 public static final int TXST_OBLIQUE = 2; 074 int fontStyle; 075 public static final int TXWE_NORMAL = 0; 076 public static final int TXWE_BOLD = 1; 077 public static final int TXWE_BOLDER = 2; 078 public static final int TXWE_LIGHTER = 3; 079 public static final int TXWE_100 = 4; 080 public static final int TXWE_200 = 5; 081 public static final int TXWE_300 = 6; 082 public static final int TXWE_400 = 7; 083 public static final int TXWE_500 = 8; 084 public static final int TXWE_600 = 9; 085 public static final int TXWE_700 = 10; 086 public static final int TXWE_800 = 11; 087 public static final int TXWE_900 = 12; 088 int fontWeight; 089 090 /** 091 * Creates a new instance of Stop 092 */ 093 public Text() 094 { 095 } 096 097 public String getTagName() 098 { 099 return TAG_NAME; 100 } 101 102 public void appendText(String text) 103 { 104 content.addLast(text); 105 } 106 107 public void appendTspan(Tspan tspan) throws SVGElementException 108 { 109 super.loaderAddChild(null, tspan); 110 content.addLast(tspan); 111 } 112 113 /** 114 * Discard cached information 115 */ 116 public void rebuild() throws SVGException 117 { 118 build(); 119 } 120 121 public java.util.List getContent() 122 { 123 return content; 124 } 125 126 /** 127 * Called after the start element but before the end element to indicate 128 * each child tag that has been processed 129 */ 130 public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException 131 { 132 super.loaderAddChild(helper, child); 133 134 content.addLast(child); 135 } 136 137 /** 138 * Called during load process to add text scanned within a tag 139 */ 140 public void loaderAddText(SVGLoaderHelper helper, String text) 141 { 142 Matcher matchWs = Pattern.compile("\\s*").matcher(text); 143 if (!matchWs.matches()) 144 { 145 content.addLast(text); 146 } 147 } 148 149 public void build() throws SVGException 150 { 151 super.build(); 152 153 StyleAttribute sty = new StyleAttribute(); 154 155 if (getPres(sty.setName("x"))) 156 { 157 x = sty.getFloatValueWithUnits(); 158 } 159 160 if (getPres(sty.setName("y"))) 161 { 162 y = sty.getFloatValueWithUnits(); 163 } 164 165 if (getStyle(sty.setName("font-family"))) 166 { 167 fontFamily = sty.getStringValue(); 168 } else 169 { 170 fontFamily = "Sans Serif"; 171 } 172 173 if (getStyle(sty.setName("font-size"))) 174 { 175 fontSize = sty.getFloatValueWithUnits(); 176 } else 177 { 178 fontSize = 12f; 179 } 180 181 if (getStyle(sty.setName("font-style"))) 182 { 183 String s = sty.getStringValue(); 184 if ("normal".equals(s)) 185 { 186 fontStyle = TXST_NORMAL; 187 } else if ("italic".equals(s)) 188 { 189 fontStyle = TXST_ITALIC; 190 } else if ("oblique".equals(s)) 191 { 192 fontStyle = TXST_OBLIQUE; 193 } 194 } else 195 { 196 fontStyle = TXST_NORMAL; 197 } 198 199 if (getStyle(sty.setName("font-weight"))) 200 { 201 String s = sty.getStringValue(); 202 if ("normal".equals(s)) 203 { 204 fontWeight = TXWE_NORMAL; 205 } else if ("bold".equals(s)) 206 { 207 fontWeight = TXWE_BOLD; 208 } 209 } else 210 { 211 fontWeight = TXWE_BOLD; 212 } 213 214 if (getStyle(sty.setName("text-anchor"))) 215 { 216 String s = sty.getStringValue(); 217 if (s.equals("middle")) 218 { 219 textAnchor = TXAN_MIDDLE; 220 } else if (s.equals("end")) 221 { 222 textAnchor = TXAN_END; 223 } else 224 { 225 textAnchor = TXAN_START; 226 } 227 } else 228 { 229 textAnchor = TXAN_START; 230 } 231 232 //text anchor 233 //text-decoration 234 //text-rendering 235 236 buildFont(); 237 } 238 239 protected void buildFont() throws SVGException 240 { 241 int style; 242 switch (fontStyle) 243 { 244 case TXST_ITALIC: 245 style = java.awt.Font.ITALIC; 246 break; 247 default: 248 style = java.awt.Font.PLAIN; 249 break; 250 } 251 252 int weight; 253 switch (fontWeight) 254 { 255 case TXWE_BOLD: 256 case TXWE_BOLDER: 257 weight = java.awt.Font.BOLD; 258 break; 259 default: 260 weight = java.awt.Font.PLAIN; 261 break; 262 } 263 264 //Get font 265 Font font = diagram.getUniverse().getFont(fontFamily); 266 if (font == null) 267 { 268// System.err.println("Could not load font"); 269 270 java.awt.Font sysFont = new java.awt.Font(fontFamily, style | weight, (int) fontSize); 271 buildSysFont(sysFont); 272 return; 273 } 274 275// font = new java.awt.Font(font.getFamily(), style | weight, font.getSize()); 276 277// Area textArea = new Area(); 278 GeneralPath textPath = new GeneralPath(); 279 textShape = textPath; 280 281 float cursorX = x, cursorY = y; 282 283 FontFace fontFace = font.getFontFace(); 284 //int unitsPerEm = fontFace.getUnitsPerEm(); 285 int ascent = fontFace.getAscent(); 286 float fontScale = fontSize / (float) ascent; 287 288// AffineTransform oldXform = g.getTransform(); 289 AffineTransform xform = new AffineTransform(); 290 291 for (Iterator it = content.iterator(); it.hasNext();) 292 { 293 Object obj = it.next(); 294 295 if (obj instanceof String) 296 { 297 String text = (String) obj; 298 if (text != null) 299 { 300 text = text.trim(); 301 } 302 303 strokeWidthScalar = 1f / fontScale; 304 305 for (int i = 0; i < text.length(); i++) 306 { 307 xform.setToIdentity(); 308 xform.setToTranslation(cursorX, cursorY); 309 xform.scale(fontScale, fontScale); 310// g.transform(xform); 311 312 String unicode = text.substring(i, i + 1); 313 MissingGlyph glyph = font.getGlyph(unicode); 314 315 Shape path = glyph.getPath(); 316 if (path != null) 317 { 318 path = xform.createTransformedShape(path); 319 textPath.append(path, false); 320 } 321// else glyph.render(g); 322 323 cursorX += fontScale * glyph.getHorizAdvX(); 324 325// g.setTransform(oldXform); 326 } 327 328 strokeWidthScalar = 1f; 329 } else if (obj instanceof Tspan) 330 { 331 Tspan tspan = (Tspan) obj; 332 333 xform.setToIdentity(); 334 xform.setToTranslation(cursorX, cursorY); 335 xform.scale(fontScale, fontScale); 336// tspan.setCursorX(cursorX); 337// tspan.setCursorY(cursorY); 338 339 Shape tspanShape = tspan.getShape(); 340 tspanShape = xform.createTransformedShape(tspanShape); 341 textPath.append(tspanShape, false); 342// tspan.render(g); 343// cursorX = tspan.getCursorX(); 344// cursorY = tspan.getCursorY(); 345 } 346 347 } 348 349 switch (textAnchor) 350 { 351 case TXAN_MIDDLE: 352 { 353 AffineTransform at = new AffineTransform(); 354 at.translate(-textPath.getBounds2D().getWidth() / 2, 0); 355 textPath.transform(at); 356 break; 357 } 358 case TXAN_END: 359 { 360 AffineTransform at = new AffineTransform(); 361 at.translate(-textPath.getBounds2D().getWidth(), 0); 362 textPath.transform(at); 363 break; 364 } 365 } 366 } 367 368 private void buildSysFont(java.awt.Font font) throws SVGException 369 { 370 GeneralPath textPath = new GeneralPath(); 371 textShape = textPath; 372 373 float cursorX = x, cursorY = y; 374 375// FontMetrics fm = g.getFontMetrics(font); 376 FontRenderContext frc = new FontRenderContext(null, true, true); 377 378// FontFace fontFace = font.getFontFace(); 379 //int unitsPerEm = fontFace.getUnitsPerEm(); 380// int ascent = fm.getAscent(); 381// float fontScale = fontSize / (float)ascent; 382 383// AffineTransform oldXform = g.getTransform(); 384 AffineTransform xform = new AffineTransform(); 385 386 for (Iterator it = content.iterator(); it.hasNext();) 387 { 388 Object obj = it.next(); 389 390 if (obj instanceof String) 391 { 392 String text = (String) obj; 393 394 Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY); 395 textPath.append(textShape, false); 396// renderShape(g, textShape); 397// g.drawString(text, cursorX, cursorY); 398 399 Rectangle2D rect = font.getStringBounds(text, frc); 400 cursorX += (float) rect.getWidth(); 401 } else if (obj instanceof Tspan) 402 { 403 /* 404 Tspan tspan = (Tspan)obj; 405 406 xform.setToIdentity(); 407 xform.setToTranslation(cursorX, cursorY); 408 409 Shape tspanShape = tspan.getShape(); 410 tspanShape = xform.createTransformedShape(tspanShape); 411 textArea.add(new Area(tspanShape)); 412 413 cursorX += tspanShape.getBounds2D().getWidth(); 414 */ 415 416 417 Tspan tspan = (Tspan) obj; 418 tspan.setCursorX(cursorX); 419 tspan.setCursorY(cursorY); 420 tspan.addShape(textPath); 421 cursorX = tspan.getCursorX(); 422 cursorY = tspan.getCursorY(); 423 424 } 425 } 426 427 switch (textAnchor) 428 { 429 case TXAN_MIDDLE: 430 { 431 AffineTransform at = new AffineTransform(); 432 at.translate(-textPath.getBounds2D().getWidth() / 2, 0); 433 textPath.transform(at); 434 break; 435 } 436 case TXAN_END: 437 { 438 AffineTransform at = new AffineTransform(); 439 at.translate(-textPath.getBounds2D().getWidth(), 0); 440 textPath.transform(at); 441 break; 442 } 443 } 444 } 445 446 public void render(Graphics2D g) throws SVGException 447 { 448 beginLayer(g); 449 renderShape(g, textShape); 450 finishLayer(g); 451 } 452 453 public Shape getShape() 454 { 455 return shapeToParent(textShape); 456 } 457 458 public Rectangle2D getBoundingBox() throws SVGException 459 { 460 return boundsToParent(includeStrokeInBounds(textShape.getBounds2D())); 461 } 462 463 /** 464 * Updates all attributes in this diagram associated with a time event. Ie, 465 * all attributes with track information. 466 * 467 * @return - true if this node has changed state as a result of the time 468 * update 469 */ 470 public boolean updateTime(double curTime) throws SVGException 471 { 472// if (trackManager.getNumTracks() == 0) return false; 473 boolean changeState = super.updateTime(curTime); 474 475 //Get current values for parameters 476 StyleAttribute sty = new StyleAttribute(); 477 boolean shapeChange = false; 478 479 if (getPres(sty.setName("x"))) 480 { 481 float newVal = sty.getFloatValueWithUnits(); 482 if (newVal != x) 483 { 484 x = newVal; 485 shapeChange = true; 486 } 487 } 488 489 if (getPres(sty.setName("y"))) 490 { 491 float newVal = sty.getFloatValueWithUnits(); 492 if (newVal != y) 493 { 494 y = newVal; 495 shapeChange = true; 496 } 497 } 498 499 if (getPres(sty.setName("font-family"))) 500 { 501 String newVal = sty.getStringValue(); 502 if (!newVal.equals(fontFamily)) 503 { 504 fontFamily = newVal; 505 shapeChange = true; 506 } 507 } 508 509 if (getPres(sty.setName("font-size"))) 510 { 511 float newVal = sty.getFloatValueWithUnits(); 512 if (newVal != fontSize) 513 { 514 fontSize = newVal; 515 shapeChange = true; 516 } 517 } 518 519 520 if (getStyle(sty.setName("font-style"))) 521 { 522 String s = sty.getStringValue(); 523 int newVal = fontStyle; 524 if ("normal".equals(s)) 525 { 526 newVal = TXST_NORMAL; 527 } else if ("italic".equals(s)) 528 { 529 newVal = TXST_ITALIC; 530 } else if ("oblique".equals(s)) 531 { 532 newVal = TXST_OBLIQUE; 533 } 534 if (newVal != fontStyle) 535 { 536 fontStyle = newVal; 537 shapeChange = true; 538 } 539 } 540 541 if (getStyle(sty.setName("font-weight"))) 542 { 543 String s = sty.getStringValue(); 544 int newVal = fontWeight; 545 if ("normal".equals(s)) 546 { 547 newVal = TXWE_NORMAL; 548 } else if ("bold".equals(s)) 549 { 550 newVal = TXWE_BOLD; 551 } 552 if (newVal != fontWeight) 553 { 554 fontWeight = newVal; 555 shapeChange = true; 556 } 557 } 558 559 if (shapeChange) 560 { 561 build(); 562// buildFont(); 563// return true; 564 } 565 566 return changeState || shapeChange; 567 } 568}