Articles Projects Tips Downloads Contacts About
 

Fix for java 7 text wrapping bug.

Java 7 introduced a new concept of wrapping. The concept seems to have some holes in the implemetation. Then wrapping of text chunks with different attributes (styled fragments) works incorrectly. The bug was posted on stackoverflow.com showing difference in wrap behaviour Java 7 vs Java 6. See the screenshot below:

The problem is incorrect stroing breakSpots array. The break spots are calculated on LabelView level and keep absolute offsets in the Document. But when content is been changed the offset becomes obsolete and must be recalculated. The fix resets the break spots array on change of ParagraphView content. The source code of the fix is below. You cna uncomment EditorKit replacing line and check java default behavior vs fixed behavior.

import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
 
public class BugWrapJava7 {
 
    private JTextPane jtp;
    private StyledDocument doc;
 
    public BugWrapJava7() {
        jtp = new JTextPane();
//        jtp.setEditorKit(new StyledEditorKit());
        jtp.setEditorKit(new MyStyledEditorKit());
        jtp.setText("\ntype some text in the above empty line and check the wrapping behavior");
        doc = jtp.getStyledDocument();
        doc.addDocumentListener(new DocumentListener() {
             public void insertUpdate(DocumentEvent e) {
                insert();
            }
 
            public void removeUpdate(DocumentEvent e) {
                insert();
            }
 
            public void changedUpdate(DocumentEvent e) {
                insert();
            }
 
            public void insert() {
                SwingUtilities.invokeLater(new Runnable() {
                     public void run() {
                        Style defaultStyle = jtp.getStyle(StyleContext.DEFAULT_STYLE);
                        doc.setCharacterAttributes(0, doc.getLength(), defaultStyle, false);
                    }
                });
            }
        });
        JScrollPane scroll = new JScrollPane(jtp);
        scroll.setPreferredSize(new Dimension(200, 200));
        JFrame frame = new JFrame();
        frame.add(scroll);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
 
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
             public void run() {
                BugWrapJava7 bugWrapJava7 = new BugWrapJava7();
            }
        });
    }
}
 
class MyStyledEditorKit extends StyledEditorKit {
    private MyFactory factory;
 
    public ViewFactory getViewFactory() {
        if (factory == null) {
            factory = new MyFactory();
        }
        return factory;
    }
}
 
class MyFactory implements ViewFactory {
    public View create(Element elem) {
        String kind = elem.getName();
        if (kind != null) {
            if (kind.equals(AbstractDocument.ContentElementName)) {
                return new MyLabelView(elem);
            } else if (kind.equals(AbstractDocument.ParagraphElementName)) {
                return new MyParagraphView(elem);
            } else if (kind.equals(AbstractDocument.SectionElementName)) {
                return new BoxView(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 MyParagraphView extends ParagraphView {
 
    public MyParagraphView(Element elem) {
        super(elem);
    }
    public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
        super.removeUpdate(e, a, f);
        resetBreakSpots();
    }
    public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
        super.insertUpdate(e, a, f);
        resetBreakSpots();
    }
 
    private void resetBreakSpots() {
        for (int i=0; i<layoutPool.getViewCount(); i++) {
            View v=layoutPool.getView(i);
            if (v instanceof MyLabelView) {
                ((MyLabelView)v).resetBreakSpots();
            }
        }
    }
}
 
class MyLabelView extends LabelView {
 
    boolean isResetBreakSpots=false;
 
    public MyLabelView(Element elem) {
        super(elem);
    }
    public View breakView(int axis, int p0, float pos, float len) {
        if (axis == View.X_AXIS) {
            resetBreakSpots();
        }
        return super.breakView(axis, p0, pos, len);
    }
    
    public void resetBreakSpots() {
        isResetBreakSpots=true;
        removeUpdate(null, null, null);
        isResetBreakSpots=false;
   }
 
    public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
        super.removeUpdate(e, a, f);
    }
 
    public void preferenceChanged(View child, boolean width, boolean height) {
        if (!isResetBreakSpots) {
            super.preferenceChanged(child, width, height);
        }
    }
}