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.geom.AffineTransform; 043import java.awt.geom.GeneralPath; 044import java.awt.geom.Point2D; 045import java.awt.geom.Rectangle2D; 046import java.util.Iterator; 047import java.util.LinkedList; 048import java.util.regex.Matcher; 049import java.util.regex.Pattern; 050 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 float textLength = -1; 091 String lengthAdjust = "spacing"; 092 093 /** 094 * Creates a new instance of Stop 095 */ 096 public Text() 097 { 098 } 099 100 public String getTagName() 101 { 102 return TAG_NAME; 103 } 104 105 public void appendText(String text) 106 { 107 content.addLast(text); 108 } 109 110 public void appendTspan(Tspan tspan) throws SVGElementException 111 { 112 super.loaderAddChild(null, tspan); 113 content.addLast(tspan); 114 } 115 116 /** 117 * Discard cached information 118 */ 119 public void rebuild() throws SVGException 120 { 121 build(); 122 } 123 124 public java.util.List getContent() 125 { 126 return content; 127 } 128 129 /** 130 * Called after the start element but before the end element to indicate 131 * each child tag that has been processed 132 */ 133 public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException 134 { 135 super.loaderAddChild(helper, child); 136 137 content.addLast(child); 138 } 139 140 /** 141 * Called during load process to add text scanned within a tag 142 */ 143 public void loaderAddText(SVGLoaderHelper helper, String text) 144 { 145 Matcher matchWs = Pattern.compile("\\s*").matcher(text); 146 if (!matchWs.matches()) 147 { 148 content.addLast(text); 149 } 150 } 151 152 public void build() throws SVGException 153 { 154 super.build(); 155 156 StyleAttribute sty = new StyleAttribute(); 157 158 if (getPres(sty.setName("x"))) 159 { 160 x = sty.getFloatValueWithUnits(); 161 } 162 163 if (getPres(sty.setName("y"))) 164 { 165 y = sty.getFloatValueWithUnits(); 166 } 167 168 if (getStyle(sty.setName("font-family"))) 169 { 170 fontFamily = sty.getStringValue(); 171 } 172 else 173 { 174 fontFamily = "Sans Serif"; 175 } 176 177 if (getStyle(sty.setName("font-size"))) 178 { 179 fontSize = sty.getFloatValueWithUnits(); 180 } 181 else 182 { 183 fontSize = 12f; 184 } 185 186 if (getStyle(sty.setName("textLength"))) 187 { 188 textLength = sty.getFloatValueWithUnits(); 189 } 190 else 191 { 192 textLength = -1; 193 } 194 195 if (getStyle(sty.setName("lengthAdjust"))) 196 { 197 lengthAdjust = sty.getStringValue(); 198 } 199 else 200 { 201 lengthAdjust = "spacing"; 202 } 203 204 if (getStyle(sty.setName("font-style"))) 205 { 206 String s = sty.getStringValue(); 207 if ("normal".equals(s)) 208 { 209 fontStyle = TXST_NORMAL; 210 } else if ("italic".equals(s)) 211 { 212 fontStyle = TXST_ITALIC; 213 } else if ("oblique".equals(s)) 214 { 215 fontStyle = TXST_OBLIQUE; 216 } 217 } else 218 { 219 fontStyle = TXST_NORMAL; 220 } 221 222 if (getStyle(sty.setName("font-weight"))) 223 { 224 String s = sty.getStringValue(); 225 if ("normal".equals(s)) 226 { 227 fontWeight = TXWE_NORMAL; 228 } else if ("bold".equals(s)) 229 { 230 fontWeight = TXWE_BOLD; 231 } 232 } else 233 { 234 fontWeight = TXWE_NORMAL; 235 } 236 237 if (getStyle(sty.setName("text-anchor"))) 238 { 239 String s = sty.getStringValue(); 240 if (s.equals("middle")) 241 { 242 textAnchor = TXAN_MIDDLE; 243 } else if (s.equals("end")) 244 { 245 textAnchor = TXAN_END; 246 } else 247 { 248 textAnchor = TXAN_START; 249 } 250 } else 251 { 252 textAnchor = TXAN_START; 253 } 254 255 //text anchor 256 //text-decoration 257 //text-rendering 258 259 buildText(); 260 } 261 262 protected void buildText() throws SVGException 263 { 264 //Get font 265 String[] families = fontFamily.split(","); 266 Font font = null; 267 for (int i = 0; i < families.length; ++i) 268 { 269 font = diagram.getUniverse().getFont(fontFamily); 270 if (font != null) 271 { 272 break; 273 } 274 } 275 276 if (font == null) 277 { 278// System.err.println("Could not load font"); 279 280 font = new FontSystem(fontFamily, fontStyle, fontWeight, (int)fontSize); 281// java.awt.Font sysFont = new java.awt.Font(fontFamily, style | weight, (int)fontSize); 282// buildSysFont(sysFont); 283// return; 284 } 285 286// font = new java.awt.Font(font.getFamily(), style | weight, font.getSize()); 287 288// Area textArea = new Area(); 289 GeneralPath textPath = new GeneralPath(); 290 textShape = textPath; 291 292 float cursorX = x, cursorY = y; 293 294 FontFace fontFace = font.getFontFace(); 295 //int unitsPerEm = fontFace.getUnitsPerEm(); 296// int ascent = fontFace.getAscent(); 297// float fontScale = fontSize / (float) ascent; 298 299// AffineTransform oldXform = g.getTransform(); 300// TextBuilder builder = new TextBuilder(); 301// 302// for (Iterator it = content.iterator(); it.hasNext();) 303// { 304// Object obj = it.next(); 305// 306// if (obj instanceof String) 307// { 308// String text = (String) obj; 309// if (text != null) 310// { 311// text = text.trim(); 312// } 313// 314// for (int i = 0; i < text.length(); i++) 315// { 316// String unicode = text.substring(i, i + 1); 317// MissingGlyph glyph = font.getGlyph(unicode); 318// 319// builder.appendGlyph(glyph); 320// } 321// } 322// else if (obj instanceof Tspan) 323// { 324// Tspan tspan = (Tspan)obj; 325// tspan.buildGlyphs(builder); 326// } 327// } 328// 329// builder.formatGlyphs(); 330 331 332 333 334 335 336 337 338 AffineTransform xform = new AffineTransform(); 339 340 for (Iterator it = content.iterator(); it.hasNext();) 341 { 342 Object obj = it.next(); 343 344 if (obj instanceof String) 345 { 346 String text = (String) obj; 347 if (text != null) 348 { 349 text = text.trim(); 350 } 351 352// strokeWidthScalar = 1f / fontScale; 353 354 for (int i = 0; i < text.length(); i++) 355 { 356 xform.setToIdentity(); 357 xform.setToTranslation(cursorX, cursorY); 358// xform.scale(fontScale, fontScale); 359// g.transform(xform); 360 361 String unicode = text.substring(i, i + 1); 362 MissingGlyph glyph = font.getGlyph(unicode); 363 364 Shape path = glyph.getPath(); 365 if (path != null) 366 { 367 path = xform.createTransformedShape(path); 368 textPath.append(path, false); 369 } 370// else glyph.render(g); 371 372// cursorX += fontScale * glyph.getHorizAdvX(); 373 cursorX += glyph.getHorizAdvX(); 374 375// g.setTransform(oldXform); 376 } 377 378 strokeWidthScalar = 1f; 379 } 380 else if (obj instanceof Tspan) 381 { 382// Tspan tspan = (Tspan) obj; 383// 384// xform.setToIdentity(); 385// xform.setToTranslation(cursorX, cursorY); 386// xform.scale(fontScale, fontScale); 387//// tspan.setCursorX(cursorX); 388//// tspan.setCursorY(cursorY); 389// 390// Shape tspanShape = tspan.getShape(); 391// tspanShape = xform.createTransformedShape(tspanShape); 392// textPath.append(tspanShape, false); 393//// tspan.render(g); 394//// cursorX = tspan.getCursorX(); 395//// cursorY = tspan.getCursorY(); 396 397 398 Tspan tspan = (Tspan)obj; 399 Point2D cursor = new Point2D.Float(cursorX, cursorY); 400// tspan.setCursorX(cursorX); 401// tspan.setCursorY(cursorY); 402 tspan.appendToShape(textPath, cursor); 403// cursorX = tspan.getCursorX(); 404// cursorY = tspan.getCursorY(); 405 cursorX = (float)cursor.getX(); 406 cursorY = (float)cursor.getY(); 407 408 } 409 410 } 411 412 switch (textAnchor) 413 { 414 case TXAN_MIDDLE: 415 { 416 AffineTransform at = new AffineTransform(); 417 at.translate(-textPath.getBounds().getWidth() / 2, 0); 418 textPath.transform(at); 419 break; 420 } 421 case TXAN_END: 422 { 423 AffineTransform at = new AffineTransform(); 424 at.translate(-textPath.getBounds().getWidth(), 0); 425 textPath.transform(at); 426 break; 427 } 428 } 429 } 430 431// private void buildSysFont(java.awt.Font font) throws SVGException 432// { 433// GeneralPath textPath = new GeneralPath(); 434// textShape = textPath; 435// 436// float cursorX = x, cursorY = y; 437// 438//// FontMetrics fm = g.getFontMetrics(font); 439// FontRenderContext frc = new FontRenderContext(null, true, true); 440// 441//// FontFace fontFace = font.getFontFace(); 442// //int unitsPerEm = fontFace.getUnitsPerEm(); 443//// int ascent = fm.getAscent(); 444//// float fontScale = fontSize / (float)ascent; 445// 446//// AffineTransform oldXform = g.getTransform(); 447// AffineTransform xform = new AffineTransform(); 448// 449// for (Iterator it = content.iterator(); it.hasNext();) 450// { 451// Object obj = it.next(); 452// 453// if (obj instanceof String) 454// { 455// String text = (String)obj; 456// text = text.trim(); 457// 458// Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY); 459// textPath.append(textShape, false); 460//// renderShape(g, textShape); 461//// g.drawString(text, cursorX, cursorY); 462// 463// Rectangle2D rect = font.getStringBounds(text, frc); 464// cursorX += (float) rect.getWidth(); 465// } else if (obj instanceof Tspan) 466// { 467// /* 468// Tspan tspan = (Tspan)obj; 469// 470// xform.setToIdentity(); 471// xform.setToTranslation(cursorX, cursorY); 472// 473// Shape tspanShape = tspan.getShape(); 474// tspanShape = xform.createTransformedShape(tspanShape); 475// textArea.add(new Area(tspanShape)); 476// 477// cursorX += tspanShape.getBounds2D().getWidth(); 478// */ 479// 480// 481// Tspan tspan = (Tspan)obj; 482// Point2D cursor = new Point2D.Float(cursorX, cursorY); 483//// tspan.setCursorX(cursorX); 484//// tspan.setCursorY(cursorY); 485// tspan.appendToShape(textPath, cursor); 486//// cursorX = tspan.getCursorX(); 487//// cursorY = tspan.getCursorY(); 488// cursorX = (float)cursor.getX(); 489// cursorY = (float)cursor.getY(); 490// 491// } 492// } 493// 494// switch (textAnchor) 495// { 496// case TXAN_MIDDLE: 497// { 498// AffineTransform at = new AffineTransform(); 499// at.translate(-textPath.getBounds().getWidth() / 2, 0); 500// textPath.transform(at); 501// break; 502// } 503// case TXAN_END: 504// { 505// AffineTransform at = new AffineTransform(); 506// at.translate(-Math.ceil(textPath.getBounds().getWidth()), 0); 507// textPath.transform(at); 508// break; 509// } 510// } 511// } 512 513 public void render(Graphics2D g) throws SVGException 514 { 515 beginLayer(g); 516 renderShape(g, textShape); 517 finishLayer(g); 518 } 519 520 public Shape getShape() 521 { 522 return shapeToParent(textShape); 523 } 524 525 public Rectangle2D getBoundingBox() throws SVGException 526 { 527 return boundsToParent(includeStrokeInBounds(textShape.getBounds2D())); 528 } 529 530 /** 531 * Updates all attributes in this diagram associated with a time event. Ie, 532 * all attributes with track information. 533 * 534 * @return - true if this node has changed state as a result of the time 535 * update 536 */ 537 public boolean updateTime(double curTime) throws SVGException 538 { 539// if (trackManager.getNumTracks() == 0) return false; 540 boolean changeState = super.updateTime(curTime); 541 542 //Get current values for parameters 543 StyleAttribute sty = new StyleAttribute(); 544 boolean shapeChange = false; 545 546 if (getPres(sty.setName("x"))) 547 { 548 float newVal = sty.getFloatValueWithUnits(); 549 if (newVal != x) 550 { 551 x = newVal; 552 shapeChange = true; 553 } 554 } 555 556 if (getPres(sty.setName("y"))) 557 { 558 float newVal = sty.getFloatValueWithUnits(); 559 if (newVal != y) 560 { 561 y = newVal; 562 shapeChange = true; 563 } 564 } 565 566 if (getStyle(sty.setName("textLength"))) 567 { 568 textLength = sty.getFloatValueWithUnits(); 569 } 570 else 571 { 572 textLength = -1; 573 } 574 575 if (getStyle(sty.setName("lengthAdjust"))) 576 { 577 lengthAdjust = sty.getStringValue(); 578 } 579 else 580 { 581 lengthAdjust = "spacing"; 582 } 583 584 if (getPres(sty.setName("font-family"))) 585 { 586 String newVal = sty.getStringValue(); 587 if (!newVal.equals(fontFamily)) 588 { 589 fontFamily = newVal; 590 shapeChange = true; 591 } 592 } 593 594 if (getPres(sty.setName("font-size"))) 595 { 596 float newVal = sty.getFloatValueWithUnits(); 597 if (newVal != fontSize) 598 { 599 fontSize = newVal; 600 shapeChange = true; 601 } 602 } 603 604 605 if (getStyle(sty.setName("font-style"))) 606 { 607 String s = sty.getStringValue(); 608 int newVal = fontStyle; 609 if ("normal".equals(s)) 610 { 611 newVal = TXST_NORMAL; 612 } else if ("italic".equals(s)) 613 { 614 newVal = TXST_ITALIC; 615 } else if ("oblique".equals(s)) 616 { 617 newVal = TXST_OBLIQUE; 618 } 619 if (newVal != fontStyle) 620 { 621 fontStyle = newVal; 622 shapeChange = true; 623 } 624 } 625 626 if (getStyle(sty.setName("font-weight"))) 627 { 628 String s = sty.getStringValue(); 629 int newVal = fontWeight; 630 if ("normal".equals(s)) 631 { 632 newVal = TXWE_NORMAL; 633 } else if ("bold".equals(s)) 634 { 635 newVal = TXWE_BOLD; 636 } 637 if (newVal != fontWeight) 638 { 639 fontWeight = newVal; 640 shapeChange = true; 641 } 642 } 643 644 if (shapeChange) 645 { 646 build(); 647// buildFont(); 648// return true; 649 } 650 651 return changeState || shapeChange; 652 } 653}