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