001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.gvt;
020:
021: import java.awt.Graphics2D;
022: import java.awt.Shape;
023: import java.awt.geom.AffineTransform;
024: import java.awt.geom.GeneralPath;
025: import java.awt.geom.Point2D;
026: import java.awt.geom.Rectangle2D;
027: import java.text.AttributedCharacterIterator;
028: import java.text.CharacterIterator;
029: import java.util.List;
030:
031: import org.apache.batik.gvt.renderer.StrokingTextPainter;
032: import org.apache.batik.gvt.text.AttributedCharacterSpanIterator;
033: import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
034: import org.apache.batik.gvt.text.Mark;
035: import org.apache.batik.gvt.text.TextHit;
036: import org.apache.batik.gvt.text.TextPaintInfo;
037: import org.apache.batik.gvt.text.TextSpanLayout;
038:
039: /**
040: * A graphics node that represents text.
041: *
042: * @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a>
043: * @version $Id: TextNode.java 475477 2006-11-15 22:44:28Z cam $
044: */
045: public class TextNode extends AbstractGraphicsNode implements
046: Selectable {
047:
048: public static final AttributedCharacterIterator.Attribute PAINT_INFO = GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO;
049:
050: /**
051: * Location of this text node (inherited, independent of explicit
052: * X and Y attributes applied to children).
053: */
054: protected Point2D location = new Point2D.Float(0, 0);
055:
056: /**
057: * Attributed Character Iterator describing the text
058: */
059: protected AttributedCharacterIterator aci;
060:
061: /**
062: * The text of this <tt>TextNode</tt>.
063: */
064: protected String text;
065:
066: /**
067: * The begin mark.
068: */
069: protected Mark beginMark = null;
070:
071: /**
072: * The end mark.
073: */
074: protected Mark endMark = null;
075:
076: /**
077: * The list of text runs.
078: */
079: protected List textRuns;
080:
081: /**
082: * The text painter used to display the text of this text node.
083: */
084: protected TextPainter textPainter = StrokingTextPainter
085: .getInstance();
086:
087: /**
088: * Internal Cache: Bounds for this text node, without taking any of the
089: * rendering attributes (e.g., stroke) into account
090: */
091: private Rectangle2D geometryBounds;
092:
093: /**
094: * Internal Cache: Primitive Bounds.
095: */
096: private Rectangle2D primitiveBounds;
097:
098: /**
099: * Internal Cache: the outline.
100: */
101: private Shape outline;
102:
103: /**
104: * Constructs a new empty <tt>TextNode</tt>.
105: */
106: public TextNode() {
107: }
108:
109: /**
110: * Sets the text painter of this text node. If the specified text
111: * painter is null, this text node will use its default text
112: * painter (StrokingTextPainter.getInstance()).
113: *
114: * @param textPainter the text painter to use
115: */
116: public void setTextPainter(TextPainter textPainter) {
117: if (textPainter == null) {
118: this .textPainter = StrokingTextPainter.getInstance();
119: } else {
120: this .textPainter = textPainter;
121: }
122: }
123:
124: /**
125: * Returns the text painter of this text node.
126: */
127: public TextPainter getTextPainter() {
128: return textPainter;
129: }
130:
131: /**
132: * Returns a list of text runs.
133: */
134: public List getTextRuns() {
135: return textRuns;
136: }
137:
138: /**
139: * Sets the list of text runs of this text node.
140: *
141: * @param textRuns the new list of text runs
142: */
143: public void setTextRuns(List textRuns) {
144: this .textRuns = textRuns;
145: }
146:
147: /**
148: * Returns the text of this <tt>TextNode</tt> as a string.
149: */
150: public String getText() {
151:
152: if (text != null)
153: return text;
154:
155: if (aci == null) {
156: text = "";
157: } else {
158: StringBuffer buf = new StringBuffer(aci.getEndIndex());
159: for (char c = aci.first(); c != CharacterIterator.DONE; c = aci
160: .next()) {
161: buf.append(c);
162: }
163: text = buf.toString();
164: }
165: return text;
166: }
167:
168: /**
169: * Sets the location of this text node.
170: *
171: * @param newLocation the new location of this text node
172: */
173: public void setLocation(Point2D newLocation) {
174: fireGraphicsNodeChangeStarted();
175: invalidateGeometryCache();
176: this .location = newLocation;
177: fireGraphicsNodeChangeCompleted();
178: }
179:
180: /**
181: * Returns the location of this text node.
182: *
183: * @return the location of this text node
184: */
185: public Point2D getLocation() {
186: return location;
187: }
188:
189: public void swapTextPaintInfo(TextPaintInfo newInfo,
190: TextPaintInfo oldInfo) {
191: fireGraphicsNodeChangeStarted();
192: invalidateGeometryCache();
193: oldInfo.set(newInfo);
194: fireGraphicsNodeChangeCompleted();
195: }
196:
197: /**
198: * Sets the attributed character iterator of this text node.
199: *
200: * @param newAci the new attributed character iterator
201: */
202: public void setAttributedCharacterIterator(
203: AttributedCharacterIterator newAci) {
204: fireGraphicsNodeChangeStarted();
205: invalidateGeometryCache();
206: this .aci = newAci;
207: text = null;
208: textRuns = null;
209: fireGraphicsNodeChangeCompleted();
210: }
211:
212: /**
213: * Returns the attributed character iterator of this text node.
214: *
215: * @return the attributed character iterator
216: */
217: public AttributedCharacterIterator getAttributedCharacterIterator() {
218: return aci;
219: }
220:
221: //
222: // Geometric methods
223: //
224:
225: /**
226: * Invalidates this <tt>TextNode</tt>. This node and all its ancestors have
227: * been informed that all its cached values related to its bounds must be
228: * recomputed.
229: */
230: protected void invalidateGeometryCache() {
231: super .invalidateGeometryCache();
232: primitiveBounds = null;
233: geometryBounds = null;
234: outline = null;
235: }
236:
237: /**
238: * Returns the bounds of the area covered by this node's primitive paint.
239: */
240: public Rectangle2D getPrimitiveBounds() {
241: if (primitiveBounds == null) {
242: if (aci != null) {
243: primitiveBounds = textPainter.getBounds2D(this );
244: }
245: }
246: return primitiveBounds;
247: }
248:
249: /**
250: * Returns the bounds of the area covered by this node, without
251: * taking any of its rendering attribute into account. That is,
252: * exclusive of any clipping, masking, filtering or stroking, for
253: * example.
254: */
255: public Rectangle2D getGeometryBounds() {
256: if (geometryBounds == null) {
257: if (aci != null) {
258: geometryBounds = textPainter.getGeometryBounds(this );
259: }
260: }
261: return geometryBounds;
262: }
263:
264: /**
265: * Returns the bounds of the sensitive area covered by this node,
266: * This includes the stroked area but does not include the effects
267: * of clipping, masking or filtering.
268: */
269: public Rectangle2D getSensitiveBounds() {
270: return getGeometryBounds();
271: }
272:
273: /**
274: * Returns the outline of this node.
275: */
276: public Shape getOutline() {
277: if (outline == null) {
278: if (aci != null) {
279: outline = textPainter.getOutline(this );
280: }
281: }
282: return outline;
283: }
284:
285: /**
286: * Return the marker for the character at index in this nodes
287: * AttributedCharacterIterator. Before Char indicates if the
288: * Marker should be considered before or after char.
289: */
290: public Mark getMarkerForChar(int index, boolean beforeChar) {
291: return textPainter.getMark(this , index, beforeChar);
292: }
293:
294: //
295: // Selection methods
296: //
297: public void setSelection(Mark begin, Mark end) {
298: if ((begin.getTextNode() != this )
299: || (end.getTextNode() != this ))
300: throw new Error("Markers not from this TextNode");
301:
302: beginMark = begin;
303: endMark = end;
304: }
305:
306: /**
307: * Initializes the current selection to begin with the character at (x, y).
308: * @param x the x coordinate of the start of the selection
309: * @param y the y coordinate of the start of the selection
310: */
311: public boolean selectAt(double x, double y) {
312: beginMark = textPainter.selectAt(x, y, this );
313: return true; // assume this always changes selection, for now.
314: }
315:
316: /**
317: * Extends the current selection to the character at (x, y).
318: * @param x the x coordinate of the end of the selection
319: * @param y the y coordinate of the end of the selection
320: */
321: public boolean selectTo(double x, double y) {
322: Mark tmpMark = textPainter.selectTo(x, y, beginMark);
323: if (tmpMark == null)
324: return false;
325: if (tmpMark != endMark) {
326: endMark = tmpMark;
327: return true;
328: }
329: return false;
330: }
331:
332: /**
333: * Selects all the text in this TextNode. The coordinates are ignored.
334: * @param x the x coordinate of the point the selection was made
335: * @param y the y coordinate of the point the selection was made
336: */
337: public boolean selectAll(double x, double y) {
338: beginMark = textPainter.selectFirst(this );
339: endMark = textPainter.selectLast(this );
340: return true; // assume this always changes selection, for now.
341: }
342:
343: /**
344: * Gets the current text selection.
345: *
346: * @return an object containing the selected content.
347: */
348: public Object getSelection() {
349: Object o = null;
350: if (aci == null)
351: return o;
352:
353: int[] ranges = textPainter.getSelected(beginMark, endMark);
354:
355: // TODO: later we can return more complex things like
356: // noncontiguous selections
357: if ((ranges != null) && (ranges.length > 1)) {
358: // make sure that they are in order
359: if (ranges[0] > ranges[1]) {
360: int temp = ranges[1];
361: ranges[1] = ranges[0];
362: ranges[0] = temp;
363: }
364: o = new AttributedCharacterSpanIterator(aci, ranges[0],
365: ranges[1] + 1);
366: }
367: return o;
368: }
369:
370: /**
371: * Returns the shape used to outline this text node.
372: *
373: * @return a Shape which encloses the current text selection.
374: */
375: public Shape getHighlightShape() {
376: Shape highlightShape = textPainter.getHighlightShape(beginMark,
377: endMark);
378: AffineTransform t = getGlobalTransform();
379: highlightShape = t.createTransformedShape(highlightShape);
380: return highlightShape;
381: }
382:
383: //
384: // Drawing methods
385: //
386:
387: /**
388: * Paints this node without applying Filter, Mask, Composite, and clip.
389: *
390: * @param g2d the Graphics2D to use
391: */
392: public void primitivePaint(Graphics2D g2d) {
393: //
394: // DO NOT REMOVE: THE FOLLOWING IS A WORK AROUND
395: // A BUG IN THE JDK 1.2 RENDERING PIPELINE WHEN
396: // THE CLIP IS A RECTANGLE
397: //
398: Shape clip = g2d.getClip();
399: if (clip != null && !(clip instanceof GeneralPath)) {
400: g2d.setClip(new GeneralPath(clip));
401: }
402: // Paint the text
403: textPainter.paint(this , g2d);
404: }
405:
406: //
407: // Geometric methods
408: //
409:
410: /**
411: * Returns true if the specified Point2D is inside the boundary of this
412: * node, false otherwise.
413: *
414: * @param p the specified Point2D in the user space
415: */
416: public boolean contains(Point2D p) {
417: // <!> FIXME: should put this code in TextPaint somewhere,
418: // as pointer-events support - same problem with pointer-events
419: // and ShapeNode
420: if (!super .contains(p)) {
421: return false;
422: }
423: List list = getTextRuns();
424: // place coords in text node coordinate system
425: for (int i = 0; i < list.size(); i++) {
426: StrokingTextPainter.TextRun run = (StrokingTextPainter.TextRun) list
427: .get(i);
428: TextSpanLayout layout = run.getLayout();
429: float x = (float) p.getX();
430: float y = (float) p.getY();
431: TextHit textHit = layout.hitTestChar(x, y);
432: if (textHit != null && contains(p, layout.getBounds2D())) {
433: return true;
434: }
435: }
436: return false;
437: }
438:
439: protected boolean contains(Point2D p, Rectangle2D b) {
440: if (b == null || !b.contains(p)) {
441: return false;
442: }
443: switch (pointerEventType) {
444: case VISIBLE_PAINTED:
445: case VISIBLE_FILL:
446: case VISIBLE_STROKE:
447: case VISIBLE:
448: return isVisible;
449: case PAINTED:
450: case FILL:
451: case STROKE:
452: case ALL:
453: return true;
454: case NONE:
455: return false;
456: default:
457: return false;
458: }
459: }
460:
461: /**
462: * Defines where the text of a <tt>TextNode</tt> can be anchored
463: * relative to its location.
464: */
465: public static final class Anchor implements java.io.Serializable {
466:
467: /**
468: * The type of the START anchor.
469: */
470: public static final int ANCHOR_START = 0;
471:
472: /**
473: * The type of the MIDDLE anchor.
474: */
475: public static final int ANCHOR_MIDDLE = 1;
476:
477: /**
478: * The type of the END anchor.
479: */
480: public static final int ANCHOR_END = 2;
481:
482: /**
483: * The anchor which enables the rendered characters to be aligned such
484: * that the start of the text string is at the initial current text
485: * location.
486: */
487: public static final Anchor START = new Anchor(ANCHOR_START);
488:
489: /**
490: * The anchor which enables the rendered characters to be aligned such
491: * that the middle of the text string is at the initial current text
492: * location.
493: */
494: public static final Anchor MIDDLE = new Anchor(ANCHOR_MIDDLE);
495:
496: /**
497: * The anchor which enables the rendered characters to be aligned such
498: * that the end of the text string is at the initial current text
499: * location.
500: */
501: public static final Anchor END = new Anchor(ANCHOR_END);
502:
503: /**
504: * The anchor type.
505: */
506: private int type;
507:
508: /**
509: * No instance of this class.
510: */
511: private Anchor(int type) {
512: this .type = type;
513: }
514:
515: /**
516: * Returns the type of this anchor.
517: */
518: public int getType() {
519: return type;
520: }
521:
522: /**
523: * This is called by the serialization code before it returns
524: * an unserialized object. To provide for unicity of
525: * instances, the instance that was read is replaced by its
526: * static equivalent. See the serialiazation specification for
527: * further details on this method's logic.
528: */
529: private Object readResolve()
530: throws java.io.ObjectStreamException {
531: switch (type) {
532: case ANCHOR_START:
533: return START;
534: case ANCHOR_MIDDLE:
535: return MIDDLE;
536: case ANCHOR_END:
537: return END;
538: default:
539: throw new Error("Unknown Anchor type");
540: }
541: }
542: }
543: }
|