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 bi...@apache.org on 2001/02/01 20:22:58 UTC
cvs commit: xml-batik/sources/org/apache/batik/bridge SVGTextElementBridge.java
billh 01/02/01 11:22:57
Modified: sources/org/apache/batik/gvt/text GlyphLayout.java
sources/org/apache/batik/bridge SVGTextElementBridge.java
Log:
Fixed text selection bug when using textLength.
Added support for "kerning", "letter-spacing", "word-spacing".
Revision Changes Path
1.5 +216 -62 xml-batik/sources/org/apache/batik/gvt/text/GlyphLayout.java
Index: GlyphLayout.java
===================================================================
RCS file: /home/cvs/xml-batik/sources/org/apache/batik/gvt/text/GlyphLayout.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- GlyphLayout.java 2001/01/31 17:54:25 1.4
+++ GlyphLayout.java 2001/02/01 19:22:49 1.5
@@ -35,7 +35,7 @@
* @see org.apache.batik.gvt.TextSpanLayout.
*
* @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
- * @version $Id: GlyphLayout.java,v 1.4 2001/01/31 17:54:25 billh Exp $
+ * @version $Id: GlyphLayout.java,v 1.5 2001/02/01 19:22:49 billh Exp $
*/
public class GlyphLayout implements TextSpanLayout {
@@ -50,6 +50,7 @@
private Point2D offset;
private Point2D prevCharPosition;
protected Shape[] glyphLogicalBounds;
+ protected Point2D[] glyphPositions;
/**
* Creates the specified text layout using the
@@ -69,10 +70,11 @@
ci = new ReorderedCharacterIterator(this.aci);
this.gv = font.createGlyphVector(frc, ci);
this.gv.performDefaultLayout();
+ this.glyphPositions = new Point2D.Float[gv.getNumGlyphs()];
this.offset = offset;
this.transform = null;
doExplicitGlyphLayout(false);
- adjustTextLength();
+ adjustTextSpacing();
computeGlyphLogicalBounds();
}
@@ -128,7 +130,7 @@
this.offset = offset;
this.gv.performDefaultLayout();
doExplicitGlyphLayout(true);
- adjustTextLength();
+ adjustTextSpacing();
computeGlyphLogicalBounds();
}
@@ -224,9 +226,9 @@
}
}
- }
- if (transform != null) {
- shape = transform.createTransformedShape(shape);
+ if (transform != null) {
+ shape = transform.createTransformedShape(shape);
+ }
}
return shape;
}
@@ -262,16 +264,11 @@
maxX = (float) (gbounds2d.getX()+gbounds2d.getWidth());
}
- //System.out.println("x,y: "+x+" "+y);
- //System.out.println("glyph "+i+" - bounds "+gbounds);
-
if (gbounds.contains(x, y)) {
- gm = gv.getGlyphMetrics(i);
boolean isRightHalf =
(x > (gbounds2d.getX()+(gbounds2d.getWidth()/2d)));
boolean isLeadingEdge = !isRightHalf;
textHit = new TextHit(i, isLeadingEdge);
- //System.out.println("Hit at "+i+", leadingEdge "+isLeadingEdge);
return textHit;
}
}
@@ -322,14 +319,13 @@
GlyphMetrics gm = gv.getGlyphMetrics(i);
Rectangle2D gbounds2d = gm.getBounds2D();
- Point2D gpos = gv.getGlyphPosition(i);
+ Point2D gpos = glyphPositions[i];
lbox = new Rectangle2D.Double(
gpos.getX()+gbounds2d.getX(),
gpos.getY()+gbounds2d.getY(),
gbounds2d.getWidth(),
gbounds2d.getHeight());
- //System.out.println("GLB["+i+"]="+lbox);
glyphLogicalBounds[i] = lbox;
}
@@ -337,7 +333,7 @@
int begin = i;
int end = begin;
- Point2D gpos = gv.getGlyphPosition(begin);
+ Point2D gpos = glyphPositions[begin];
// calculate a "run" over the same y nominal position,
// over which the glyphs have positive 'x' advances.
@@ -353,8 +349,8 @@
float currX = x;
while (end<c) {
lbox = (Rectangle2D.Double) glyphLogicalBounds[end];
- currY = (float) gv.getGlyphPosition(end).getY();
- currX = (float) gv.getGlyphPosition(end).getX();
+ currY = (float) glyphPositions[end].getY();
+ currX = (float) glyphPositions[end].getX();
if ((currX < x) || (Math.abs(currY - y) > epsilon)) {
break;
}
@@ -387,6 +383,7 @@
lboxPrev.getY(),
(double) (x - prevx),
lboxPrev.getHeight());
+
//System.out.println("Setting glb["+(n-1)+"]="+
// glyphLogicalBounds[n-1]);
}
@@ -426,7 +423,7 @@
// run direction of this aci
int inc = (aci.getAttribute(TextAttribute.RUN_DIRECTION) ==
- TextAttribute.RUN_DIRECTION_LTR) ? 1 : -1;
+ TextAttribute.RUN_DIRECTION_RTL) ? -1 : 1;
ndx = (inc > 0) ? begin : end-1;
@@ -435,7 +432,8 @@
while (ch != CharacterIterator.DONE) {
// get BiDi embedding
- Integer embed = (Integer) aci.getAttribute(TextAttribute.BIDI_EMBEDDING);
+ Integer embed = (Integer) aci.getAttribute(
+ TextAttribute.BIDI_EMBEDDING);
//System.out.println("BiDi embedding level : "+embed);
// get BiDi span
int runLimit = aci.getRunLimit(TextAttribute.BIDI_EMBEDDING);
@@ -444,7 +442,8 @@
int runEndNdx = ndx;
if (embed != null) {
- isReversed = (Math.abs(Math.IEEEremainder((double)embed.intValue(), 2d)) < 0.1) ? false : true;
+ isReversed = (Math.abs(Math.IEEEremainder(
+ (double)embed.intValue(), 2d)) < 0.1) ? false : true;
if (isReversed) {
runEndNdx = ndx + inc*(runLimit-aciIndex);
inc = -inc;
@@ -574,23 +573,29 @@
return new Font(aci.getAttributes());
}
- protected void adjustTextLength() {
+ protected void adjustTextSpacing() {
aci.first();
Boolean customSpacing = (Boolean) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING);
+ GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING);
Float length = (Float) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.BBOX_WIDTH);
+ GVTAttributedCharacterIterator.TextAttribute.BBOX_WIDTH);
+ Integer lengthAdjust = (Integer) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST);
if ((customSpacing != null) && customSpacing.booleanValue()) {
- adjustSpacing(length);
+ applySpacingParams(length, lengthAdjust,
+ (Float) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.KERNING),
+ (Float) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING),
+ (Float) aci.getAttribute(
+ GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING));
}
- Integer lengthAdjust = (Integer) aci.getAttribute(
- GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST);
- if (lengthAdjust==GVTAttributedCharacterIterator.TextAttribute.ADJUST_ALL) {
+ if (lengthAdjust ==
+ GVTAttributedCharacterIterator.TextAttribute.ADJUST_ALL) {
transform = computeStretchTransform(length);
}
- aci.setIndex(aci.getEndIndex());
}
protected AffineTransform computeStretchTransform(Float length) {
@@ -599,62 +604,210 @@
double xscale = 1d;
double yscale = 1d;
if (isVertical()) {
- yscale = length.floatValue()/advance.getY();
+ yscale = length.floatValue()/gv.getVisualBounds().getHeight();
} else {
- xscale = length.floatValue()/advance.getX();
+ xscale = length.floatValue()/gv.getVisualBounds().getWidth();
}
try {
Point2D startPos = gv.getGlyphPosition(0);
- AffineTransform translation = AffineTransform.getTranslateInstance(
+ AffineTransform translation =
+ AffineTransform.getTranslateInstance(
startPos.getX(),
startPos.getY());
AffineTransform inverse = translation.createInverse();
t = translation;
- t.concatenate(AffineTransform.getScaleInstance(xscale, yscale));
+ t.concatenate(
+ AffineTransform.getScaleInstance(xscale, yscale));
t.concatenate(inverse);
} catch (java.awt.geom.NoninvertibleTransformException e) {;}
}
return t;
}
-
- protected void adjustSpacing(Float length) {
- float deltax = 0f;
- float deltay = 0f;
- if (length!= null && !length.isNaN()) {
- //System.out.println("Adjusting length to "+length);
- int numGlyphs = gv.getNumGlyphs();
- if (numGlyphs > 1) {
- if (!isVertical()) {
- deltax =
- (length.floatValue() - (float) advance.getX())/(numGlyphs-1);
- } else {
- deltay =
- (length.floatValue() - (float) advance.getY())/(numGlyphs-1);
- }
+ protected void applySpacingParams(Float length,
+ Integer lengthAdjust,
+ Float kern,
+ Float letterSpacing,
+ Float wordSpacing) {
+
+ /**
+ * Two passes required when textLength is specified:
+ * First, apply spacing properties,
+ * then adjust spacing with new advances based on ratio
+ * of expected length to actual advance.
+ */
+
+ advance = doSpacing(kern, letterSpacing, wordSpacing);
+ if ((lengthAdjust ==
+ GVTAttributedCharacterIterator.TextAttribute.ADJUST_SPACING) &&
+ length!= null && !length.isNaN()) { // adjust if necessary
+ float xscale = 1f;
+ float yscale = 1f;
+ if (!isVertical()) {
+ xscale = length.floatValue()/(float) gv.getVisualBounds().getWidth();
+ } else {
+ yscale = length.floatValue()/(float) gv.getVisualBounds().getHeight();
+ }
+ rescaleSpacing(xscale, yscale);
+ }
+ }
- float[] gp = new float[(numGlyphs+1)*2];
- gp = (float[])
- gv.getGlyphPositions(0, gv.getNumGlyphs(), gp).clone();
- float x = 0f;
- float y = 0f;
- for (int i=0; i<numGlyphs; ++i) {
- x = gp[i*2] + deltax*i;
- y = gp[1*2+1] + deltay*i;
- gv.setGlyphPosition(i, new Point2D.Float(x, y));
+ protected Point2D doSpacing(Float kern,
+ Float letterSpacing,
+ Float wordSpacing) {
+
+ boolean autoKern = true;
+ boolean doWordSpacing = false;
+ boolean doLetterSpacing = false;
+ float kernVal = 0f;
+ float letterSpacingVal = 0f;
+ float wordSpacingVal = 0f;
+
+ if ((kern instanceof Float) && (!kern.isNaN())) {
+ kernVal = kern.floatValue();
+ autoKern = false;
+ //System.out.println("KERNING: "+kernVal);
+ }
+ if ((letterSpacing instanceof Float) && (!letterSpacing.isNaN())) {
+ letterSpacingVal = letterSpacing.floatValue();
+ doLetterSpacing = true;
+ //System.out.println("LETTER-SPACING: "+letterSpacingVal);
+ }
+ if ((wordSpacing instanceof Float) && (!wordSpacing.isNaN())) {
+ wordSpacingVal = wordSpacing.floatValue();
+ doWordSpacing = true;
+ //System.out.println("WORD_SPACING: "+wordSpacingVal);
+ }
+
+ int numGlyphs = gv.getNumGlyphs();
+
+ float dx = 0f;
+ float dy = 0f;
+ Point2D newPositions[] = new Point2D[numGlyphs];
+ Point2D prevPos = glyphPositions[0];
+ float x = (float) prevPos.getX();
+ float y = (float) prevPos.getY();
+ try {
+ if ((numGlyphs > 1) &&
+ (doWordSpacing || doLetterSpacing || !autoKern)) {
+ for (int i=1; i<numGlyphs; ++i) {
+ Point2D gpos = glyphPositions[i];
+ dx = (float)gpos.getX()-(float)prevPos.getX();
+ dy = (float)gpos.getY()-(float)prevPos.getY();
+ boolean inWS = false;
+ // while this is whitespace, increment
+ int beginWS = i;
+ int endWS = i;
+ GlyphMetrics gm = gv.getGlyphMetrics(i);
+ // BUG: gm.isWhitespace() fails for latin SPACE glyph!
+ while ((gm.getBounds2D().getWidth()<0.01d) ||
+ gm.isWhitespace()) {
+ ++i;
+ ++endWS;
+ if (!inWS) inWS = true;
+ if (i>=numGlyphs) {
+ inWS = false;
+ break;
+ }
+ gpos = glyphPositions[i];
+ gm = gv.getGlyphMetrics(i);
}
- if (!isVertical()) {
- x += (float) gv.getGlyphMetrics(numGlyphs-1).getAdvance();
+ // then apply wordSpacing
+ if ( inWS ) {
+ if (doWordSpacing) {
+ int nWS = endWS-beginWS;
+ float px = (float) prevPos.getX();
+ float py = (float) prevPos.getY();
+ dx = (float) (gpos.getX() - px)/nWS;
+ dy = (float) (gpos.getY() - py)/nWS;
+ if (isVertical()) {
+ dy = (float) (wordSpacing.floatValue()+
+ gv.getGlyphMetrics(beginWS-1).
+ getBounds2D().getHeight())/nWS;
+ } else {
+ dx = (float) (wordSpacing.floatValue()+
+ gv.getGlyphMetrics(beginWS-1).
+ getBounds2D().getWidth())/nWS;
+ }
+ for (int j=beginWS; j<=endWS; ++j) {
+ x += dx;
+ y += dy;
+ newPositions[j] = new Point2D.Float(x, y);
+ }
+ }
} else {
- y += (float) gv.getGlyphMetrics(numGlyphs-1).getAdvance();
+ dx = (float) (gpos.getX()-prevPos.getX());
+ dy = (float) (gpos.getY()-prevPos.getY());
+ if (autoKern) {
+ if (isVertical()) dy += letterSpacingVal;
+ else dx += letterSpacingVal;
+ } else {
+ // apply explicit kerning adjustments,
+ // discarding any auto-kern dx values
+ if (isVertical()) {
+ dy = (float)
+ gv.getGlyphMetrics(i-1).getBounds2D().getWidth()+
+ kernVal + letterSpacingVal;
+ } else {
+ dx = (float)
+ gv.getGlyphMetrics(i-1).getBounds2D().getWidth()+
+ kernVal + letterSpacingVal;
+ }
+ }
+ x += dx;
+ y += dy;
+ newPositions[i] = new Point2D.Float(x, y);
}
- gv.setGlyphPosition(numGlyphs, new Point2D.Float(x, y));
- advance = new Point2D.Float((float) (x-offset.getX()),
- (float) (y-offset.getY()));
+ prevPos = gpos;
}
+ for (int i=1; i<numGlyphs; ++i) { // assign the new positions
+ glyphPositions[i] = newPositions[i];
+ gv.setGlyphPosition(i, glyphPositions[i]);
+ }
}
+ if (isVertical()) {
+ dx = 0f;
+ dy = (float)
+ gv.getGlyphMetrics(numGlyphs-1).getBounds2D().getHeight()+
+ kernVal+letterSpacingVal;
+ } else {
+ dx = (float)
+ gv.getGlyphMetrics(numGlyphs-1).getBounds2D().getWidth()+
+ kernVal+letterSpacingVal;
+ dy = 0f;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ //Point2D newAdvance = new Point2D.Float((float) prevPos.getX()+dx,
+ // (float) prevPos.getY()+dy);
+ Point2D newAdvance = advance;
+ gv.setGlyphPosition(numGlyphs, newAdvance);
+ return newAdvance;
+ }
+
+ protected void rescaleSpacing(float xscale, float yscale) {
+ Rectangle2D bounds = gv.getVisualBounds();
+ float initX = (float) bounds.getX();
+ float initY = (float) bounds.getY();
+ int numGlyphs = gv.getNumGlyphs();
+ float dx = 0f;
+ float dy = 0f;
+ for (int i=0; i<numGlyphs; ++i) {
+ Point2D gpos = glyphPositions[i];
+ dx = (float)gpos.getX()-initX;
+ dy = (float)gpos.getY()-initY;
+ glyphPositions[i] = new Point2D.Float(initX+dx*xscale,
+ initY+dy*yscale);
+ gv.setGlyphPosition(i, glyphPositions[i]);
+ }
+ gv.setGlyphPosition(numGlyphs, new Point2D.Float(initX+dx*xscale,
+ initY+dy*yscale));
+ advance = new Point2D.Float((float)(initX+dx*xscale-offset.getX()),
+ (float)(initY+dy*yscale-offset.getY()));
}
+
protected void doExplicitGlyphLayout(boolean applyOffset) {
char ch = aci.first();
int i=0;
@@ -706,7 +859,8 @@
curr_y_pos += gp[i*2 + 1]-gp[i*2 - 1];
}
- gv.setGlyphPosition(i, new Point2D.Float(curr_x_pos, curr_y_pos));
+ glyphPositions[i] = new Point2D.Float(curr_x_pos, curr_y_pos);
+ gv.setGlyphPosition(i, glyphPositions[i]);
//System.out.print(ch);
//System.out.print("["+curr_x_pos+","+curr_y_pos+"]");
curr_x_pos += (float) gv.getGlyphMetrics(i).getAdvance();
1.7 +31 -22 xml-batik/sources/org/apache/batik/bridge/SVGTextElementBridge.java
Index: SVGTextElementBridge.java
===================================================================
RCS file: /home/cvs/xml-batik/sources/org/apache/batik/bridge/SVGTextElementBridge.java,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -r1.6 -r1.7
--- SVGTextElementBridge.java 2001/02/01 13:45:46 1.6
+++ SVGTextElementBridge.java 2001/02/01 19:22:55 1.7
@@ -66,7 +66,7 @@
* A factory for the <text> SVG element.
*
* @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
- * @version $Id: SVGTextElementBridge.java,v 1.6 2001/02/01 13:45:46 tkormann Exp $
+ * @version $Id: SVGTextElementBridge.java,v 1.7 2001/02/01 19:22:55 billh Exp $
*/
public class SVGTextElementBridge implements GraphicsNodeBridge, SVGConstants {
protected final static Map fonts = new HashMap(11);
@@ -335,17 +335,20 @@
if (as != null) {
addGlyphPositionAttributes(
as, true, indexMap, ctx, nodeElement);
- stripLast = !preserve && (as.getIterator().first() == ' ');
+ stripLast = !preserve &&
+ (as.getIterator().first() == ' ');
if (stripLast) {
AttributedString las =
(AttributedString) result.removeLast();
if (las != null) {
- AttributedCharacterIterator iter = las.getIterator();
+ AttributedCharacterIterator iter =
+ las.getIterator();
int endIndex = iter.getEndIndex()-1;
if (iter.setIndex(endIndex) == ' ') {
las = new AttributedString(
las.getIterator(null,
- iter.getBeginIndex(), endIndex));
+ iter.getBeginIndex(),
+ endIndex));
}
result.add(las);
}
@@ -354,7 +357,7 @@
}
} catch(MalformedURLException ex) {
throw new IllegalAttributeValueException(
- Messages.formatMessage("tref.xlinkHref.badURL", null));
+ Messages.formatMessage("tref.xlinkHref.badURL", null));
} catch (Exception ex) { /* Nothing to do */ }
}
break;
@@ -362,17 +365,20 @@
s = n.getNodeValue();
int[] indexMap = new int[s.length()];
as = createAttributedString(
- s, m, indexMap, preserve, stripFirst, last && top);
+ s, m, indexMap, preserve, stripFirst, last && top);
if (as != null) {
if (first) {
- addGlyphPositionAttributes(as, !top, indexMap, ctx, element);
+ addGlyphPositionAttributes(
+ as, !top, indexMap, ctx, element);
}
- stripLast = !preserve && (as.getIterator().first() == ' ');
+ stripLast =
+ !preserve && (as.getIterator().first() == ' ');
if (stripLast && !result.isEmpty()) {
AttributedString las =
(AttributedString) result.removeLast();
if (las != null) {
- AttributedCharacterIterator iter = las.getIterator();
+ AttributedCharacterIterator iter =
+ las.getIterator();
int endIndex = iter.getEndIndex()-1;
if (iter.setIndex(endIndex) == ' ') {
las = new AttributedString(
@@ -616,6 +622,7 @@
CSSPrimitiveValue v;
String s;
float f;
+ short t;
result.put(GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER, element);
@@ -867,12 +874,13 @@
// Letter Spacing
v = (CSSPrimitiveValue)cssDecl.getPropertyCSSValue
(CSS_LETTER_SPACING_PROPERTY);
- if (v.getPrimitiveType() != CSSPrimitiveValue.CSS_IDENT) {
- s = v.getStringValue();
- f = SVGUtilities.svgToUserSpace(element,
- CSS_LETTER_SPACING_PROPERTY, s,
- uctx,
- UnitProcessor.HORIZONTAL_LENGTH);
+ t = v.getPrimitiveType();
+ if (t != CSSPrimitiveValue.CSS_IDENT) {
+ f = UnitProcessor.cssToUserSpace(t,
+ v.getFloatValue(t),
+ (SVGElement) element,
+ UnitProcessor.HORIZONTAL_LENGTH,
+ uctx);
// XXX: HACK: Assuming horizontal length units is wrong,
// layout might be vertical!
@@ -888,12 +896,13 @@
// Word spacing
v = (CSSPrimitiveValue)cssDecl.getPropertyCSSValue
(CSS_WORD_SPACING_PROPERTY);
- if (v.getPrimitiveType() != CSSPrimitiveValue.CSS_IDENT) {
- s = v.getStringValue();
- f = SVGUtilities.svgToUserSpace(element,
- CSS_WORD_SPACING_PROPERTY, s,
- uctx,
- UnitProcessor.HORIZONTAL_LENGTH);
+ t = v.getPrimitiveType();
+ if (t != CSSPrimitiveValue.CSS_IDENT) {
+ f = UnitProcessor.cssToUserSpace(t,
+ v.getFloatValue(t),
+ (SVGElement) element,
+ UnitProcessor.HORIZONTAL_LENGTH,
+ uctx);
// XXX: HACK: Assuming horizontal length units is wrong,
// layout might be vertical!
@@ -1002,7 +1011,7 @@
// Text decoration
CSSValue cssVal = cssDecl.getPropertyCSSValue
(CSS_TEXT_DECORATION_PROPERTY);
- short t = cssVal.getCssValueType();
+ t = cssVal.getCssValueType();
if (t == CSSValue.CSS_VALUE_LIST) {
CSSValueList lst = (CSSValueList)cssVal;
for (int i = 0; i < lst.getLength(); i++) {