/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.apache.batik.bridge;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator;
import java.text.CharacterIterator;
import java.util.List;
import org.apache.batik.gvt.AbstractGraphicsNode;
import org.apache.batik.gvt.Selectable;
import org.apache.batik.gvt.text.AttributedCharacterSpanIterator;
import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
import org.apache.batik.gvt.text.TextPaintInfo;
A graphics node that represents text.
Author: Thierry Kormann Version: $Id: TextNode.java 1808888 2017-09-19 14:22:11Z ssteiner $
/**
* A graphics node that represents text.
*
* @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a>
* @version $Id: TextNode.java 1808888 2017-09-19 14:22:11Z ssteiner $
*/
public class TextNode extends AbstractGraphicsNode implements Selectable {
public static final
AttributedCharacterIterator.Attribute PAINT_INFO =
GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO;
Location of this text node (inherited, independent of explicit
X and Y attributes applied to children).
/**
* Location of this text node (inherited, independent of explicit
* X and Y attributes applied to children).
*/
protected Point2D location = new Point2D.Float(0, 0);
Attributed Character Iterator describing the text
/**
* Attributed Character Iterator describing the text
*/
protected AttributedCharacterIterator aci;
The text of this TextNode
.
/**
* The text of this <code>TextNode</code>.
*/
protected String text;
The begin mark.
/**
* The begin mark.
*/
protected Mark beginMark = null;
The end mark.
/**
* The end mark.
*/
protected Mark endMark = null;
The list of text runs.
/**
* The list of text runs.
*/
protected List textRuns;
The text painter used to display the text of this text node.
/**
* The text painter used to display the text of this text node.
*/
protected TextPainter textPainter = StrokingTextPainter.getInstance();
Internal Cache: Bounds for this text node, without taking any of the
rendering attributes (e.g., stroke) into account
/**
* Internal Cache: Bounds for this text node, without taking any of the
* rendering attributes (e.g., stroke) into account
*/
private Rectangle2D geometryBounds;
Internal Cache: Primitive Bounds.
/**
* Internal Cache: Primitive Bounds.
*/
private Rectangle2D primitiveBounds;
Internal Cache: the outline.
/**
* Internal Cache: the outline.
*/
private Shape outline;
Constructs a new empty TextNode
.
/**
* Constructs a new empty <code>TextNode</code>.
*/
public TextNode() {
}
Sets the text painter of this text node. If the specified text
painter is null, this text node will use its default text
painter (StrokingTextPainter.getInstance()).
Params: - textPainter – the text painter to use
/**
* Sets the text painter of this text node. If the specified text
* painter is null, this text node will use its default text
* painter (StrokingTextPainter.getInstance()).
*
* @param textPainter the text painter to use
*/
public void setTextPainter(TextPainter textPainter) {
if (textPainter == null) {
this.textPainter = StrokingTextPainter.getInstance();
} else {
this.textPainter = textPainter;
}
}
Returns the text painter of this text node.
/**
* Returns the text painter of this text node.
*/
public TextPainter getTextPainter() {
return textPainter;
}
Returns a list of text runs.
/**
* Returns a list of text runs.
*/
public List getTextRuns() {
return textRuns;
}
Sets the list of text runs of this text node.
Params: - textRuns – the new list of text runs
/**
* Sets the list of text runs of this text node.
*
* @param textRuns the new list of text runs
*/
public void setTextRuns(List textRuns) {
this.textRuns = textRuns;
}
Returns the text of this TextNode
as a string.
/**
* Returns the text of this <code>TextNode</code> as a string.
*/
public String getText() {
if (text != null)
return text;
if (aci == null) {
text = "";
} else {
StringBuffer buf = new StringBuffer(aci.getEndIndex());
for (char c = aci.first();
c != CharacterIterator.DONE;
c = aci.next()) {
buf.append(c);
}
text = buf.toString();
}
return text;
}
Sets the location of this text node.
Params: - newLocation – the new location of this text node
/**
* Sets the location of this text node.
*
* @param newLocation the new location of this text node
*/
public void setLocation(Point2D newLocation){
fireGraphicsNodeChangeStarted();
invalidateGeometryCache();
this.location = newLocation;
fireGraphicsNodeChangeCompleted();
}
Returns the location of this text node.
Returns: the location of this text node
/**
* Returns the location of this text node.
*
* @return the location of this text node
*/
public Point2D getLocation(){
return location;
}
public void swapTextPaintInfo(TextPaintInfo newInfo,
TextPaintInfo oldInfo) {
fireGraphicsNodeChangeStarted();
invalidateGeometryCache();
oldInfo.set(newInfo);
fireGraphicsNodeChangeCompleted();
}
Sets the attributed character iterator of this text node.
Params: - newAci – the new attributed character iterator
/**
* Sets the attributed character iterator of this text node.
*
* @param newAci the new attributed character iterator
*/
public void setAttributedCharacterIterator
(AttributedCharacterIterator newAci) {
fireGraphicsNodeChangeStarted();
invalidateGeometryCache();
this.aci = newAci;
text = null;
textRuns = null;
fireGraphicsNodeChangeCompleted();
}
Returns the attributed character iterator of this text node.
Returns: the attributed character iterator
/**
* Returns the attributed character iterator of this text node.
*
* @return the attributed character iterator
*/
public AttributedCharacterIterator getAttributedCharacterIterator(){
return aci;
}
//
// Geometric methods
//
Invalidates this TextNode
. This node and all its ancestors have
been informed that all its cached values related to its bounds must be
recomputed.
/**
* Invalidates this <code>TextNode</code>. This node and all its ancestors have
* been informed that all its cached values related to its bounds must be
* recomputed.
*/
protected void invalidateGeometryCache() {
super.invalidateGeometryCache();
primitiveBounds = null;
geometryBounds = null;
outline = null;
}
Returns the bounds of the area covered by this node's primitive paint.
/**
* Returns the bounds of the area covered by this node's primitive paint.
*/
public Rectangle2D getPrimitiveBounds(){
if (primitiveBounds == null) {
if (aci != null) {
primitiveBounds = textPainter.getBounds2D(this);
}
}
return primitiveBounds;
}
Returns the bounds of the area covered by this node, without
taking any of its rendering attribute into account. That is,
exclusive of any clipping, masking, filtering or stroking, for
example.
/**
* Returns the bounds of the area covered by this node, without
* taking any of its rendering attribute into account. That is,
* exclusive of any clipping, masking, filtering or stroking, for
* example.
*/
public Rectangle2D getGeometryBounds(){
if (geometryBounds == null){
if (aci != null) {
geometryBounds = textPainter.getGeometryBounds(this);
}
}
return geometryBounds;
}
Returns the bounds of the sensitive area covered by this node,
This includes the stroked area but does not include the effects
of clipping, masking or filtering.
/**
* Returns the bounds of the sensitive area covered by this node,
* This includes the stroked area but does not include the effects
* of clipping, masking or filtering.
*/
public Rectangle2D getSensitiveBounds() {
return getGeometryBounds();
}
Returns the outline of this node.
/**
* Returns the outline of this node.
*/
public Shape getOutline() {
if (outline == null) {
if (aci != null) {
outline = textPainter.getOutline(this);
}
}
return outline;
}
Return the marker for the character at index in this nodes
AttributedCharacterIterator. Before Char indicates if the
Marker should be considered before or after char.
/**
* Return the marker for the character at index in this nodes
* AttributedCharacterIterator. Before Char indicates if the
* Marker should be considered before or after char.
*/
public Mark getMarkerForChar(int index, boolean beforeChar) {
return textPainter.getMark(this, index, beforeChar);
}
//
// Selection methods
//
public void setSelection(Mark begin, Mark end) {
if ((begin.getTextNode() != this) ||
(end.getTextNode() != this))
throw new RuntimeException("Markers not from this TextNode");
beginMark = begin;
endMark = end;
}
Initializes the current selection to begin with the character at (x, y).
Params: - x – the x coordinate of the start of the selection
- y – the y coordinate of the start of the selection
/**
* Initializes the current selection to begin with the character at (x, y).
* @param x the x coordinate of the start of the selection
* @param y the y coordinate of the start of the selection
*/
public boolean selectAt(double x, double y) {
beginMark = textPainter.selectAt(x, y, this);
return true; // assume this always changes selection, for now.
}
Extends the current selection to the character at (x, y).
Params: - x – the x coordinate of the end of the selection
- y – the y coordinate of the end of the selection
/**
* Extends the current selection to the character at (x, y).
* @param x the x coordinate of the end of the selection
* @param y the y coordinate of the end of the selection
*/
public boolean selectTo(double x, double y) {
Mark tmpMark = textPainter.selectTo(x, y, beginMark);
if (tmpMark == null)
return false;
if (tmpMark != endMark) {
endMark = tmpMark;
return true;
}
return false;
}
Selects all the text in this TextNode. The coordinates are ignored.
Params: - x – the x coordinate of the point the selection was made
- y – the y coordinate of the point the selection was made
/**
* Selects all the text in this TextNode. The coordinates are ignored.
* @param x the x coordinate of the point the selection was made
* @param y the y coordinate of the point the selection was made
*/
public boolean selectAll(double x, double y) {
beginMark = textPainter.selectFirst(this);
endMark = textPainter.selectLast(this);
return true; // assume this always changes selection, for now.
}
Gets the current text selection.
Returns: an object containing the selected content.
/**
* Gets the current text selection.
*
* @return an object containing the selected content.
*/
public Object getSelection() {
Object o = null;
if (aci == null) return o;
int[] ranges = textPainter.getSelected(beginMark, endMark);
// TODO: later we can return more complex things like
// noncontiguous selections
if ((ranges != null) && (ranges.length > 1)) {
// make sure that they are in order
if (ranges[0] > ranges[1]) {
int temp = ranges[1];
ranges[1] = ranges[0];
ranges[0] = temp;
}
o = new AttributedCharacterSpanIterator
(aci, ranges[0], ranges[1]+1);
}
return o;
}
Returns the shape used to outline this text node.
Returns: a Shape which encloses the current text selection.
/**
* Returns the shape used to outline this text node.
*
* @return a Shape which encloses the current text selection.
*/
public Shape getHighlightShape() {
Shape highlightShape =
textPainter.getHighlightShape(beginMark, endMark);
AffineTransform t = getGlobalTransform();
highlightShape = t.createTransformedShape(highlightShape);
return highlightShape;
}
//
// Drawing methods
//
Paints this node without applying Filter, Mask, Composite, and clip.
Params: - g2d – the Graphics2D to use
/**
* Paints this node without applying Filter, Mask, Composite, and clip.
*
* @param g2d the Graphics2D to use
*/
public void primitivePaint(Graphics2D g2d) {
//
// DO NOT REMOVE: THE FOLLOWING IS A WORK AROUND
// A BUG IN THE JDK 1.2 RENDERING PIPELINE WHEN
// THE CLIP IS A RECTANGLE
//
Shape clip = g2d.getClip();
if (clip != null && !(clip instanceof GeneralPath)) {
g2d.setClip(new GeneralPath(clip));
}
// Paint the text
textPainter.paint(this, g2d);
}
//
// Geometric methods
//
Returns true if the specified Point2D is inside the boundary of this
node, false otherwise.
Params: - p – the specified Point2D in the user space
/**
* Returns true if the specified Point2D is inside the boundary of this
* node, false otherwise.
*
* @param p the specified Point2D in the user space
*/
public boolean contains(Point2D p) {
// <!> FIXME: should put this code in TextPaint somewhere,
// as pointer-events support - same problem with pointer-events
// and ShapeNode
if (!super.contains(p)) {
return false;
}
List list = getTextRuns();
// place coords in text node coordinate system
for (Object aList : list) {
StrokingTextPainter.TextRun run =
(StrokingTextPainter.TextRun) aList;
TextSpanLayout layout = run.getLayout();
float x = (float) p.getX();
float y = (float) p.getY();
TextHit textHit = layout.hitTestChar(x, y);
if (textHit != null && contains(p, layout.getBounds2D())) {
return true;
}
}
return false;
}
protected boolean contains(Point2D p, Rectangle2D b) {
if (b == null || !b.contains(p)) {
return false;
}
switch(pointerEventType) {
case VISIBLE_PAINTED:
case VISIBLE_FILL:
case VISIBLE_STROKE:
case VISIBLE:
return isVisible;
case PAINTED:
case FILL:
case STROKE:
case ALL:
return true;
case NONE:
return false;
default:
return false;
}
}
Defines where the text of a TextNode
can be anchored
relative to its location.
/**
* Defines where the text of a <code>TextNode</code> can be anchored
* relative to its location.
*/
public static final class Anchor implements java.io.Serializable {
The type of the START anchor.
/**
* The type of the START anchor.
*/
public static final int ANCHOR_START = 0;
The type of the MIDDLE anchor.
/**
* The type of the MIDDLE anchor.
*/
public static final int ANCHOR_MIDDLE = 1;
The type of the END anchor.
/**
* The type of the END anchor.
*/
public static final int ANCHOR_END = 2;
The anchor which enables the rendered characters to be aligned such
that the start of the text string is at the initial current text
location.
/**
* The anchor which enables the rendered characters to be aligned such
* that the start of the text string is at the initial current text
* location.
*/
public static final Anchor START = new Anchor(ANCHOR_START);
The anchor which enables the rendered characters to be aligned such
that the middle of the text string is at the initial current text
location.
/**
* The anchor which enables the rendered characters to be aligned such
* that the middle of the text string is at the initial current text
* location.
*/
public static final Anchor MIDDLE = new Anchor(ANCHOR_MIDDLE);
The anchor which enables the rendered characters to be aligned such
that the end of the text string is at the initial current text
location.
/**
* The anchor which enables the rendered characters to be aligned such
* that the end of the text string is at the initial current text
* location.
*/
public static final Anchor END = new Anchor(ANCHOR_END);
The anchor type.
/**
* The anchor type.
*/
private int type;
No instance of this class.
/**
* No instance of this class.
*/
private Anchor(int type) {
this.type = type;
}
Returns the type of this anchor.
/**
* Returns the type of this anchor.
*/
public int getType() {
return type;
}
This is called by the serialization code before it returns
an unserialized object. To provide for unicity of
instances, the instance that was read is replaced by its
static equivalent. See the serialiazation specification for
further details on this method's logic.
/**
* This is called by the serialization code before it returns
* an unserialized object. To provide for unicity of
* instances, the instance that was read is replaced by its
* static equivalent. See the serialiazation specification for
* further details on this method's logic.
*/
private Object readResolve() throws java.io.ObjectStreamException {
switch(type){
case ANCHOR_START:
return START;
case ANCHOR_MIDDLE:
return MIDDLE;
case ANCHOR_END:
return END;
default:
throw new RuntimeException("Unknown Anchor type");
}
}
}
}