In order to precisely position text characters in SVG, it's often necessary to set kerning='0' on text elements to ensure that unwanted tweaks aren't made to character positions during display or printing. However, Batik handles the 'kerning' attribute incorrectly. It treats a kerning value as an absolute glyph-spacing value rather than as an *adjustment* to glyph spacing.
For example, Batik would render this text element: <text kerning='0'>hello</text>, with zero spacing between each glyph. Each glyph would be touching. That would be an incorrect result. What should happen instead is that each glyph should be layed-out with some whitespace (namely the glyph's left and right-side bearing) surrounding it. The value 0 here simply means that there should be zero kerning adjustment, not zero spacing between characters. (For reference, here are some nice diagrams showing font terminology[1] and kerning[2].) This example[3] shows the problem in Batik. The example is from the SVG spec but with a kerning value of 0 on the text element for illustration. So, how do we get the correct result? The SVG1.1 spec says this: "If a <length> is provided, then auto-kerning is disabled. Instead, inter-character spacing is set to the given <length>."[4] The term "inter-character spacing" here is vague -- what exactly do they mean by a character? How wide is a character? Batik currently interprets a character width as the bounding-box width of the glyph. Given this interpretation, Batik is operating correctly. But a better interpretation for width, I think, is the bounding-box width plus the left and right-side bearing of the glyph -- i.e the full advance width of the glyph. The spec supports this interpretation: It goes on to say, "The most common scenario, other than auto, is to set 'kerning' to a value of 0 so that auto-kerning is disabled." The intent here is that you set 'kerning' to 0 to disable kerning. That implies that the kerning value is an adjustment rather than an absolute value. Remember that kerning is a subtle adjustment to glyph spacing to make text look nicer. Kerned and un-kerned text should look much the same in gross terms. Un-kerned text certainly shouldn't look like it's had all of its spacing removed. (Adobe's viewer implementation supports these assertions). The method responsible for applying the 'kerning' value is org.apache.batik.gvt.text.GlyphLayout.doSpacing. I've reworked it and provided a patch[5]. An additional problem with the original code is that it attempts to remove any auto-kerning values from the GlyphVector before applying the explicit 'kerning' value. In doing so, it also loses explicit x-coords (e.g. in <text x="10,20,30">). However, since neither Java nor Batik currently actually do any kerning, it made sense to remove this code. Dan Phillips Software Mechanics [1] http://developer.apple.com/documentation/Carbon/Conceptual/ATSUI_Concepts/atsui_chap2/chapter_2_section_6.html [2] http://developer.apple.com/documentation/Carbon/Conceptual/ATSUI_Concepts/atsui_chap2/chapter_2_section_8.html#//apple_ref/doc/uid/TP30000028/TPXREF149 [3] <?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg width="10cm" height="3cm" viewBox="0 0 1000 300" xmlns="http://www.w3.org/2000/svg" version="1.1"> <desc>Example text01 - 'Hello, out there' in blue</desc> <text x="250" y="150" kerning="0" font-family="Verdana" font-size="55" fill="blue" > Hello, out there </text> <!-- Show outline of canvas using 'rect' element --> <rect x="1" y="1" width="998" height="298" fill="none" stroke="blue" stroke-width="2" /> </svg> [4] http://www.w3.org/TR/SVG11/text.html#KerningProperty [5] Index: GlyphLayout.java =================================================================== RCS file: /home/cvspublic/xml-batik/sources/org/apache/batik/gvt/text/GlyphLayout.java,v retrieving revision 1.52 diff -u -r1.52 GlyphLayout.java --- GlyphLayout.java 8 May 2003 11:39:13 -0000 1.52 +++ GlyphLayout.java 1 Jul 2003 18:02:13 -0000 @@ -1391,7 +1391,7 @@ /** * Performs any spacing adjustments required and returns the new advance - * value. + * value. Assumes that the text is un-kerned. * * @param kern The kerning adjustment to apply to the space * between each char. @@ -1400,21 +1400,18 @@ 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())) { @@ -1437,28 +1434,19 @@ advance.getY() - (gv.getGlyphPosition(numGlyphs-1).getY() - y)); try { - // do letter spacing first - if ((numGlyphs > 1) && (doLetterSpacing || !autoKern)) { + if (numGlyphs > 1) { for (int i=1; i<=numGlyphs; ++i) { Point2D gpos = gv.getGlyphPosition(i); dx = (float)gpos.getX()-(float)prevPos.getX(); dy = (float)gpos.getY()-(float)prevPos.getY(); - if (autoKern) { - if (vertical) dy += letterSpacingVal; - else dx += letterSpacingVal; + + // apply explicit kerning and letter-spacing adjustments + if (vertical) { + dy += kernVal + letterSpacingVal; } else { - // apply explicit kerning adjustments, - // discarding any auto-kern dx values - if (vertical) { - dy = (float) - gv.getGlyphMetrics(i-1).getBounds2D().getHeight()+ - kernVal + letterSpacingVal; - } else { - dx = (float) - gv.getGlyphMetrics(i-1).getBounds2D().getWidth()+ - kernVal + letterSpacingVal; - } + dx += kernVal + letterSpacingVal; } + x += dx; y += dy; newPositions[i] = new Point2D.Float(x, y); @@ -1472,25 +1460,13 @@ } } - // adjust the advance of the last character - if (autoKern) { - if (vertical) { - lastCharAdvance.setLocation(lastCharAdvance.getX(), - lastCharAdvance.getY() + letterSpacingVal); - } else { - lastCharAdvance.setLocation(lastCharAdvance.getX() - + letterSpacingVal, lastCharAdvance.getY()); - } + // adjust the advance of the last character + if (vertical) { + lastCharAdvance.setLocation(lastCharAdvance.getX(), + lastCharAdvance.getY() + kernVal + letterSpacingVal); } else { - if (vertical) { - lastCharAdvance.setLocation(lastCharAdvance.getX(), - gv.getGlyphMetrics(numGlyphs-2).getBounds2D().getHeight()+ - kernVal + letterSpacingVal); - } else { - lastCharAdvance.setLocation( - gv.getGlyphMetrics(numGlyphs-2).getBounds2D().getWidth()+ - kernVal + letterSpacingVal, lastCharAdvance.getY()); - } + lastCharAdvance.setLocation(lastCharAdvance.getX() + kernVal + letterSpacingVal, + lastCharAdvance.getY()); } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]