You are viewing a plain text version of this content. The canonical link for it is here.
Posted to batik-dev@xmlgraphics.apache.org by di...@apache.org on 2001/04/29 10:22:27 UTC
cvs commit: xml-batik/sources/org/apache/batik/gvt/renderer BasicTextPainter.java ConcreteTextPainter.java StrokingTextPainter.java
dino 01/04/29 01:22:27
Modified: sources/org/apache/batik/gvt/renderer BasicTextPainter.java
ConcreteTextPainter.java StrokingTextPainter.java
Log:
adding first pass at svg fonts
Revision Changes Path
1.3 +29 -27 xml-batik/sources/org/apache/batik/gvt/renderer/BasicTextPainter.java
Index: BasicTextPainter.java
===================================================================
RCS file: /home/cvs/xml-batik/sources/org/apache/batik/gvt/renderer/BasicTextPainter.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- BasicTextPainter.java 2001/02/02 14:13:16 1.2
+++ BasicTextPainter.java 2001/04/29 08:22:26 1.3
@@ -39,9 +39,9 @@
*
* @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
* @author <a href="vincent.hardy@sun.com>Vincent Hardy</a>
- * @version $Id: BasicTextPainter.java,v 1.2 2001/02/02 14:13:16 billh Exp $
+ * @version $Id: BasicTextPainter.java,v 1.3 2001/04/29 08:22:26 dino Exp $
*/
-public class BasicTextPainter implements TextPainter {
+public abstract class BasicTextPainter implements TextPainter {
/**
* Paints the specified text node using the
@@ -51,21 +51,21 @@
* @param g2d the Graphics2D to use
* @param context the rendering context.
*/
- public void paint(TextNode node,
- Graphics2D g2d, GraphicsNodeRenderContext context) {
-
+ public abstract void paint(TextNode node,
+ Graphics2D g2d, GraphicsNodeRenderContext context); // {
+/*
FontRenderContext frc = context.getFontRenderContext();
AttributedCharacterIterator aci = node.getAttributedCharacterIterator();
/* XXX: The code below only
* works for J2SE base implementation of AttributeCharacterIterator
*/
-
+/*
TextSpanLayout layout =
getOffsetAdjustedTextLayout(aci, node.getLocation(), frc);
layout.draw(g2d);
}
-
+*/
private static TextLayoutFactory textLayoutFactory =
new ConcreteTextLayoutFactory();
@@ -180,13 +180,13 @@
* <em>Note: The Mark instances passed must have been instantiated by
* an instance of this enclosing TextPainter implementation.</em>
*/
- public Shape getHighlightShape(org.apache.batik.gvt.text.Mark beginMark,
- org.apache.batik.gvt.text.Mark endMark) {
+ public abstract Shape getHighlightShape(org.apache.batik.gvt.text.Mark beginMark,
+ org.apache.batik.gvt.text.Mark endMark);// {
// TODO: later we can return more complex things like
// noncontiguous selections
- BasicTextPainter.Mark begin;
+ /* BasicTextPainter.Mark begin;
BasicTextPainter.Mark end;
try {
begin = (BasicTextPainter.Mark) beginMark;
@@ -230,8 +230,8 @@
}
return highlightShape;
}
+*/
-
/*
* Get a Rectangle2D in userspace coords which encloses the textnode
* glyphs composed from an AttributedCharacterIterator.
@@ -282,11 +282,11 @@
* @param includeStrokeWidth whether to include the effect of stroke width
* in bounds computation.
*/
- protected Rectangle2D getBounds(TextNode node,
+ protected abstract Rectangle2D getBounds(TextNode node,
FontRenderContext context,
boolean includeDecoration,
- boolean includeStrokeWidth) {
-
+ boolean includeStrokeWidth); //{
+/*
AttributedCharacterIterator aci =
node.getAttributedCharacterIterator();
TextSpanLayout layout =
@@ -311,7 +311,7 @@
return bounds;
}
-
+*/
/*
* Get a Shape in userspace coords which defines the textnode glyph outlines.
* @param node the TextNode to measure
@@ -319,9 +319,9 @@
* @param includeDecoration whether to include text decoration
* outlines.
*/
- protected Shape getOutline(TextNode node, FontRenderContext frc,
- boolean includeDecoration) {
- Shape outline;
+ protected abstract Shape getOutline(TextNode node, FontRenderContext frc,
+ boolean includeDecoration); // {
+/* Shape outline;
AttributedCharacterIterator aci = node.getAttributedCharacterIterator();
TextSpanLayout layout =
getOffsetAdjustedTextLayout(aci, node.getLocation(), frc);
@@ -356,7 +356,7 @@
return outline;
}
-
+*/
/*
* Get a Shape in userspace coords which defines the textnode glyph outlines.
* @param node the TextNode to measure
@@ -406,7 +406,7 @@
* TODO: fix/replace, removing dependencies on this code!
*/
- private TextSpanLayout getOffsetAdjustedTextLayout(
+ /* private TextSpanLayout getOffsetAdjustedTextLayout(
AttributedCharacterIterator aci,
Point2D location,
FontRenderContext frc) {
@@ -453,16 +453,18 @@
return layout;
}
+*/
+
+ protected Mark cachedMark = null;
+ protected AttributedCharacterIterator cachedACI = null;
+ protected TextHit cachedHit = null;
- private Mark cachedMark = null;
- private AttributedCharacterIterator cachedACI = null;
- private TextHit cachedHit = null;
- private org.apache.batik.gvt.text.Mark hitTest(
+ protected abstract org.apache.batik.gvt.text.Mark hitTest(
double x, double y, AttributedCharacterIterator aci,
TextNode node,
- GraphicsNodeRenderContext context) {
-
+ GraphicsNodeRenderContext context);// {
+/*
FontRenderContext frc = context.getFontRenderContext();
TextSpanLayout layout =
@@ -486,8 +488,8 @@
return cachedMark;
}
-
+*/
/**
* This TextPainter's implementation of the Mark interface.
1.2 +3 -3 xml-batik/sources/org/apache/batik/gvt/renderer/ConcreteTextPainter.java
Index: ConcreteTextPainter.java
===================================================================
RCS file: /home/cvs/xml-batik/sources/org/apache/batik/gvt/renderer/ConcreteTextPainter.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- ConcreteTextPainter.java 2001/01/23 17:08:01 1.1
+++ ConcreteTextPainter.java 2001/04/29 08:22:26 1.2
@@ -23,9 +23,9 @@
* Renders the attributed character iterator of a <tt>TextNode</tt>.
*
* @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
- * @version $Id: ConcreteTextPainter.java,v 1.1 2001/01/23 17:08:01 tkormann Exp $
+ * @version $Id: ConcreteTextPainter.java,v 1.2 2001/04/29 08:22:26 dino Exp $
*/
-public class ConcreteTextPainter extends BasicTextPainter {
+public abstract class ConcreteTextPainter extends BasicTextPainter {
/**
* Paints the specified attributed character iterator using the
* specified Graphics2D and context and font context.
@@ -33,7 +33,7 @@
* @param g2d the Graphics2D to use
* @param context rendering context.
*/
- public void paint(AttributedCharacterIterator aci, Point2D location, TextNode.Anchor anchor,
+ public void paint(AttributedCharacterIterator aci, Point2D location, TextNode.Anchor anchor,
Graphics2D g2d, GraphicsNodeRenderContext context){
// Compute aci size to be able to draw it
TextLayout layout = new TextLayout(aci, context.getFontRenderContext());
1.3 +519 -35 xml-batik/sources/org/apache/batik/gvt/renderer/StrokingTextPainter.java
Index: StrokingTextPainter.java
===================================================================
RCS file: /home/cvs/xml-batik/sources/org/apache/batik/gvt/renderer/StrokingTextPainter.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- StrokingTextPainter.java 2001/01/30 15:05:27 1.2
+++ StrokingTextPainter.java 2001/04/29 08:22:26 1.3
@@ -18,6 +18,9 @@
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
+import java.awt.Composite;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Rectangle2D;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
@@ -25,12 +28,18 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.Vector;
import org.apache.batik.gvt.GraphicsNodeRenderContext;
import org.apache.batik.gvt.TextNode;
import org.apache.batik.gvt.TextPainter;
import org.apache.batik.gvt.text.AttributedCharacterSpanIterator;
import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
import org.apache.batik.gvt.text.TextSpanLayout;
+import org.apache.batik.gvt.font.GVTFont;
+import org.apache.batik.gvt.font.GVTFontFamily;
+import org.apache.batik.gvt.font.UnresolvedFontFamily;
+import org.apache.batik.gvt.font.FontFamilyResolver;
+import org.apache.batik.gvt.text.TextHit;
/**
* More sophisticated implementation of TextPainter which
@@ -41,7 +50,7 @@
* @see org.apache.batik.gvt.text.GVTAttributedCharacterIterator
*
* @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
- * @version $Id: StrokingTextPainter.java,v 1.2 2001/01/30 15:05:27 billh Exp $
+ * @version $Id: StrokingTextPainter.java,v 1.3 2001/04/29 08:22:26 dino Exp $
*/
public class StrokingTextPainter extends BasicTextPainter {
@@ -49,6 +58,7 @@
static {
extendedAtts.add(GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER);
+ extendedAtts.add(GVTAttributedCharacterIterator.TextAttribute.GVT_FONT);
}
/**
@@ -67,8 +77,30 @@
FontRenderContext frc = context.getFontRenderContext();
AttributedCharacterIterator aci = node.getAttributedCharacterIterator();
+ List textRuns = getTextRuns(node, aci, frc);
+
+ /*
+ * Text Chunks contain one or more TextRuns,
+ * which they create from the ACI.
+ * Each TextRun contains one TextLayout object.
+ * We render each of the TextLayout glyphsets
+ * in turn.
+ */
+ for (int i = 0; i < textRuns.size(); ++i) {
+ paintTextRun((TextRun) textRuns.get(i), g2d, context);
+ }
+
+ // TODO: FONT_VARIANT, SUPERSCRIPT, SUBSCRIPT...
+ }
+
+
+ private List getTextRuns(TextNode node,
+ AttributedCharacterIterator aci,
+ FontRenderContext frc) {
+
List textRuns = new ArrayList();
- aci.first();
+ AttributedCharacterIterator fontaci = createModifiedACIForFontMatching(node, aci);
+ fontaci.first();
/*
* We iterate through the spans over extended attributes,
@@ -78,7 +110,11 @@
TextChunk chunk;
int beginChunk = 0;
do {
- chunk = getTextChunk(node, aci, textRuns, beginChunk, frc);
+ /*
+ * Text Chunks contain one or more TextRuns,
+ * which they create from the ACI.
+ */
+ chunk = getTextChunk(node, fontaci, textRuns, beginChunk, frc);
/* Adjust according to text-anchor property value */
@@ -89,21 +125,11 @@
} while (chunk != null);
- /*
- * Text Chunks contain one or more TextRuns,
- * which they create from the ACI. Each TextRun contains
- * one TextLayout object.
- * We render each of the TextLayout glyphsets
- * in turn.
- */
- for (int i=0; i<textRuns.size(); ++i) {
- paintTextRun((TextRun) textRuns.get(i), g2d);
- }
-
- // TODO: FONT_VARIANT, SUPERSCRIPT, SUBSCRIPT...
+ return textRuns;
}
+
private TextChunk getTextChunk(TextNode node,
AttributedCharacterIterator aci,
List textRuns,
@@ -119,10 +145,13 @@
int chunkStartIndex = aci.getIndex();
boolean isChunkStart = true;
do {
+
int start = aci.getRunStart(extendedAtts);
int end = aci.getRunLimit(extendedAtts);
+
runaci =
new AttributedCharacterSpanIterator(aci, start, end);
+
Float fx = (Float) runaci.getAttribute(
GVTAttributedCharacterIterator.TextAttribute.X);
@@ -168,6 +197,7 @@
}
}
+
private AttributedCharacterIterator createModifiedACIForVerticalLayout(
AttributedCharacterIterator runaci) {
@@ -185,6 +215,146 @@
return as.getIterator();
}
+
+ /**
+ * Returns a new AttributedCharacterIterator that contains resolved GVTFont
+ * attributes. This is then used when creating the text runs so that the
+ * text can be split on changes of font as well as tspans and trefs.
+ *
+ * @param node The text node that the aci belongs to.
+ * @param aci The aci to be modified.
+ *
+ * @return The new modified aci.
+ */
+ private AttributedCharacterIterator createModifiedACIForFontMatching(
+ TextNode node, AttributedCharacterIterator aci) {
+
+ /* System.out.print("selecting font characters in: ");
+ for (char c = aci.first(); c != aci.DONE; c = aci.next()) {
+ System.out.print(c);
+ }
+ System.out.println();
+*/
+
+ aci.first();
+ AttributedCharacterSpanIterator acsi
+ = new AttributedCharacterSpanIterator(aci, aci.getBeginIndex(), aci.getEndIndex());
+ AttributedString as = new AttributedString(acsi);
+ aci.first();
+
+ boolean moreChunks = true;
+ while (moreChunks) {
+ int start = aci.getRunStart(GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER);
+ int end = aci.getRunLimit(GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER);
+
+ AttributedCharacterSpanIterator runaci = new AttributedCharacterSpanIterator(aci, start, end);
+
+ Vector fontFamilies = (Vector)runaci.getAttributes().get(GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
+
+ if (fontFamilies == null) {
+ // no font families set, just return the same aci
+ return aci;
+ }
+
+ // resolve any unresolved font families in the list
+ Vector resolvedFontFamilies = new Vector();
+ for (int i = 0; i < fontFamilies.size(); i++) {
+ GVTFontFamily fontFamily = (GVTFontFamily) fontFamilies.get(i);
+ if (fontFamily instanceof UnresolvedFontFamily) {
+ GVTFontFamily resolvedFontFamily = FontFamilyResolver.resolve((UnresolvedFontFamily)fontFamily);
+ if (resolvedFontFamily != null) {
+ // font family was successfully resolved
+ resolvedFontFamilies.add(resolvedFontFamily);
+ }
+ } else {
+ // already resolved
+ resolvedFontFamilies.add(fontFamily);
+ }
+ }
+ // if could not resolve at least one of the fontFamilies then use
+ // the default faont
+ if (resolvedFontFamilies.size() == 0) {
+ resolvedFontFamilies.add(FontFamilyResolver.defaultFont);
+ }
+
+ // create a list of fonts of the correct size
+ Float fontSizeFloat = (Float)as.getIterator().getAttribute(TextAttribute.SIZE);
+ float fontSize = 12;
+ if (fontSizeFloat != null) {
+ fontSize = fontSizeFloat.floatValue();
+ }
+ Vector gvtFonts = new Vector();
+ for (int i = 0; i < resolvedFontFamilies.size(); i++) {
+ GVTFont font = ((GVTFontFamily)resolvedFontFamilies.get(i)).deriveFont(fontSize, runaci);
+ gvtFonts.add(font);
+ }
+
+ // now for each char or group of chars in the string,
+ // find a font that can display it
+
+ int runaciLength = end-start;
+ boolean[] fontAssigned = new boolean[runaciLength];
+ for (int i = 0; i < runaciLength; i++) {
+ fontAssigned[i] = false;
+ }
+
+ for (int i = 0; i < gvtFonts.size(); i++) {
+ // assign this font to all characters it can display if it has
+ // not already been assigned
+
+ GVTFont font = (GVTFont)gvtFonts.get(i);
+
+ int currentRunIndex = runaci.getBeginIndex();
+ while (currentRunIndex < runaci.getEndIndex()) {
+
+ int displayUpToIndex = font.canDisplayUpTo(runaci, currentRunIndex, end);
+
+ if (displayUpToIndex == -1) {
+ // for each char, if not already assigned a font, assign this font to it
+ for (int j = currentRunIndex; j < end; j++) {
+ if (!fontAssigned[j - start]) {
+ as.addAttribute(GVTAttributedCharacterIterator.TextAttribute.GVT_FONT, font, j, j+1);
+ fontAssigned[j - start] = true;
+ }
+ }
+ currentRunIndex = runaci.getEndIndex();
+
+ } else if (displayUpToIndex > currentRunIndex) {
+ // could display some but not all
+ // for each char it can display, if not already assigned a font, assign this font to it
+
+ for (int j = currentRunIndex; j < displayUpToIndex; j++) {
+ if (!fontAssigned[j - start]) {
+ as.addAttribute(GVTAttributedCharacterIterator.TextAttribute.GVT_FONT, font, j, j+1);
+ fontAssigned[j - start] = true;
+ }
+ }
+ // set currentRunIndex to be one after the char couldn't display
+ currentRunIndex = displayUpToIndex+1;
+ } else {
+ // couldn't display the current char
+ currentRunIndex++;
+ }
+ }
+ }
+ // assign the first font to any chars haven't alreay been assigned
+ for (int i = 0; i < runaciLength; i++) {
+ if (!fontAssigned[i]) {
+ as.addAttribute(GVTAttributedCharacterIterator.TextAttribute.GVT_FONT,
+ gvtFonts.get(0), start+i, start+i+1);
+ }
+ }
+ if (aci.setIndex(end) == aci.DONE) {
+ moreChunks = false;
+ }
+ }
+ return as.getIterator();
+ }
+
+
+
+
+
private void adjustChunkOffsets(List textRuns, Point2D advance,
int beginChunk, int endChunk) {
@@ -226,7 +396,8 @@
}
- private void paintTextRun(TextRun textRun, Graphics2D g2d) {
+ private void paintTextRun(TextRun textRun, Graphics2D g2d,
+ GraphicsNodeRenderContext context) {
AttributedCharacterIterator runaci = textRun.getACI();
TextSpanLayout layout = textRun.getLayout();
@@ -255,27 +426,9 @@
if (overline && !layout.isVertical()) {
paintOverline(textRun, g2d);
}
-
- Shape outline = layout.getOutline();
+ layout.draw(g2d, context);
- // check if we need to fill this glyph
- Paint paint = (Paint) runaci.getAttribute(TextAttribute.FOREGROUND);
- if (paint != null) {
- g2d.setPaint(paint);
- g2d.fill(outline);
- }
-
- // check if we need to draw the outline of this glyph
- Stroke stroke = (Stroke) runaci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.STROKE);
- paint = (Paint) runaci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.STROKE_PAINT);
- if (stroke != null && paint != null) {
- g2d.setStroke(stroke);
- g2d.setPaint(paint);
- g2d.draw(outline);
- }
boolean strikethrough =
(runaci.getAttribute(GVTAttributedCharacterIterator.
TextAttribute.STRIKETHROUGH) ==
@@ -382,6 +535,337 @@
g2d.draw(strikethroughShape);
}
}
+
+
+
+ /*
+ * Get a Rectangle2D in userspace coords which encloses the textnode
+ * glyphs composed from an AttributedCharacterIterator.
+ * @param node the TextNode to measure
+ * @param g2d the Graphics2D to use
+ * @param context rendering context.
+ * @param includeDecoration whether to include text decoration
+ * in bounds computation.
+ * @param includeStrokeWidth whether to include the effect of stroke width
+ * in bounds computation.
+ */
+ protected Rectangle2D getBounds(TextNode node,
+ FontRenderContext context,
+ boolean includeDecoration,
+ boolean includeStrokeWidth) {
+
+ Rectangle2D bounds;
+
+ if (includeStrokeWidth) {
+ Shape s = getStrokeOutline(node, context, includeDecoration);
+ if (s != null) {
+ bounds = s.getBounds2D();
+ } else {
+ bounds = getOutline(node, context, false).getBounds2D();
+ }
+ } else {
+ if (includeDecoration) {
+ bounds = getOutline(node, context, true).getBounds2D();
+ } else {
+ bounds = getOutline(node, context, false).getBounds2D();
+ }
+ }
+ return bounds;
+
+ }
+
+ /**
+ * Get a Shape in userspace coords which defines the textnode glyph outlines.
+ * @param node the TextNode to measure
+ * @param frc the font rendering context.
+ * @param includeDecoration whether to include text decoration
+ * outlines.
+ */
+ protected Shape getOutline(TextNode node, FontRenderContext frc,
+ boolean includeDecoration) {
+
+ Shape outline = null;
+ AttributedCharacterIterator aci = node.getAttributedCharacterIterator();
+
+ // get the list of text runs
+ List textRuns = getTextRuns(node, aci, frc);
+
+ // for each text run, get its outline and append it to the overall outline
+ for (int i = 0; i < textRuns.size(); ++i) {
+ TextRun textRun = (TextRun)textRuns.get(i);
+ Shape textRunOutline = textRun.getLayout().getOutline();;
+
+ if (includeDecoration) {
+ AttributedCharacterIterator textRunACI = textRun.getACI();
+ int decorationTypes = 0;
+ if (textRunACI.getAttribute(GVTAttributedCharacterIterator.
+ TextAttribute.UNDERLINE) != null) {
+ decorationTypes |= TextSpanLayout.DECORATION_UNDERLINE;
+ }
+ if (textRunACI.getAttribute(GVTAttributedCharacterIterator.
+ TextAttribute.OVERLINE) != null) {
+ decorationTypes |= TextSpanLayout.DECORATION_OVERLINE;
+ }
+ if (textRunACI.getAttribute(GVTAttributedCharacterIterator.
+ TextAttribute.STRIKETHROUGH) != null) {
+ decorationTypes |= TextSpanLayout.DECORATION_STRIKETHROUGH;
+ }
+ if (decorationTypes != 0) {
+ if (!(textRunOutline instanceof GeneralPath)) {
+ textRunOutline = new GeneralPath(textRunOutline);
+ }
+ ((GeneralPath) textRunOutline).setWindingRule(
+ GeneralPath.WIND_NON_ZERO);
+ ((GeneralPath) textRunOutline).append(
+ textRun.getLayout().getDecorationOutline(decorationTypes), false);
+ }
+ }
+
+ if (outline == null) {
+ outline = textRunOutline;
+ } else {
+ if (!(outline instanceof GeneralPath)) {
+ outline = new GeneralPath(outline);
+ }
+ ((GeneralPath) outline).setWindingRule(
+ GeneralPath.WIND_NON_ZERO);
+ ((GeneralPath) outline).append(textRunOutline, false);
+ }
+
+ }
+ return outline;
+ }
+
+
+ protected org.apache.batik.gvt.text.Mark hitTest(
+ double x, double y, AttributedCharacterIterator aci,
+ TextNode node,
+ GraphicsNodeRenderContext context) {
+
+ FontRenderContext frc = context.getFontRenderContext();
+
+ // get the list of text runs
+ List textRuns = getTextRuns(node, aci, frc);
+
+ // for each text run, see if its been hit
+ for (int i = 0; i < textRuns.size(); ++i) {
+ TextRun textRun = (TextRun)textRuns.get(i);
+ TextSpanLayout layout = textRun.getLayout();
+ TextHit textHit = layout.hitTestChar((float) x, (float) y);
+ if (textHit != null && layout.getBounds().contains(x,y)) {
+ textHit.setTextNode(node);
+ textHit.setFontRenderContext(frc);
+
+ // Note that a texthit char index of -1 signals that the
+ // hit, though within the text element bounds, did not
+ // coincide with a glyph.
+ if ((aci != cachedACI) ||
+ (textHit == null) ||
+ (cachedHit == null) ||
+ ((textHit.getCharIndex() != -1) &&
+ (textHit.getInsertionIndex() != cachedHit.getInsertionIndex()))) {
+ cachedMark = new BasicTextPainter.Mark(x, y, layout, textHit);
+ cachedACI = textRun.getACI();
+ cachedHit = textHit;
+ return cachedMark;
+ } // else old mark is still valid, return it.
+ }
+ }
+ return cachedMark;
+ }
+
+ /**
+ * Return a Shape, in the coordinate system of the text layout,
+ * which encloses the text selection delineated by two Mark instances.
+ * <em>Note: The Mark instances passed must have been instantiated by
+ * an instance of this enclosing TextPainter implementation.</em>
+ */
+ public Shape getHighlightShape(org.apache.batik.gvt.text.Mark beginMark,
+ org.apache.batik.gvt.text.Mark endMark) {
+
+
+ // TODO: later we can return more complex things like
+ // noncontiguous selections
+
+ BasicTextPainter.Mark begin;
+ BasicTextPainter.Mark end;
+ try {
+ begin = (BasicTextPainter.Mark) beginMark;
+ end = (BasicTextPainter.Mark) endMark;
+ } catch (ClassCastException cce) {
+ throw new
+ Error("This Mark was not instantiated by this TextPainter class!");
+ }
+
+ /* if (begin == null) {
+ System.out.println("begin mark is null");
+ } else {
+ System.out.println("begin mark is at: " + begin.getHit().getCharIndex());
+ }
+ if (end == null) {
+ System.out.println("end mark is null");
+ } else {
+ System.out.println("end mark is at: " + end.getHit().getCharIndex());
+ }
+*/
+ TextSpanLayout beginLayout = null;
+ TextSpanLayout endLayout = null;
+ if (begin != null && end != null) {
+ beginLayout = begin.getLayout();
+ endLayout = end.getLayout();
+ }
+ if (beginLayout == null || endLayout == null) {
+ return null;
+ }
+
+ // System.out.println("beginLayout offset is: " + beginLayout.getOffset());
+ // System.out.println("endLayout offset is: " + endLayout.getOffset());
+
+ if (beginLayout.getOffset().equals(endLayout.getOffset())) {
+ int firsthit = 0;
+ int lasthit = 0;
+ if (begin != end) {
+ firsthit = (begin.getHit().isLeadingEdge()) ?
+ begin.getHit().getCharIndex() :
+ begin.getHit().getCharIndex()+1;
+ lasthit = (end.getHit().isLeadingEdge()) ?
+ end.getHit().getCharIndex() :
+ end.getHit().getCharIndex()+1;
+ if (firsthit > lasthit) {
+ int temp = firsthit;
+ firsthit = lasthit;
+ lasthit = temp;
+ }
+ } else {
+ lasthit = beginLayout.getCharacterCount();
+ }
+ if (firsthit < 0) {
+ firsthit = 0;
+ }
+ return beginLayout.getLogicalHighlightShape(
+ firsthit,
+ lasthit);
+ } else {
+ // selection must span more than one text layout (run)
+
+ // get the list of text runs
+ TextNode textNode = begin.getHit().getTextNode();
+ FontRenderContext frc = begin.getHit().getFontRenderContext();
+ List textRuns = getTextRuns(textNode, textNode.getAttributedCharacterIterator(), frc);
+
+
+
+ // find out whether selection is right to left or not, ie. whether
+ // beginLayout is before endLayout or not
+ boolean leftToRight = true;
+ for (int i = 0; i < textRuns.size(); ++i) {
+ TextRun textRun = (TextRun)textRuns.get(i);
+ TextSpanLayout layout = textRun.getLayout();
+ if (layout.getOffset().equals(beginLayout.getOffset())) {
+ break;
+ }
+ if (layout.getOffset().equals(endLayout.getOffset())) {
+ leftToRight = false;
+ break;
+ }
+ }
+ /* if (leftToRight) {
+ System.out.println("left to right selection");
+ } else {
+ System.out.println("right to left selection");
+ }
+*/
+ GeneralPath highlightedShape = new GeneralPath();
+ boolean startedHighlight = false;
+ boolean finishedHighlight = false;
+
+ // for each text run
+ for (int i = 0; i < textRuns.size(); ++i) {
+ TextRun textRun = (TextRun)textRuns.get(i);
+ TextSpanLayout layout = textRun.getLayout();
+
+ Shape layoutHighlightedShape = null;
+
+ if (leftToRight) {
+
+ if (layout.getOffset().equals(beginLayout.getOffset())) { // found the first layout
+
+ startedHighlight = true;
+ int firsthit = (begin.getHit().isLeadingEdge()) ?
+ begin.getHit().getCharIndex() :
+ begin.getHit().getCharIndex()+1;
+ if (firsthit < 0) {
+ firsthit = 0;
+ }
+ layoutHighlightedShape = layout.getLogicalHighlightShape(
+ firsthit, layout.getCharacterCount());
+
+ } else if (layout.getOffset().equals(endLayout.getOffset())) {
+
+ finishedHighlight = true;
+ int lasthit = (end.getHit().isLeadingEdge()) ?
+ end.getHit().getCharIndex() :
+ end.getHit().getCharIndex()+1;
+
+ if (lasthit < 0) {
+ lasthit = layout.getCharacterCount();
+ }
+ layoutHighlightedShape = layout.getLogicalHighlightShape(
+ 0, lasthit);
+
+ } else if (startedHighlight) {
+ layoutHighlightedShape = layout.getLogicalHighlightShape(
+ 0, layout.getCharacterCount());
+ }
+
+ } else { // right to left
+
+ if (layout.getOffset().equals(beginLayout.getOffset())) { // found the first layout
+ finishedHighlight = true;
+ int lasthit = (begin.getHit().isLeadingEdge()) ?
+ begin.getHit().getCharIndex() :
+ begin.getHit().getCharIndex()+1;
+
+ if (lasthit < 0) {
+ lasthit = layout.getCharacterCount();
+ }
+ layoutHighlightedShape = layout.getLogicalHighlightShape(
+ 0, lasthit);
+
+ } else if (layout.getOffset().equals(endLayout.getOffset())) {
+ startedHighlight = true;
+ int firsthit = (end.getHit().isLeadingEdge()) ?
+ end.getHit().getCharIndex() :
+ end.getHit().getCharIndex()+1;
+ if (firsthit < 0) {
+ firsthit = 0;
+ }
+
+ layoutHighlightedShape = layout.getLogicalHighlightShape(
+ firsthit, layout.getCharacterCount());
+
+ } else if (startedHighlight) {
+ layoutHighlightedShape = layout.getLogicalHighlightShape(
+ 0, layout.getCharacterCount());
+ }
+
+ }
+
+ // append the highlighted shape of this layout to the
+ // overall hightlighted shape
+ if (layoutHighlightedShape != null && !layoutHighlightedShape.getBounds().isEmpty()) {
+ highlightedShape.append(layoutHighlightedShape, false);
+ }
+ // if has appended the last highlight, then don't process any more
+ if (finishedHighlight) {
+ break;
+ }
+ }
+ return highlightedShape;
+ }
+ }
+
+
/**
* Inner convenience class for associating a TextLayout for
---------------------------------------------------------------------
To unsubscribe, e-mail: batik-dev-unsubscribe@xml.apache.org
For additional commands, e-mail: batik-dev-help@xml.apache.org