Search code examples
javaswing

How to change line spacing in a multiline JButton


Since multiline buttons can be created with html text, I tried html's line-height attribute with decimal, pixel and percentage values to change the vertical line spacing. But in vain.

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

public class LineSpacing extends JFrame {
  public static final long serialVersionUID = 100L;

  public LineSpacing() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setSize(480, 240);
    setLayout(new FlowLayout());
    setLocationRelativeTo(null);

    add(createBox("Decimal", new String[]{"0.8", "0.9", "1.1"}));
    add(createBox("px", new String[]{"10px", "12px", "15px"}));
    add(createBox("%", new String[]{"85%", "95%", "110%"}));
    setVisible(true);
  }


  static public void main(String args[]) {
    EventQueue.invokeLater(LineSpacing::new);
  }


  private Box createBox(String header, String[] values) {
    Box box= Box.createVerticalBox();
    JPanel p= new JPanel();
    p.add(new JLabel(header, SwingConstants.CENTER));
    box.add(p);
    for (int i=0; i<3; i++) {
      JButton b= new JButton("<html><center><span style='line-height:"+
        values[i]+"'>First line<br>and the second</span></html>");
      b.setPreferredSize(new Dimension(130, 40));
      b.setMargin(new Insets(0,0,0,0));
      box.add(b);
    }
    return box;
  }

}

EDIT

Thanks to Dewmith Mihisara for his code below.

I modified his LineHeightButton class to get the overprinting fixed.

  add(createBox("Decimal", new Dimension(130, 40),
            new int[]{-1, 0, 1, 2, 4, 6}));
.
.
    private Box createBox(String header, Dimension dim, int[] spacings) {
.
.
    JButton b = new LineHeightButton("First line\nand the second",
                                     dim, spacing);
====================================================

class LineHeightButton extends JButton {
    private final int lineSpacing;
    String text;

    public LineHeightButton(String text, Dimension dim, int lineSpacing) {
        super();
        this.lineSpacing = lineSpacing;
        this.text= text;
        setPreferredSize(dim);
        setMinimumSize(dim);
        setMaximumSize(dim);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setFont(getFont());
        String[] lines = text.split("\n");
        int i= 0, y = 15;
        for (String line : lines) {
            g2.drawString(line, getInsets().left,
                y += i*(g2.getFontMetrics().getHeight()-5 + lineSpacing));
            i++;
        }
    }
}

Remains the centering to be done, but that was not the problem.


Solution

  • Swing's HTML support is based on a subset of HTML 3.2, and it does not fully support CSS style, which includes the line-height property in all forms.

    The line-height property might work with relative or percentage values if the HTML rendering engine in Swing recognizes it. However, since it's not working as intended, consider these workarounds:

    Replace the <span> element with a <div> and set the line-height there. Sometimes, div elements with line-height are rendered better.

    JButton b = new JButton("<html><center><div style='line-height:" +
        values[i] + "'>First line<br>and the second</div></html>");
    

    If the line-height property still doesn't work, use explicit spacing methods, such as padding or adding <br> tags with inline font-size.

    Instead of relying on HTML styling, adjust the text rendering yourself using a custom JButton or a JLabel with multiline support.

    Example :

    import java.awt.*;
    import javax.swing.*;
    
    public class LineSpacingCustom extends JFrame {
        public static final long serialVersionUID = 100L;
    
        public LineSpacingCustom() {
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setSize(480, 240);
            setLayout(new FlowLayout());
            setLocationRelativeTo(null);
    
            add(createBox("Decimal", new float[]{0.8f, 0.9f, 1.1f}));
            setVisible(true);
        }
    
        public static void main(String[] args) {
            EventQueue.invokeLater(LineSpacingCustom::new);
        }
    
        private Box createBox(String header, float[] spacings) {
            Box box = Box.createVerticalBox();
            JPanel p = new JPanel();
            p.add(new JLabel(header, SwingConstants.CENTER));
            box.add(p);
    
            for (float spacing : spacings) {
                JButton b = new LineHeightButton("First line\nand the second", spacing);
                b.setPreferredSize(new Dimension(130, 60));
                box.add(b);
            }
            return box;
        }
    }
    
    class LineHeightButton extends JButton {
        private final float lineSpacing;
    
        public LineHeightButton(String text, float lineSpacing) {
            super();
            this.lineSpacing = lineSpacing;
            setText(text);
        }
    
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.setFont(getFont());
            String[] lines = getText().split("\n");
            int y = getInsets().top;
            for (String line : lines) {
                g2.drawString(line, getInsets().left, y += g2.getFontMetrics().getHeight() * lineSpacing);
            }
        }
    }