How to remove gap in java swing label of large size
JDigit
may give you some ideas:
It override's
paintComponent()
to down-sample a high-resolutionBufferedImage
and control the geometry.It uses
setBorderPainted(false)
to set theborderPainted
property.It uses a
FocusHandler
for custom highlighting.
Addendum: As noted here, the underlying problem is the font's leading, defined in FontMetrics
as being included in the font's height. As suggested in a comment by @Guillaume Polet, you can render the text wherever you want in your own JComponent
. TextLayout
, discussed here, can be used to calculate the bounds, as shown below.
Pros:
Absolute control over placement.
Geometry of
TexteLayout
bounds based onFontMetrics
.
Cons:
No
Icon
support.No HTML support.
Note that the JComponent
authors "recommend that you put the component in a JPanel
and set the border on the JPanel
."
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* @see https://stackoverflow.com/a/16014525/230513
*/
public class UnleadedTest {
private static class Unleaded extends JComponent {
private Font font = new Font("Verdana", Font.PLAIN, 144);
private FontRenderContext frc = new FontRenderContext(null, true, true);
private String text;
private TextLayout layout;
private Rectangle r;
public Unleaded(String text) {
this.text = text;
calcBounds();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(r.width, r.height);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
calcBounds();
layout.draw(g2d, -r.x, -r.y);
}
private void calcBounds() {
layout = new TextLayout(text, font, frc);
r = layout.getPixelBounds(null, 0, 0);
}
}
private void display() {
JFrame f = new JFrame("Unleaded");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Unleaded label = new Unleaded("Unleaded");
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createTitledBorder("Title"));
panel.add(label);
f.add(panel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new UnleadedTest().display();
}
});
}
}
The "right way" to do this would be to extend "BasicLabelUI" and override the "protected String layoutCL()" method. This is the method that is responsible for laying out everything inside the label and is called when the "getPreferredSize()" of the JLabel is called. So this method determines the height the component is going to be.
If you drill down deep enough you'll see that the height is determined by the following line in the SwingUtilities:1021 class (which is used by layoutCL):
textR.height = fm.getHeight();
So the label is not causing the white space, the font is. The label just conforms to what the FontMetrics object says is the maximum height of the font for that size.
The easiest way would probably be to cheat; Force the size calculation to do something it shouldn't. Below is your example with a custom LabelUI component which you can experiment on. For example if you force the variable to 'dy' to '-40' the text will be at the top. If you want to make something more durable you could check all the leters in the string of the label, measure their maximum height and use that in the layoutCL method. But thats more work obviously.
package Core;
import sun.swing.SwingUtilities2;
import javax.swing.*;
import javax.swing.plaf.LabelUI;
import javax.swing.plaf.basic.BasicLabelUI;
import javax.swing.text.View;
import java.awt.*;
public class LabelDemo extends JPanel {
public LabelDemo() {
super(new GridBagLayout());
JLabel label2;
GridBagConstraints c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
// Create the other labels.
label2 = new JLabel("Text-Only Label");
label2.setVerticalAlignment(SwingUtilities.TOP);
label2.setVerticalTextPosition(SwingUtilities.TOP);
label2.setUI(SkinnyLabelUI.createUI(label2));
label2.setBorder(BorderFactory.createTitledBorder("aaaaaaaa"));
label2.setFont(new Font("Verdana", Font.PLAIN, (int) 220));
// label2.setBorder(new EmptyBorder(-50, 0, 0, 0));
// Add the labels.
add(label2, c);
}
/**
* Create the GUI and show it. For thread safety, this method should be
* invoked from the event dispatch thread.
*/
private static void createAndShowGUI() {
// Create and set up the window.
JFrame frame = new JFrame("LabelDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Add content to the window.
frame.add(new LabelDemo());
// Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
// Schedule a job for the event dispatch thread:
// creating and showing this application's GUI.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Turn off metal's use of bold fonts
UIManager.put("swing.boldMetal", Boolean.FALSE);
createAndShowGUI();
}
});
}
private static class SkinnyLabelUI extends BasicLabelUI {
private static final SkinnyLabelUI labelUI = new SkinnyLabelUI();
public static LabelUI createUI(JComponent c) {
return labelUI;
}
protected String layoutCL(
JLabel label,
FontMetrics fm,
String text,
Icon icon,
Rectangle viewR,
Rectangle iconR,
Rectangle textR) {
int verticalAlignment = label.getVerticalAlignment();
int horizontalAlignment = label.getHorizontalAlignment();
int verticalTextPosition = label.getVerticalTextPosition();
int horizontalTextPosition = label.getHorizontalTextPosition();
if (icon != null) {
iconR.width = icon.getIconWidth();
iconR.height = icon.getIconHeight();
} else {
iconR.width = iconR.height = 0;
}
/* Initialize the text bounds rectangle textR. If a null
* or and empty String was specified we substitute "" here
* and use 0,0,0,0 for textR.
*/
boolean textIsEmpty = (text == null) || text.equals("");
int lsb = 0;
int rsb = 0;
/* Unless both text and icon are non-null, we effectively ignore
* the value of textIconGap.
*/
int gap;
View v;
if (textIsEmpty) {
textR.width = textR.height = 0;
text = "";
gap = 0;
} else {
int availTextWidth;
gap = (icon == null) ? 0 : label.getIconTextGap();
if (horizontalTextPosition == SwingUtilities.CENTER) {
availTextWidth = viewR.width;
} else {
availTextWidth = viewR.width - (iconR.width + gap);
}
v = (label != null) ? (View) label.getClientProperty("html") : null;
if (v != null) {
textR.width = Math.min(availTextWidth,
(int) v.getPreferredSpan(View.X_AXIS));
textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
} else {
textR.width = SwingUtilities2.stringWidth(label, fm, text);
lsb = SwingUtilities2.getLeftSideBearing(label, fm, text);
if (lsb < 0) {
// If lsb is negative, add it to the width and later
// adjust the x location. This gives more space than is
// actually needed.
// This is done like this for two reasons:
// 1. If we set the width to the actual bounds all
// callers would have to account for negative lsb
// (pref size calculations ONLY look at width of
// textR)
// 2. You can do a drawString at the returned location
// and the text won't be clipped.
textR.width -= lsb;
}
if (textR.width > availTextWidth) {
text = SwingUtilities2.clipString(label, fm, text,
availTextWidth);
textR.width = SwingUtilities2.stringWidth(label, fm, text);
}
textR.height = fm.getHeight();
System.out.println("font height: " + textR.height);
}
}
/* Compute textR.x,y given the verticalTextPosition and
* horizontalTextPosition properties
*/
if (verticalTextPosition == SwingUtilities.TOP) {
if (horizontalTextPosition != SwingUtilities.CENTER) {
textR.y = 0;
} else {
textR.y = -(textR.height + gap);
}
} else if (verticalTextPosition == SwingUtilities.CENTER) {
textR.y = (iconR.height / 2) - (textR.height / 2);
} else { // (verticalTextPosition == BOTTOM)
if (horizontalTextPosition != SwingUtilities.CENTER) {
textR.y = iconR.height - textR.height;
} else {
textR.y = (iconR.height + gap);
}
}
if (horizontalTextPosition == SwingUtilities.LEFT) {
textR.x = -(textR.width + gap);
} else if (horizontalTextPosition == SwingUtilities.CENTER) {
textR.x = (iconR.width / 2) - (textR.width / 2);
} else { // (horizontalTextPosition == RIGHT)
textR.x = (iconR.width + gap);
}
// WARNING: DefaultTreeCellEditor uses a shortened version of
// this algorithm to position it's Icon. If you change how this
// is calculated, be sure and update DefaultTreeCellEditor too.
/* labelR is the rectangle that contains iconR and textR.
* Move it to its proper position given the labelAlignment
* properties.
*
* To avoid actually allocating a Rectangle, Rectangle.union
* has been inlined below.
*/
int labelR_x = Math.min(iconR.x, textR.x);
int labelR_width = Math.max(iconR.x + iconR.width,
textR.x + textR.width) - labelR_x;
int labelR_y = Math.min(iconR.y, textR.y);
int labelR_height = Math.max(iconR.y + iconR.height,
textR.y + textR.height) - labelR_y;
int dx, dy;
if (verticalAlignment == SwingUtilities.TOP) {
dy = viewR.y - labelR_y;
} else if (verticalAlignment == SwingUtilities.CENTER) {
dy = (viewR.y + (viewR.height / 2)) - (labelR_y + (labelR_height / 2));
} else { // (verticalAlignment == BOTTOM)
dy = (viewR.y + viewR.height) - (labelR_y + labelR_height);
}
if (horizontalAlignment == SwingUtilities.LEFT) {
dx = viewR.x - labelR_x;
} else if (horizontalAlignment == SwingUtilities.RIGHT) {
dx = (viewR.x + viewR.width) - (labelR_x + labelR_width);
} else { // (horizontalAlignment == CENTER)
dx = (viewR.x + (viewR.width / 2))
- (labelR_x + (labelR_width / 2));
}
/* Translate textR and glypyR by dx,dy.
*/
textR.x += dx;
textR.y += dy;
iconR.x += dx;
iconR.y += dy;
if (lsb < 0) {
// lsb is negative. Shift the x location so that the text is
// visually drawn at the right location.
textR.x -= lsb;
textR.width += lsb;
}
if (rsb > 0) {
textR.width -= rsb;
}
return text;
}
}
}