/**
 * @author Stanislav Lapitsky
 * @version 1.0
 */

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

public class VerticalTextPane extends JEditorPane {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Vertical text editting in JEditorPane example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final VerticalTextPane scaledTextPane = new VerticalTextPane();
        JScrollPane scroll = new JScrollPane(scaledTextPane);
        frame.getContentPane().add(scroll);

        final JRadioButton rdbDownUp = new JRadioButton("Bottom to top", false);
        final JRadioButton rdbUpDown = new JRadioButton("Top to bottom", true);
        ButtonGroup bg = new ButtonGroup();
        bg.add(rdbDownUp);
        bg.add(rdbUpDown);
        ActionListener lst = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                VerticalView root = (VerticalView) scaledTextPane.getUI().getRootView(scaledTextPane).getView(0);
                if (rdbDownUp.isSelected()) {
                    root.orientation = VerticalView.ORIENTATION_DOWN_UP;
                }
                else {
                    root.orientation = VerticalView.ORIENTATION_UP_DOWN;
                }
                scaledTextPane.invalidate();
                scaledTextPane.validate();
                scaledTextPane.repaint();
            }
        };
        rdbDownUp.addActionListener(lst);
        rdbUpDown.addActionListener(lst);
        JPanel p = new JPanel();
        p.add(rdbDownUp);
        p.add(rdbUpDown);
        frame.getContentPane().add(p, BorderLayout.NORTH);

        frame.setSize(600, 200);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public VerticalTextPane() {
        super();
        final SimpleAttributeSet attrs = new SimpleAttributeSet();
        setEditorKit(new VerticalEditorKit());
        StyledDocument doc = (StyledDocument) VerticalTextPane.this.getDocument();
        doc.setCharacterAttributes(0, 1, attrs, true);
        try {
            StyleConstants.setFontSize(attrs, 16);
            doc.insertString(0, "16 font test\n", attrs);

            StyleConstants.setFontSize(attrs, 20);
            doc.insertString(0, "20 font test\n", attrs);

            StyleConstants.setFontSize(attrs, 24);
            doc.insertString(0, "24 font test\n", attrs);
        }
        catch (BadLocationException ex) {
        }
    }

    public void repaint(int x, int y, int width, int height) {
        super.repaint(0, 0, getWidth(), getHeight());
    }

}

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);
        }

    }
}

//-----------------------------------------------------------------
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);
    }

    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);
    }

    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);
    }

    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;
    }

    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);
        }
    }

    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);
    }
}