tags:

views:

1511

answers:

4

How can I create a Java/Swing text component that is both styled and has a custom font? I want to highlight a small portion of the text in red, but at the same time use a custom (embedded via Font.createFont) font. JLabel accepts HTML text, which allows me to highlight a portion of the text, but it ignores font settings when rendering HTML. Other text components such as JTextArea will use the custom font, but they won't render HTML. What's the easiest way to do both?

Here's an example of using JTextPane unsuccessfully:

    JTextPane textPane = new JTextPane();
    textPane.setFont(myCustomFont);
    textPane.setText(text);
    MutableAttributeSet attributes = new SimpleAttributeSet();
    StyleConstants.setForeground(attributes, Color.RED);
    textPane.getStyledDocument().setCharacterAttributes(
        text.indexOf(toHighlight),
        toHighlight.length(),
        attributes, true
    );

This successfully displays the text with the "toHighlight" portion highlighted in red, but it doesn't use myCustomFont. Note that I could set a String font with StyleConstants.setFontFamily(), but not a custom font.

A: 

You should try to use JEditorPane or JTextPane instead.

They allow rich style in the content, at the price of a more complex API. Unfortunately, if you are in search of a pixel-prefect UI, they also have an additional problem: they don't support baseline-alignment (Java 6 feature).

jfpoilpret
I've tried both of those, but still with the same problem. If I use StyledDocument, it ignores any font settings I have, and insists that I set fonts using "FontFamily", which only allows a String--not a custom Font.
Ross
Could you show a snippet that shows what you've done with StyledDocument and that doesn't work?
jfpoilpret
I've added a more detailed example to the question. Hopefully that will help clear up what I'm trying to do.
Ross
+2  A: 

OK, I see the problem better now.

After checking some Swing source code, it is clear you cannot use the DefaultStyledDocument and have it use a physical font (one you created yourself with createFont) out of the box.

However, what I think you could do is implement your own StyleContext this way:

public class MyStyleContext extends javax.swing.text.StyleContext
{
    @Override public Font getFont(AttributeSet attr)
    {
        Font font = attr.getAttribute("MyFont");
        if (font != null)
            return font;
        else
            return super.getFont(attr);
    }
}

Then you have to:

  1. create a DefaultStyledDocument with a new MyStyleContext()
  2. "attach" it to the JTextPane
  3. call attributes.addAttribute("MyFont", myCustomFont); in your snippet above

I did not try it but I think it should work or it might be a good path to investigate.

jfpoilpret
Awesome, thanks! I'll give it a shot.
Ross
Worked perfectly. Code sample below. Thanks again. :)
Ross
+2  A: 

jfpoilpret's solution worked perfectly! For posterity's sake, here's a working code snippet:

    JTextPane textPane = new JTextPane();
    textPane.setStyledDocument(new DefaultStyledDocument(new StyleContext() {
        @Override
        public Font getFont(AttributeSet attr) {
            return myCustomFont;
        }
    }));
    textPane.setText(text);
    MutableAttributeSet attributes = new SimpleAttributeSet();
    StyleConstants.setForeground(attributes, Color.RED);
    textPane.getStyledDocument().setCharacterAttributes(
        text.indexOf(toHighlight),
        toHighlight.length(),
        attributes, true
    );

Thanks, jfpoilpret!

Ross
A: 

I had the same problem when writing a program in Clojure, ie. using fonts loaded from TTF in a JEditorPane displaying HTML text. The solution here worked all right - I copy the interesting part here for future reference:

(def font1 (with-open [s (FileInputStream. "SomeFont.ttf")]
             (.deriveFont (Font/createFont Font/TRUETYPE_FONT s) (float 14))))
(def font2 (Font. "SansSerif") Font/PLAIN 14)
(let [editor (JEditorPane. "text/html" "")]
  (.setDocument editor
                (proxy [HTMLDocument] []
                  (getFont [attr]
                    (if (= (.getAttribute attr StyleConstants/FontFamily)
                           "MyFont")
                      font1
                      font2)))))

This assumes that the HTML document refers to a font-family "MyFont", e.g. with a CSS snippet like

p { font-family: "MyFont" }

Note that with this you have to handle all font requests. This is because of the limitation of proxy not being able to call the member functions of the superclass. Also, if you want to handle different font sizes, you have to do that "manually", checking the StyleConstants/FontSize attribute and creating a font with deriveFont accordingly.

I hope this will help somebody :)