javahtmlswingjeditorpanehtmleditorkit

Swing HTMLEditorKit / JEditorPane doesn't handle <br> and empty lines correctly


When a JEditorPane backed by an HTMLEditorKit contains a <br> tag followed by an empty line, that line is not rendered correctly and the caret is not handled correctly. Consider this sample code:

import java.awt.*;
import java.io.*;

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.*;

public class HTMLEditorTest {

    public static void main(String[] args) throws IOException, BadLocationException {
        JFrame frame = new JFrame();

        Reader stringReader = new StringReader("test<br><p>a");
        HTMLEditorKit htmlKit = new HTMLEditorKit();
        HTMLDocument htmlDoc = (HTMLDocument) htmlKit.createDefaultDocument();
        htmlKit.read(stringReader, htmlDoc, 0);
        JEditorPane editorPane = new JEditorPane();
        editorPane.setEditorKit(htmlKit);
        editorPane.setDocument(htmlDoc);

        frame.getContentPane().add(BorderLayout.CENTER, new JScrollPane(editorPane));
        frame.setBounds(100, 100, 500, 400);
        frame.setVisible(true);
    }
}

The empty line after the <br> tag is not rendered. When the caret is positioned left of the 'a' char and the arrow up key is pressed, the caret disappears:

Before pressing 'up':

enter image description here

After pressing 'up':

After pressing 'up'

Note that the distance between 'test' and 'a' is too small, and the caret has disappeared.

When you then enter text, the missing empty line becomes visible:

After pressing 'b'

The problem seems to be that the empty line is rendered with a height of 0px, and thus is not visible, including the caret if it is on that line. Once the line has content, that content forces a non-zero line height.

Do you know a simple workaround / fix for this problem? I reckon in the worst case, I have to write my own editor kit (see also here and here for custom line wrapping in JEditorPane) and/or custom tag (also here).


Solution

  • Found a solution, using a custom editor kit:

    public class MyEditorKit extends HTMLEditorKit {
    
        private static final int MIN_HEIGHT_VIEWS = 10;
    
        @Override
        public ViewFactory getViewFactory() {
    
            return new HTMLFactory() {
    
                @Override
                public View create(Element e) {
                    View v = super.create(e);
                    // Test for BRView must use String comparison, as the class is package-visible and not available to us
                    if ((v instanceof InlineView) && !v.getClass().getSimpleName().equals("BRView")) {
    
                        View v2 = new InlineView(e) {
    
                            @Override
                            public float getMaximumSpan(int axis) {
                                float result = super.getMaximumSpan(axis);
                                if (axis == Y_AXIS) {
                                    result = Math.max(result, MIN_HEIGHT_VIEWS);
                                }
                                return result;
                            }
    
                            @Override
                            public float getMinimumSpan(int axis) {
                                float result = super.getMinimumSpan(axis);
                                if (axis == Y_AXIS) {
                                    result = Math.max(result, MIN_HEIGHT_VIEWS);
                                }
                                return result;
                            }
    
                            @Override
                            public float getPreferredSpan(int axis) {
                                float result = super.getPreferredSpan(axis);
                                if (axis == Y_AXIS) {
                                    result= Math.max(result, MIN_HEIGHT_VIEWS);
                                }
    
                                return result;
                            }
                        };
    
                        v = v2;
                    }
    
                    return v;
                }
            };
        }
    }
    

    The editor kit returns a custom HTMLFactory. This factory creates custom InlineView objects for leaf elements, where the InlineView cannot have a height of 0. It will always have at least a MIN_HEIGHT_VIEW, which I set to 10 pixels (works reasonably well with default font sizes). The original implementation makes sense when rendering HTML just for viewing, as an empty line after a <br> tag should indeed be ignored. But for editing, users will expect to see the caret on the next line after inserting a linebreak.