3

TL;DR: What is the easiest and the most correct way to set up a Graphics instance to use default settings to render strings? Or how to render a string using default anti-aliasing settings, so that it looks like a JLabel? More detailed description of the issue follows...

I'm trying to create a custom JComponent subclass. In fact, it's a sort of a TableCellRenderer that is able display rich text. Extending JEditorPane is too heavy and slow (tried it actually), and JLabel can't display rich text, so I decided to implement my own, lightweight and fast. Now, it (obviously) needs to draw some text in paintComponent(), and I would like this text to look like in all other text components, like JLabel.

However, when I do it, it seems to use different anti-aliasing settings from the rest of the app, so it looks rather ugly. I realize that I can just cast Graphics to Graphics2D to use the appropriate API, but the question is, what exact settings to use? That is, what to pass to setRenderingHint() as the second parameter?

I can get it look fine on my system by playing with various AA values, but then won't it suddenly look awful on some other system with different default AA settings?

I tried to look at JLabel and LabelUI sources, but they seem to use a lot of black magic, like querying some occult properties using JComponent.getClientProperty() and SwingUtilities2 which isn't even a part of the official Swing. Of course, I could try to mimic that, but that's a) too tedious and b) bound to use some not-too-documented features that aren't event guaranteed to have a stable API.

Or maybe there is a way to reuse some existing UI delegate? Basically, I just want my text to look exactly as displayed by a JLabel. I could even use an instance of JLabel or its subclass as a sort of "rubber stamp" to draw the text, but that looks a bit ugly and I'm not sure about performance. I realize that JTable actually does exactly that, but using a JLabel to draw an entire cell is one thing, using it to draw parts of the cell just doesn't feel right. For example, it could happen that some L&F decorates these JLabels in a special way that will just look ugly.

Ideally, I would like to have something like this (imaginary code):

((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    JLabel.getDefaultUI().getRenderingHint());

Or better yet (set up everything, not just AA):

JLabel.getDefaultUI().setupGraphics(g); // this sets up g just like for drawing a JLabel

But there is seems to be no such simple thing as far as I can see.

Sergei Tachenov
  • 24,345
  • 8
  • 57
  • 73
  • I think it's better to set the "TL;DR" first, so the readers can get the summary before reading the whole text. – Yago Méndez Vidal Sep 29 '14 at 08:16
  • The intention to "let the text look like that on a JLabel" reminded me of the (at the first glance seemingly unrelated) question http://stackoverflow.com/questions/25031328 : The text rendering in Swing is indeed rather obscure. However, the "rubber stamp" approach that you mentioned is not really uncommon: That's exactly how default `ListCellRenderer` implementations work... – Marco13 Sep 30 '14 at 16:22
  • @Marco13, given that I'm trying to create a sort of rich text `TableCellRenderer`, maybe I'll go with that one. But rendering a whole cell is one thing, and rendering different pieces of text is another: it could happen that some L&F draws my rubber-stamp `JLabel`s with some decorations around them, which are nice around the cell, but not exactly inside it. – Sergei Tachenov Sep 30 '14 at 16:35
  • This *might* be (it seems unlikely for me, given that a `JLabel` is commonly used as such a "stamp", but of course, every L&F may do whatever it wants). You might want to have a look at the implementation notes and source code of `DefaultTableCellRenderer` nevertheless... – Marco13 Sep 30 '14 at 16:40
  • Referring to your EDIT with the `JEditorPane`: Some of the implementation notes in `DefaultTableCellRenderer` (particularly, some methods that are overridden to be empty) exactly refer to the performance when using a component as a stamp. It *might* be possible to do something similar in an own subclass of `JEditorPane`, but this would have to be tested... – Marco13 Sep 30 '14 at 16:43
  • @Marco13, the first thing I did is overridden those methods to be empty. No, JEditorPane is just way too heavy. And it's kind of hard to fit in 16 pixel height, to get rid of all those margins and other unnecessary formatting. It feels like using a web browser to draw a single button. – Sergei Tachenov Sep 30 '14 at 17:08
  • Sorry, just thought that it may be worth a try. Can't help you with the "manual text rendering" issue beyond that. – Marco13 Sep 30 '14 at 17:11

2 Answers2

3

Sometimes we can't see the forest for the trees...

and JLabel can't display rich text

If your need is having rich text in a table renderer, then (maybe) that's the question! JLabel in fact does render rich text through HTML [Doc], and even you can use JXLabel from SwingX for further functionality (rotation! line break!).

Considering that DefaultTableRenderer is in fact a JLabel maybe you can extend it and do a simple modification:

public class AATableRenderer extends DefaultTableCellRenderer {

    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
        DefaultTableCellRenderer c = (DefaultTableCellRenderer) super
                .getTableCellRendererComponent(table, value, isSelected,
                        hasFocus, row, column);

        String text = c.getText();

        // Do some style transformations maybe...

        c.setText("<html>" + text + "</html>");

        return c;
    }

}

or you can modify your JTable in the prepareRenderer() method

public class AATable extends JTable {

    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
        Component c = super.prepareRenderer(renderer, row, column);

        if (c instanceof JLabel) {
            JLabel l = (JLabel) c;
            String text = l.getText();

            // Do some style transformations maybe...

            l.setText("<html>" + text + "</html>");
        }

        return c;
    }

}

But maybe you can't use the HTML thing... In that case, if you have to stick to your custom component you might have to do it per look and feel as each is managed on their own way. For instance note that sometimes this properties are set in the system and most Swing L&Fs respect them (Windows 7 configures this in My Computer > Properties > Advanced System Configuration > Performance > Smooth fonts (a check in the list)).

