source: josm/trunk/src/com/kitfox/svg/Text.java@ 7676

Last change on this file since 7676 was 7676, checked in by stoecker, 10 years ago

update SVG code to current SVN (fix line endings), see #10479

File size: 16.9 KB
Line 
1/*
2 * SVG Salamander
3 * Copyright (c) 2004, Mark McKay
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
9 *
10 * - Redistributions of source code must retain the above
11 * copyright notice, this list of conditions and the following
12 * disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials
16 * provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
32 * projects can be found at http://www.kitfox.com
33 *
34 * Created on January 26, 2004, 1:56 AM
35 */
36package com.kitfox.svg;
37
38import com.kitfox.svg.xml.StyleAttribute;
39import java.awt.Graphics2D;
40import java.awt.Shape;
41import java.awt.font.FontRenderContext;
42import java.awt.geom.AffineTransform;
43import java.awt.geom.GeneralPath;
44import java.awt.geom.Point2D;
45import java.awt.geom.Rectangle2D;
46import java.util.Iterator;
47import java.util.LinkedList;
48import java.util.regex.Matcher;
49import java.util.regex.Pattern;
50
51//import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
52/**
53 * @author Mark McKay
54 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
55 */
56public class Text extends ShapeElement
57{
58 public static final String TAG_NAME = "text";
59
60 float x = 0;
61 float y = 0;
62 AffineTransform transform = null;
63 String fontFamily;
64 float fontSize;
65 //List of strings and tspans containing the content of this node
66 LinkedList content = new LinkedList();
67 Shape textShape;
68 public static final int TXAN_START = 0;
69 public static final int TXAN_MIDDLE = 1;
70 public static final int TXAN_END = 2;
71 int textAnchor = TXAN_START;
72 public static final int TXST_NORMAL = 0;
73 public static final int TXST_ITALIC = 1;
74 public static final int TXST_OBLIQUE = 2;
75 int fontStyle;
76 public static final int TXWE_NORMAL = 0;
77 public static final int TXWE_BOLD = 1;
78 public static final int TXWE_BOLDER = 2;
79 public static final int TXWE_LIGHTER = 3;
80 public static final int TXWE_100 = 4;
81 public static final int TXWE_200 = 5;
82 public static final int TXWE_300 = 6;
83 public static final int TXWE_400 = 7;
84 public static final int TXWE_500 = 8;
85 public static final int TXWE_600 = 9;
86 public static final int TXWE_700 = 10;
87 public static final int TXWE_800 = 11;
88 public static final int TXWE_900 = 12;
89 int fontWeight;
90
91 /**
92 * Creates a new instance of Stop
93 */
94 public Text()
95 {
96 }
97
98 public String getTagName()
99 {
100 return TAG_NAME;
101 }
102
103 public void appendText(String text)
104 {
105 content.addLast(text);
106 }
107
108 public void appendTspan(Tspan tspan) throws SVGElementException
109 {
110 super.loaderAddChild(null, tspan);
111 content.addLast(tspan);
112 }
113
114 /**
115 * Discard cached information
116 */
117 public void rebuild() throws SVGException
118 {
119 build();
120 }
121
122 public java.util.List getContent()
123 {
124 return content;
125 }
126
127 /**
128 * Called after the start element but before the end element to indicate
129 * each child tag that has been processed
130 */
131 public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException
132 {
133 super.loaderAddChild(helper, child);
134
135 content.addLast(child);
136 }
137
138 /**
139 * Called during load process to add text scanned within a tag
140 */
141 public void loaderAddText(SVGLoaderHelper helper, String text)
142 {
143 Matcher matchWs = Pattern.compile("\\s*").matcher(text);
144 if (!matchWs.matches())
145 {
146 content.addLast(text);
147 }
148 }
149
150 public void build() throws SVGException
151 {
152 super.build();
153
154 StyleAttribute sty = new StyleAttribute();
155
156 if (getPres(sty.setName("x")))
157 {
158 x = sty.getFloatValueWithUnits();
159 }
160
161 if (getPres(sty.setName("y")))
162 {
163 y = sty.getFloatValueWithUnits();
164 }
165
166 if (getStyle(sty.setName("font-family")))
167 {
168 fontFamily = sty.getStringValue();
169 } else
170 {
171 fontFamily = "Sans Serif";
172 }
173
174 if (getStyle(sty.setName("font-size")))
175 {
176 fontSize = sty.getFloatValueWithUnits();
177 } else
178 {
179 fontSize = 12f;
180 }
181
182 if (getStyle(sty.setName("font-style")))
183 {
184 String s = sty.getStringValue();
185 if ("normal".equals(s))
186 {
187 fontStyle = TXST_NORMAL;
188 } else if ("italic".equals(s))
189 {
190 fontStyle = TXST_ITALIC;
191 } else if ("oblique".equals(s))
192 {
193 fontStyle = TXST_OBLIQUE;
194 }
195 } else
196 {
197 fontStyle = TXST_NORMAL;
198 }
199
200 if (getStyle(sty.setName("font-weight")))
201 {
202 String s = sty.getStringValue();
203 if ("normal".equals(s))
204 {
205 fontWeight = TXWE_NORMAL;
206 } else if ("bold".equals(s))
207 {
208 fontWeight = TXWE_BOLD;
209 }
210 } else
211 {
212 fontWeight = TXWE_NORMAL;
213 }
214
215 if (getStyle(sty.setName("text-anchor")))
216 {
217 String s = sty.getStringValue();
218 if (s.equals("middle"))
219 {
220 textAnchor = TXAN_MIDDLE;
221 } else if (s.equals("end"))
222 {
223 textAnchor = TXAN_END;
224 } else
225 {
226 textAnchor = TXAN_START;
227 }
228 } else
229 {
230 textAnchor = TXAN_START;
231 }
232
233 //text anchor
234 //text-decoration
235 //text-rendering
236
237 buildFont();
238 }
239
240 protected void buildFont() throws SVGException
241 {
242 int style;
243 switch (fontStyle)
244 {
245 case TXST_ITALIC:
246 style = java.awt.Font.ITALIC;
247 break;
248 default:
249 style = java.awt.Font.PLAIN;
250 break;
251 }
252
253 int weight;
254 switch (fontWeight)
255 {
256 case TXWE_BOLD:
257 case TXWE_BOLDER:
258 weight = java.awt.Font.BOLD;
259 break;
260 default:
261 weight = java.awt.Font.PLAIN;
262 break;
263 }
264
265 //Get font
266 Font font = diagram.getUniverse().getFont(fontFamily);
267 if (font == null)
268 {
269// System.err.println("Could not load font");
270
271 java.awt.Font sysFont = new java.awt.Font(fontFamily, style | weight, (int) fontSize);
272 buildSysFont(sysFont);
273 return;
274 }
275
276// font = new java.awt.Font(font.getFamily(), style | weight, font.getSize());
277
278// Area textArea = new Area();
279 GeneralPath textPath = new GeneralPath();
280 textShape = textPath;
281
282 float cursorX = x, cursorY = y;
283
284 FontFace fontFace = font.getFontFace();
285 //int unitsPerEm = fontFace.getUnitsPerEm();
286 int ascent = fontFace.getAscent();
287 float fontScale = fontSize / (float) ascent;
288
289// AffineTransform oldXform = g.getTransform();
290 AffineTransform xform = new AffineTransform();
291
292 for (Iterator it = content.iterator(); it.hasNext();)
293 {
294 Object obj = it.next();
295
296 if (obj instanceof String)
297 {
298 String text = (String) obj;
299 if (text != null)
300 {
301 text = text.trim();
302 }
303
304 strokeWidthScalar = 1f / fontScale;
305
306 for (int i = 0; i < text.length(); i++)
307 {
308 xform.setToIdentity();
309 xform.setToTranslation(cursorX, cursorY);
310 xform.scale(fontScale, fontScale);
311// g.transform(xform);
312
313 String unicode = text.substring(i, i + 1);
314 MissingGlyph glyph = font.getGlyph(unicode);
315
316 Shape path = glyph.getPath();
317 if (path != null)
318 {
319 path = xform.createTransformedShape(path);
320 textPath.append(path, false);
321 }
322// else glyph.render(g);
323
324 cursorX += fontScale * glyph.getHorizAdvX();
325
326// g.setTransform(oldXform);
327 }
328
329 strokeWidthScalar = 1f;
330 } else if (obj instanceof Tspan)
331 {
332 Tspan tspan = (Tspan) obj;
333
334 xform.setToIdentity();
335 xform.setToTranslation(cursorX, cursorY);
336 xform.scale(fontScale, fontScale);
337// tspan.setCursorX(cursorX);
338// tspan.setCursorY(cursorY);
339
340 Shape tspanShape = tspan.getShape();
341 tspanShape = xform.createTransformedShape(tspanShape);
342 textPath.append(tspanShape, false);
343// tspan.render(g);
344// cursorX = tspan.getCursorX();
345// cursorY = tspan.getCursorY();
346 }
347
348 }
349
350 switch (textAnchor)
351 {
352 case TXAN_MIDDLE:
353 {
354 AffineTransform at = new AffineTransform();
355 at.translate(-textPath.getBounds().getWidth() / 2, 0);
356 textPath.transform(at);
357 break;
358 }
359 case TXAN_END:
360 {
361 AffineTransform at = new AffineTransform();
362 at.translate(-textPath.getBounds().getWidth(), 0);
363 textPath.transform(at);
364 break;
365 }
366 }
367 }
368
369 private void buildSysFont(java.awt.Font font) throws SVGException
370 {
371 GeneralPath textPath = new GeneralPath();
372 textShape = textPath;
373
374 float cursorX = x, cursorY = y;
375
376// FontMetrics fm = g.getFontMetrics(font);
377 FontRenderContext frc = new FontRenderContext(null, true, true);
378
379// FontFace fontFace = font.getFontFace();
380 //int unitsPerEm = fontFace.getUnitsPerEm();
381// int ascent = fm.getAscent();
382// float fontScale = fontSize / (float)ascent;
383
384// AffineTransform oldXform = g.getTransform();
385 AffineTransform xform = new AffineTransform();
386
387 for (Iterator it = content.iterator(); it.hasNext();)
388 {
389 Object obj = it.next();
390
391 if (obj instanceof String)
392 {
393 String text = (String)obj;
394 text = text.trim();
395
396 Shape textShape = font.createGlyphVector(frc, text).getOutline(cursorX, cursorY);
397 textPath.append(textShape, false);
398// renderShape(g, textShape);
399// g.drawString(text, cursorX, cursorY);
400
401 Rectangle2D rect = font.getStringBounds(text, frc);
402 cursorX += (float) rect.getWidth();
403 } else if (obj instanceof Tspan)
404 {
405 /*
406 Tspan tspan = (Tspan)obj;
407
408 xform.setToIdentity();
409 xform.setToTranslation(cursorX, cursorY);
410
411 Shape tspanShape = tspan.getShape();
412 tspanShape = xform.createTransformedShape(tspanShape);
413 textArea.add(new Area(tspanShape));
414
415 cursorX += tspanShape.getBounds2D().getWidth();
416 */
417
418
419 Tspan tspan = (Tspan)obj;
420 Point2D cursor = new Point2D.Float(cursorX, cursorY);
421// tspan.setCursorX(cursorX);
422// tspan.setCursorY(cursorY);
423 tspan.appendToShape(textPath, cursor);
424// cursorX = tspan.getCursorX();
425// cursorY = tspan.getCursorY();
426 cursorX = (float)cursor.getX();
427 cursorY = (float)cursor.getY();
428
429 }
430 }
431
432 switch (textAnchor)
433 {
434 case TXAN_MIDDLE:
435 {
436 AffineTransform at = new AffineTransform();
437 at.translate(-textPath.getBounds().getWidth() / 2, 0);
438 textPath.transform(at);
439 break;
440 }
441 case TXAN_END:
442 {
443 AffineTransform at = new AffineTransform();
444 at.translate(-Math.ceil(textPath.getBounds().getWidth()), 0);
445 textPath.transform(at);
446 break;
447 }
448 }
449 }
450
451 public void render(Graphics2D g) throws SVGException
452 {
453 beginLayer(g);
454 renderShape(g, textShape);
455 finishLayer(g);
456 }
457
458 public Shape getShape()
459 {
460 return shapeToParent(textShape);
461 }
462
463 public Rectangle2D getBoundingBox() throws SVGException
464 {
465 return boundsToParent(includeStrokeInBounds(textShape.getBounds2D()));
466 }
467
468 /**
469 * Updates all attributes in this diagram associated with a time event. Ie,
470 * all attributes with track information.
471 *
472 * @return - true if this node has changed state as a result of the time
473 * update
474 */
475 public boolean updateTime(double curTime) throws SVGException
476 {
477// if (trackManager.getNumTracks() == 0) return false;
478 boolean changeState = super.updateTime(curTime);
479
480 //Get current values for parameters
481 StyleAttribute sty = new StyleAttribute();
482 boolean shapeChange = false;
483
484 if (getPres(sty.setName("x")))
485 {
486 float newVal = sty.getFloatValueWithUnits();
487 if (newVal != x)
488 {
489 x = newVal;
490 shapeChange = true;
491 }
492 }
493
494 if (getPres(sty.setName("y")))
495 {
496 float newVal = sty.getFloatValueWithUnits();
497 if (newVal != y)
498 {
499 y = newVal;
500 shapeChange = true;
501 }
502 }
503
504 if (getPres(sty.setName("font-family")))
505 {
506 String newVal = sty.getStringValue();
507 if (!newVal.equals(fontFamily))
508 {
509 fontFamily = newVal;
510 shapeChange = true;
511 }
512 }
513
514 if (getPres(sty.setName("font-size")))
515 {
516 float newVal = sty.getFloatValueWithUnits();
517 if (newVal != fontSize)
518 {
519 fontSize = newVal;
520 shapeChange = true;
521 }
522 }
523
524
525 if (getStyle(sty.setName("font-style")))
526 {
527 String s = sty.getStringValue();
528 int newVal = fontStyle;
529 if ("normal".equals(s))
530 {
531 newVal = TXST_NORMAL;
532 } else if ("italic".equals(s))
533 {
534 newVal = TXST_ITALIC;
535 } else if ("oblique".equals(s))
536 {
537 newVal = TXST_OBLIQUE;
538 }
539 if (newVal != fontStyle)
540 {
541 fontStyle = newVal;
542 shapeChange = true;
543 }
544 }
545
546 if (getStyle(sty.setName("font-weight")))
547 {
548 String s = sty.getStringValue();
549 int newVal = fontWeight;
550 if ("normal".equals(s))
551 {
552 newVal = TXWE_NORMAL;
553 } else if ("bold".equals(s))
554 {
555 newVal = TXWE_BOLD;
556 }
557 if (newVal != fontWeight)
558 {
559 fontWeight = newVal;
560 shapeChange = true;
561 }
562 }
563
564 if (shapeChange)
565 {
566 build();
567// buildFont();
568// return true;
569 }
570
571 return changeState || shapeChange;
572 }
573}
Note: See TracBrowser for help on using the repository browser.