Articles Projects Tips Downloads Contacts About

Using custom GlyphPainter implementation to achieve effects like kerning, scaled text, and opposite italic in the JEditorPane/JTextPane.
By Stanislav Lapitsky

Some special effects like kerning, opposite italic, text scaling and some more text transformations can't be achieved by modifications of views. To support them we have to modify classes one level down. The classes directly render texts. To show the things I wrote custom GlyphPainter where I can transform text on lower level to achieve desired layout and view. The GLyphPainter can be basement for some more effects like gradient painting of text, texts with shadow or outlined letters but this article shows the 3 declared effects only.

The GlyphPainter uses GlyphVector standard java class to layout, measure and render text. The GlyphVector is created when painter is created and rebuild if source text is changed see the code below:

    public GlyphVectorPainter(String text, GlyphView v) {
        if (v!=null) {
            this.text=text;
            this.font=v.getFont();
            init(v);
        }
    }
    
    void sync(GlyphView v) {
        try {
            String localText=v.getDocument().getText(v.getStartOffset(), v.getEndOffset()-v.getStartOffset());
            if (!localText.equals(text)) {
                this.text=localText;
                init(v);
            }
        } catch (BadLocationException e) {
            e.printStackTrace();
        }
    }

    private void init(GlyphView v) {
        AffineTransform transform = GraphicsEnvironment.getLocalGraphicsEnvironment()
            .getDefaultScreenDevice().getDefaultConfiguration().getDefaultTransform();
        FontRenderContext frc = new FontRenderContext(transform, true, true);
        lm = font.getLineMetrics(text.toCharArray(), 0, text.toCharArray().length, frc);

        glyphVector=font.layoutGlyphVector(frc,text.toCharArray(), 0, text.length(), 0);
        checkKerning(v);
        checkOppositeItalic(v);
        checkScale(v);
        spaceVector=font.layoutGlyphVector(frc," ".toCharArray(), 0, 1, 0);
    }

After creation effects settings are checked and proper flags are set.

For kerning feature GlyphVector is modified. Every Glyph position is shifted according to the kerning shift value passed.

A special interface was declared to support other transformations. It obtains original transformation and parent view and returns result transformation which should be applied to modify textís shape.

import javax.swing.text.*;
import java.awt.geom.AffineTransform;

public interface TextShapeTransform {
    public AffineTransform getTransform(View gv, AffineTransform original);
}

Two implementations of the interface show how support scaled text and opposite italic.

import javax.swing.text.*;
import java.awt.geom.AffineTransform;

public class OppositeItalicTransform implements TextShapeTransform {
    private float y=0;
    public AffineTransform getTransform(View gv, AffineTransform original) {
        AffineTransform newTr=new AffineTransform(original);
        newTr.translate(0, getY());
        newTr.shear(0.3,0);
        newTr.translate(2,-getY());

        return newTr;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }
}

and

import javax.swing.text.View;
import java.awt.geom.AffineTransform;

public class ScaleTransform implements TextShapeTransform {
    private float yScale=1;
    private float y=0;
    private float ascent =0;

    public ScaleTransform(float yScale) {
        this.setYScale(yScale);
    }
    
    public AffineTransform getTransform(View gv, AffineTransform original) {
        AffineTransform newTr=new AffineTransform();
        newTr.translate(0, -(getY()- ascent+3)*getYScale());
        newTr.scale(1, getYScale());
        newTr.translate(0, (getY()-ascent+3)/getYScale());

        return newTr;
    }
    
    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    public float getYScale() {
        return yScale;
    }

    public void setYScale(float yScale) {
        this.yScale = yScale;
    }

    public float getAscent() {
        return ascent;
    }

    public void setAscent(float ascent) {
        this.ascent = ascent;
    }
}

The following screenshot shows the results of the effects using.

I have to add a few words about other possible text effects. Shadow can be achieved by painting the same vector twice original and e.g. opposite italic. Gradient paint can be achieved by calling setPaint() in the paint() method of the GlyphPainter. GlyphVector has getOutline() method which retunrs the vector outline. NOTE: the outline can be used with relatively big fonts in opposite case there will be no visible effect except aliased letters.

Back to Table of Content