I don't think you can reuse the LabelUI (the basic one) because maybe all that magic interferres in the way you want to paint when you just want the smoothing... Not sure, it's not impossible but I can't tell.

THIS IS THE ANSWER TO THE ORIGINAL QUESTION

Short answer

g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

Long answer

The reason why your component uses a different settings is because Swing uses a delegation architecture for the painting: the L&F holds the delegate prototypes for each type of component, when a component is instantiated the delegate is created and injected, and there the painting (and behavior) is done. When you just implement the paintComponent(Graphics) method you're skipping all that architecture and doing the painting directly and you're missing those settings that might be done in the delegates*. [Doc]

The most proper ("proper" is not always the best solution!) way to do your custom component is building your UI delegate, registering it and doing the appropriate procedures. [Doc]

Anyway this is a long tedious work, so let's be practical: If your application just uses one look and feel, if you're not going to reuse this component, if your time constraints do not allow you to spend a lot of time developing a single component... if you just need to set your component anti-aliased, use this code snippet:

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JComponent;

public class AAComponent extends JComponent {

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); // Maybe you want to skip this, up to your needs...

        Graphics2D g2 = null;
        try {
            g2 = (Graphics2D) g.create();

            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            // Your painting procedures
        } finally {
            if (g2 != null) {
                g2.dispose();
            }
        }
    }

}

Maybe the instantiation of g2 looks a bit weird but it's quite optimal, it's a way not to propagate changes in your Graphics and the computational cost is less than minimal. Also if you'd want to propagate the antialiasing to the super class painting (in this case JComponent does nothing) just call the super implementation after setting the rendering hint.

* This is a bit inaccurate because the configuration does not necessarily rely on the L&F but in the combination with delegates, but I think it's a good way to illustrate what happens.

  • So there is no easier way to make my text to look exactly like a JLabel, short of doing exactly the same thing as JLabel does? I mean, building an UI delegate for every L&F there is? I'd rather use a JLabel as a rubber-stamp, much like JTable does. – Sergei Tachenov Sep 30 '14 at 16:19
  • Oh, and my application has rather limited use within one organization, but a) learning the proper way to do things never hurts, and b) even with one default L&F I managed to choose a nice AA setting (wasn't even ON, but rather HRGB or something) on Win 7, then moved to a nearby computer with Win XP and got a noticeable difference (funny enough, my component looked better than `JLabel`s, but still). – Sergei Tachenov Sep 30 '14 at 17:14
  • As far as I know, that's not a configuration by default in every look and feel and not in UI delegates, but it's possible that a given L&F can be configured so, i.e. Substance I believe it has a property for that. Time ago when I faced this (or something similar) I figured that `GraphicsEnvironment` could be involved in the `Graphics` configuration but as far as I got, it's coded per L&F. I'm going to give you an alternative anyway... (going to edit the answer) – Yago Méndez Vidal Sep 30 '14 at 19:59
  • Da-a-amn. They really should have documented this in JLabel javadocs! I looked everywhere. I even read all those tutorials on JTable and JLabel, but failed to noticed that one about HTML. Looks like the way to go! Not sure if HTML parsing will give an acceptable performance, but then I'll just have to fall back to setting AA manually... Still worth the try. Thanks! – Sergei Tachenov Oct 01 '14 at 15:49
  • Works pretty fine and it's a reduced set of HTML which makes it more efficient. AFAIK it works across many Swing components (I believe in `JButton` also works) and also note that you can use CSS styling by adding the attribute `style` to your tags. – Yago Méndez Vidal Oct 02 '14 at 08:45
0

For general purposes, the following attributes should set anti-aliasing across your application.

    System.out.println(System.getProperty("awt.useSystemAAFontSettings"));
    System.setProperty("awt.useSystemAAFontSettings","on"); 
    System.out.println(System.getProperty("awt.useSystemAAFontSettings"));
    System.setProperty("swing.aatext", "true");
    // etc.

Alternatively, when overriding paintComponent() in the graphics context of your JLabel or JPanel:

        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g.create();
            try {
                // you can query the RenderingHints here
                g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
            } finally {
                if(g2 != null) {
                    g2.dispose();
                }
            }
        }
Gorbles
  • 1,169
  • 12
  • 27
  • No, no, no. I don't want to turn the anti-aliasing on. I want to turned on if it is turned by default for the particular system and also to use the same mode as the standard components. I. e., I want drawString() to use exactly the same AA settings as JLabel does, for example. – Sergei Tachenov Sep 30 '14 at 16:04
  • That's why I included the way to get the current setting as well. – Gorbles Oct 01 '14 at 08:19
  • But the problem is, standard components like JLabel use some other defaults than the `Graphics` instance that is passed to `paintComponent()`. – Sergei Tachenov Oct 01 '14 at 15:40
  • You sure? I've set that in my projects (Java 7) and `JButtons` and `JLabels` are antialiased at the system level. – Gorbles Oct 02 '14 at 14:06
  • I don't want to change AA for `JButton`s and `JLabel`s. I wanted to change AA for text drawn using `drawString()` to match those of `JLabel`s. Turns out it nearly impossible to do, so I have to use `JLabel`s instead of `drawString()`. – Sergei Tachenov Oct 03 '14 at 14:34
  • Ahh, sorry for misunderstanding :) – Gorbles Oct 06 '14 at 08:27