Articles Projects Tips Downloads Contacts About

Vertical text feature in the JEditorPane/JTextPane Component
By Stanislav Lapitsky

The previous article (See: Scaling in the JEditorPane) about scaling support explains how to transform root view to provide the feature. The same approach can be used for Vertical text representation and editing in the JEditorPane. We will just use another kind of transform. The following images show how the features look like. Two possible orientation of vertical text are top-to-bottom and bottom-to-top. The images show and the article will explain both approaches.

Bottom-to-top orientation.

Top-to-bottom orientation.

JEditorPane creates edits and represents all content via EditorKit so for our example we’ll extend the simplest StyledEditorKit. EditorKits represent data using views which are generated by ViewFactory. We should replace the default ViewFactory so the only method we override in our EditorKit is getViewFactory().

class VerticalEditorKit extends StyledEditorKit {
    public ViewFactory getViewFactory() {
        return new StyledViewFactory();
    }

    class StyledViewFactory implements ViewFactory {

        public View create(Element elem) {
            String kind = elem.getName();
            if (kind != null) {
                if (kind.equals(AbstractDocument.ContentElementName)) {
                    return new LabelView(elem);
                }
                else if (kind.equals(AbstractDocument.ParagraphElementName)) {
                    return new ParagraphView(elem);
                }
                else if (kind.equals(AbstractDocument.SectionElementName)) {
                    return new VerticalView(elem, View.Y_AXIS);
                }
                else if (kind.equals(StyleConstants.ComponentElementName)) {
                    return new ComponentView(elem);
                }
                else if (kind.equals(StyleConstants.IconElementName)) {
                    return new IconView(elem);
                }
            }

            // default to text display
            return new LabelView(elem);
        }

    }
}

Like in case with scale we should replace only root view, the view for the document’s root element. All our changes are made in the root view (VerticalView class). We have to update several methods to provide correct layout and rendering, caret navigation, and mouse clicking.

Vertical layout and painting.

To perform layout we will use the same code called by super but swap width and height. We also adapt getXXXSpan methods to return swapped values.

    public float getMinimumSpan(int axis) {
        if (axis == View.X_AXIS) {
            return super.getMinimumSpan(View.Y_AXIS);
        }
        else {
            return super.getMinimumSpan(View.X_AXIS);
        }
    }

    public float getMaximumSpan(int axis) {
        if (axis == View.X_AXIS) {
            return super.getMaximumSpan(View.Y_AXIS);
        }
        else {
            return super.getMaximumSpan(View.X_AXIS);
        }
    }

    public float getPreferredSpan(int axis) {
        if (axis == View.X_AXIS) {
            return super.getPreferredSpan(View.Y_AXIS);
        }
        else {
            return super.getPreferredSpan(View.X_AXIS);
        }
    }

    protected void layout(int width, int height) {
        super.layout(height, width);
    }

One more thing to implement. In out calculations we will need width and height of root view so we just store them in the class’ fields. We also need an orientation flag showing desired vertical text orientation (may be top-to-bottom or bottom-to-top). So we define a field for orientation and two constant values of the field.

class VerticalView extends BoxView {
    public static final int ORIENTATION_UP_DOWN = 0;
    public static final int ORIENTATION_DOWN_UP = 1;
    int orientation = ORIENTATION_UP_DOWN;
    float w = 0;
    float h = 0;

    public VerticalView(Element elem, int axis) {
        super(elem, axis);
    }

    public void setSize(float width, float height) {
        this.w = height;
        this.h = width;
        super.setSize(width, height);
    }

After all the preliminary work is done we can change paint() method rotating and translating real representation to have vertical view. We rotate to Math.PI/2 (plus or minus depending on selected orientation) and translate the rotated content to proper point.

    public void paint(Graphics g, Shape allocation) {
        Graphics2D g2d = (Graphics2D) g;
        AffineTransform old = g2d.getTransform();
        if (orientation == ORIENTATION_DOWN_UP) {
            g2d.rotate( -Math.PI / 2, 0, 0);
            g2d.translate( -w, 0);
        }
        else {
            g2d.rotate(Math.PI / 2, 0, 0);
            g2d.translate(0, -h);
        }
        super.paint(g2d, allocation);
        g2d.setTransform(old);
    }

Arrow keys navigation adapting.

Rotated text requires changed navigation by keys strategy. Left and right arrows should go to next and previous row (depending on orientation selected by user). Up and down arrows accordingly will move caret to previous or next char. To implement the navigation we override getNextVisualPositionFrom() method changing direction and calling super.

    public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
                                         int direction, Position.Bias[] biasRet) throws BadLocationException {
        int d = direction;

        if (orientation == ORIENTATION_DOWN_UP) {
            switch (direction) {
                case NORTH:
                    d = EAST;
                    break;
                case SOUTH:
                    d = WEST;
                    break;
                case EAST:
                    d = SOUTH;
                    break;
                case WEST:
                    d = NORTH;
                    break;
            }
        }
        else {
            switch (direction) {
                case NORTH:
                    d = WEST;
                    break;
                case SOUTH:
                    d = EAST;
                    break;
                case EAST:
                    d = NORTH;
                    break;
                case WEST:
                    d = SOUTH;
                    break;
            }
        }
        return super.getNextVisualPositionFrom(pos, b, a, d, biasRet);
    }

Caret showing and navigation by mouse.

The vertical text should rotate caret accordingly to allow proper navigation. Real caret rectangle for each caret position is returned by modelToView method. We override the method and change shape returned by super. An important note. By default caret width is restricted by SUN’s caret and UI implementation. We have to change caretWidth property of our JEditorPane component to show horizontal caret.

    public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
        Rectangle alloc;
        alloc = a.getBounds();
        Shape s = super.modelToView(pos, alloc, b);
        alloc = s.getBounds();

        if (orientation == ORIENTATION_DOWN_UP) {
            int tmp = alloc.x;
            alloc.x = alloc.y + 10;
            alloc.y = (int) w - tmp;

            tmp = alloc.width;
            alloc.width = alloc.height;
        }
        else {
            int tmp = alloc.x;
            alloc.x = (int) h - alloc.y - 10;
            alloc.y = tmp;

            tmp = alloc.width;
            alloc.width = alloc.height;
        }
        JEditorPane c = (JEditorPane)this.getContainer();
        c.putClientProperty("caretWidth", new Integer(alloc.width));
        alloc.height = 2;

        return alloc;
    }

Then we modify viewToModel method changing coordinates to allow proper caret setting in a point which was clicked by user.

    public int viewToModel(float x, float y, Shape a,
                           Position.Bias[] bias) {
        Rectangle alloc = a.getBounds();
        int tmp = alloc.width;
        alloc.width = alloc.height;
        alloc.height = tmp;

        if (orientation == ORIENTATION_DOWN_UP) {
            return super.viewToModel(w - y, x, alloc, bias);
        }
        else {
            return super.viewToModel(y, h - x, alloc, bias);
        }
    }

Installing the kit let user view and edit vertical text. Mouse cursor wasn’t changed though. It still has the same view as in JEditorPane with normal text but cursor could be replaced with a custom one .

Appendix

Here is the full source code of the vertical text example.

Back to Table of Content