JTextPane and Hanging Indent Glitch

佐手、 提交于 2020-01-04 03:38:28

问题


I'm facing an annoying little bug with JTextPane and hanging indent.

Here's a simple example:

public class Scrap {

public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(200, 200);
    frame.setLayout(new BorderLayout());

    JTextPane textPane = new JTextPane();

    JScrollPane scroll = new JScrollPane(textPane);

    frame.add(scroll);

    StyledDocument doc = (StyledDocument) textPane.getDocument();

    try {

        String str = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum ";

        doc.insertString(doc.getLength(), str, null);

        // Hanging indent
        MutableAttributeSet mas = new SimpleAttributeSet();
        StyleConstants.setLeftIndent(mas, 20);
        StyleConstants.setFirstLineIndent(mas, -20);
        doc.setParagraphAttributes(0, str.length(), mas, false);

    } catch (BadLocationException e) {
        e.printStackTrace();
    }

    frame.setVisible(true);
    frame.setLocationRelativeTo(null);
}
}

On my computer, with Java 7, the first row is bolder than the other rows for some reason... Anyone have ideas how to fix this?


回答1:


I got back to this, and I got it fixed! At least well enough for my needs. The problem was, as I suspected, that JTextPane drew the first line twice.

Oracle conveniently ignored my bug report, I guess they just don't care about Swing anymore.

Here's the fix (including the long word wrap fix for Java 7, which I found from somewhere):

import javax.swing.*;
import javax.swing.text.Element;
import javax.swing.text.ParagraphView;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.InlineView;
import java.awt.*;

/**
 * A fixed HTML Editor Kit, which fixes two things:
 * - Word wrapping of long words (bugged in Java 7)
 * - A hanging indent bug
 */
public class FixedHtmlEditorKit extends HTMLEditorKit {

@Override
public ViewFactory getViewFactory() {
    return new HTMLEditorKit.HTMLFactory() {
        public View create(Element e) {
            View v = super.create(e);

            if (v instanceof InlineView) {
                return new InlineView(e) {
                    public int getBreakWeight(int axis, float pos, float len) {
                        return GoodBreakWeight;
                    }

                    public View breakView(int axis, int p0, float pos, float len) {
                        if (axis == View.X_AXIS) {
                            checkPainter();
                            int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
                            if (p0 == getStartOffset() && p1 == getEndOffset()) {
                                return this;
                            }
                            return createFragment(p0, p1);
                        }
                        return this;
                    }
                };
            }
            else if (v instanceof ParagraphView) {
                return new ParagraphView(e) {
                    protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
                        if (r == null) {
                            r = new SizeRequirements();
                        }
                        float pref = layoutPool.getPreferredSpan(axis);
                        float min = layoutPool.getMinimumSpan(axis);
                        // Don't include insets, Box.getXXXSpan will include them.
                        r.minimum = (int) min;
                        r.preferred = Math.max(r.minimum, (int) pref);
                        r.maximum = Integer.MAX_VALUE;
                        r.alignment = 0.5f;
                        return r;
                    }

                    private boolean allowedToPaintFirstView = true;

                    private float tabBase;

                    /*
                     * We need to override this since tabBase is private in ParagraphView.
                     */
                    @Override
                    protected float getTabBase() {
                        return tabBase;
                    }

                    @Override
                    protected void paintChild(Graphics g, Rectangle alloc, int index) {
                        // Don't paint the first index twice!
                        if (index == 0 && !allowedToPaintFirstView) {
                            return;
                        }
                        super.paintChild(g, alloc, index);
                    }


                    public void paint(Graphics g, Shape a) {
                        Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();

                        tabBase = alloc.x + getLeftInset();

                        // line with the negative firstLineIndent value needs
                        // special handling
                        if (firstLineIndent < 0) {
                            Shape sh = getChildAllocation(0, a);
                            if ((sh != null) &&  sh.intersects(alloc)) {
                                int x = alloc.x + getLeftInset() + firstLineIndent;
                                int y = alloc.y + getTopInset();

                                Rectangle clip = g.getClipBounds();
                                Rectangle tempRect = new Rectangle();
                                tempRect.x = x + getOffset(X_AXIS, 0);
                                tempRect.y = y + getOffset(Y_AXIS, 0);
                                tempRect.width = getSpan(X_AXIS, 0) - firstLineIndent;
                                tempRect.height = getSpan(Y_AXIS, 0);
                                if (tempRect.intersects(clip)) {
                                    tempRect.x = tempRect.x - firstLineIndent;
                                    allowedToPaintFirstView = true;
                                    paintChild(g, tempRect, 0);
                                    allowedToPaintFirstView = false;
                                }
                            }
                        }

                        super.paint(g, a);
                    }
                };
            }
            return v;
        }
    };
}

}




回答2:


If I am not wrong, you might be using MetalLookAndFeel, which by default uses bold fonts for many controls.

Try this, to Turn off metal's use of bold fonts in ur main method;

    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            //Turn off metal's use of bold fonts
        UIManager.put("swing.boldMetal", Boolean.FALSE);
            createAndShowGUI();
        }
    });



回答3:


Almost five years later this code came in handy. However, we did run into an issue where the initial paint is showing incorrect tab behavior when setting tab-stops. This might be Java 8 specific, or it might just be easily hidden in applications where a repaint happens at the right time.

We solved the issue by forcefully injecting the tabBase into the parent via reflection as any further attempts at extending the class became complicated.

Here is the modified portion of the accepted answer

@Override
public void paint(Graphics g, Shape a) {
    final Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();

    tabBase = alloc.x + getLeftInset();

    // Set the tabBase into the parent ParagraphView - not all its use getTabBase() but the parent
    // handles some of the painting, so it needs to have this value set properly
    try {
        final Field parentTabBase = ParagraphView.class.getDeclaredField("tabBase");
        parentTabBase.setAccessible(true);
        parentTabBase.set(this, tabBase);
        parentTabBase.setAccessible(false);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        throw new RuntimeException("Error encountered setting tabBase", e);
    }

    // line with the negative firstLineIndent value needs
    // special handling
    if (firstLineIndent < 0) {


来源:https://stackoverflow.com/questions/12817239/jtextpane-and-hanging-indent-glitch

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!