Search code examples
javajavafxemoji

Is it possible to display emoji inside JavaFX TextArea?


I need an emoji along with text editing support, but when I insert any emoji in the text area (no matter if via code or via clipboard) it doesn't display at all. I tried downloading NotoColorEmoji font and setting it for the text area, but again nothing is displayed. Moreover, this font only contains emoji, so regular characters are not displayed as well.

var url = getClass().getResource("NotoColorEmoji-Regular.ttf").toExternalForm();
var ta = new TextArea("๐Ÿ˜€๐Ÿ˜ƒ๐Ÿ˜„");
ta.setFont(Font.loadFont(url, 12));

I know about 3rd party libs, but they can't all be used for various reasons. So, please, don't recommend any of them.

Is there a way to get emoji support in standard text input control? Any workarounds?

UPDATE:

TL;DR: I think it's a JavaFX bug. Some emoji fonts fully/partially cannot be rendered. Those that do only rendered as greyscale.

Environment: Ubuntu 22.04 (KDE 5.24) or Fedora 39 (KDE 5.27), JDK/JFX 21 or JDK/JFX 22 (latest)

Out-of-the-box, both Ubuntu and Fedora use the 'Noto Color Emoji' font, which cannot be rendered by JavaFX. Most symbols are missing.

JavaFX has own font renderer engine which reads the Linux font config settings. It's easy to check by not closing the XML tag. You'll get a JavaFX warning on startup.

So I downloaded all the available emoji TTF fonts and tried them out. To change the font, you need to put the TTF files in ~/.fonts and the configuration in ~/.config/fontconfig.

 <match>
  <test name="family">
   <string>sans-serif</string>
  </test>
  <edit binding="strong" name="family" mode="prepend">
   <string>Segoe UI Emoji</string>
  </edit>
 </match>
 <match>
  <test name="family">
   <string>serif</string>
  </test>
  <edit binding="strong" name="family" mode="prepend">
   <string>Segoe UI Emoji</string>
  </edit>
 </match>
 <match>
  <test name="family">
   <string>monospace</string>
  </test>
  <edit binding="strong" name="family" mode="prepend">
   <string>Segoe UI Emoji</string>
  </edit>
 </match>
 <!-- optionally override system emoji font -->
 <match>
  <test name="family">
   <string>Noto Emoji</string>
  </test>
  <edit binding="strong" name="family" mode="prepend">
   <string>Segoe UI Emoji</string>
  </edit>
 </match>

Here is the test node:

var eta = new TextArea("""
                ๐Ÿ˜€ ๐Ÿ˜ƒ ๐Ÿ˜„ ๐Ÿ˜ ๐Ÿ˜† ๐Ÿ˜… ๐Ÿ˜‚ ๐Ÿคฃ ๐Ÿฅฒ ๐Ÿฅน ๐Ÿ˜Š ๐Ÿ˜‡ ๐Ÿ™‚ ๐Ÿ™ƒ ๐Ÿ˜‰ ๐Ÿ˜Œ ๐Ÿ˜ ๐Ÿฅฐ ๐Ÿ˜˜ ๐Ÿ˜— ๐Ÿ˜™ ๐Ÿ˜š ๐Ÿ˜‹ ๐Ÿ˜› ๐Ÿ˜ ๐Ÿ˜œ ๐Ÿคช 
                ๐Ÿคจ ๐Ÿง ๐Ÿค“ ๐Ÿ˜Ž ๐Ÿฅธ ๐Ÿคฉ ๐Ÿฅณ ๐Ÿ™‚โ€ ๐Ÿ˜ ๐Ÿ˜’ ๐Ÿ™‚โ€ ๐Ÿ˜ž ๐Ÿ˜” ๐Ÿ˜Ÿ ๐Ÿ˜• ๐Ÿ™ โ˜น๏ธ ๐Ÿ˜ฃ ๐Ÿ˜– ๐Ÿ˜ซ ๐Ÿ˜ฉ ๐Ÿฅบ ๐Ÿ˜ข ๐Ÿ˜ญ ๐Ÿ˜ฎ ๐Ÿ˜ค ๐Ÿ˜  
                ๐Ÿ˜ก ๐Ÿคฌ ๐Ÿคฏ ๐Ÿ˜ณ ๐Ÿฅต ๐Ÿฅถ ๐Ÿ˜ฑ ๐Ÿ˜จ ๐Ÿ˜ฐ ๐Ÿ˜ฅ ๐Ÿ˜“ ๐Ÿซฃ ๐Ÿค— ๐Ÿซก ๐Ÿค” ๐Ÿซข ๐Ÿคญ ๐Ÿคซ ๐Ÿคฅ ๐Ÿ˜ถ ๐Ÿ˜ถ ๐Ÿ˜ ๐Ÿ˜‘ ๐Ÿ˜ฌ ๐Ÿซจ ๐Ÿซ  ๐Ÿ™„ 
                ๐Ÿ˜ฏ ๐Ÿ˜ฆ ๐Ÿ˜ง ๐Ÿ˜ฎ ๐Ÿ˜ฒ ๐Ÿฅฑ ๐Ÿ˜ด ๐Ÿคค ๐Ÿ˜ช ๐Ÿ˜ต ๐Ÿ˜ต ๐Ÿซฅ ๐Ÿค ๐Ÿฅด ๐Ÿคข ๐Ÿคฎ ๐Ÿคง ๐Ÿ˜ท ๐Ÿค’ ๐Ÿค• ๐Ÿค‘ ๐Ÿค  ๐Ÿ˜ˆ ๐Ÿ‘ฟ ๐Ÿ‘น ๐Ÿ‘บ ๐Ÿคก 
                ๐Ÿ’ฉ ๐Ÿ‘ป ๐Ÿ’€ ๐Ÿ‘ฝ ๐Ÿ‘พ ๐Ÿค– ๐ŸŽƒ ๐Ÿ˜บ ๐Ÿ˜ธ ๐Ÿ˜น ๐Ÿ˜ป ๐Ÿ˜ผ ๐Ÿ˜ฝ ๐Ÿ™€ ๐Ÿ˜ฟ ๐Ÿ˜พ 
                """);
.root {
    -fx-font-family: "serif";
}

I also found a very similar bug for MacOS JDK-8290866, but unfortunately setting -Dprism.lcdtext=true as well as changing -fx-smoothing-type didn't help.

And the results:

enter image description here


Solution

  • An example to demonstrate current behavior and summarize comments, showing how various emoji fonts are handled (not a comprehensive cross-platform answer).

    I only ran the example app on OS X, other OS environments may exhibit different behavior.

    OS X output

    Environment

    • OS X 14.5
    • openJDK 22.0.2
    • JavaFX 22.0.2

    emojis

    Sample app

    In the example app, the fonts required are downloaded dynamically at runtime, so they don't need to be pre-downloaded and installed in the OS for you to use them. However, the drawback of this approach is that it takes a few seconds for the app to start as it gathers and initializes the fonts off of the net.

    import javafx.application.Application;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.control.TextArea;
    import javafx.scene.layout.Pane;
    import javafx.scene.layout.VBox;
    import javafx.scene.text.Font;
    import javafx.stage.Stage;
    
    import java.io.IOException;
    import java.util.List;
    
    public class EmoApp extends Application {
        private static final String CSS_DATA = "data:text/css,";
    
        private static final String CSS = CSS_DATA + """
            .root {
               -fx-font-size: 16px;
            }
            .default {}
            .noto-color-emoji {
              -fx-font-family: "Noto Color Emoji";
            }
            .open-sans-emoji {
              -fx-font-family: "OpenSansEmoji";
            }
            .segoe-emoji {
              -fx-font-family: "Segoe UI Emoji";
            }
            .apple-color-emoji {
              -fx-font-family: "Apple Color Emoji";
            }
            """;
    
        private static final String EMOJI_TEXT = """
            ๐Ÿ˜€ ๐Ÿ˜ƒ ๐Ÿ˜„ ๐Ÿ˜ ๐Ÿ˜† ๐Ÿ˜… ๐Ÿ˜‚ ๐Ÿคฃ ๐Ÿฅฒ ๐Ÿฅน ๐Ÿ˜Š ๐Ÿ˜‡ ๐Ÿ™‚ ๐Ÿ™ƒ ๐Ÿ˜‰ ๐Ÿ˜Œ ๐Ÿ˜ ๐Ÿฅฐ ๐Ÿ˜˜ ๐Ÿ˜— ๐Ÿ˜™ ๐Ÿ˜š ๐Ÿ˜‹ ๐Ÿ˜› ๐Ÿ˜ ๐Ÿ˜œ ๐Ÿคช 
            ๐Ÿคจ ๐Ÿง ๐Ÿค“ ๐Ÿ˜Ž ๐Ÿฅธ ๐Ÿคฉ ๐Ÿฅณ ๐Ÿ™‚โ€ ๐Ÿ˜ ๐Ÿ˜’ ๐Ÿ™‚โ€ ๐Ÿ˜ž ๐Ÿ˜” ๐Ÿ˜Ÿ ๐Ÿ˜• ๐Ÿ™ โ˜น๏ธ ๐Ÿ˜ฃ ๐Ÿ˜– ๐Ÿ˜ซ ๐Ÿ˜ฉ ๐Ÿฅบ ๐Ÿ˜ข ๐Ÿ˜ญ ๐Ÿ˜ฎ ๐Ÿ˜ค ๐Ÿ˜  
            ๐Ÿ˜ก ๐Ÿคฌ ๐Ÿคฏ ๐Ÿ˜ณ ๐Ÿฅต ๐Ÿฅถ ๐Ÿ˜ฑ ๐Ÿ˜จ ๐Ÿ˜ฐ ๐Ÿ˜ฅ ๐Ÿ˜“ ๐Ÿซฃ ๐Ÿค— ๐Ÿซก ๐Ÿค” ๐Ÿซข ๐Ÿคญ ๐Ÿคซ ๐Ÿคฅ ๐Ÿ˜ถ ๐Ÿ˜ถ ๐Ÿ˜ ๐Ÿ˜‘ ๐Ÿ˜ฌ ๐Ÿซจ ๐Ÿซ  ๐Ÿ™„ 
            ๐Ÿ˜ฏ ๐Ÿ˜ฆ ๐Ÿ˜ง ๐Ÿ˜ฎ ๐Ÿ˜ฒ ๐Ÿฅฑ ๐Ÿ˜ด ๐Ÿคค ๐Ÿ˜ช ๐Ÿ˜ต ๐Ÿ˜ต ๐Ÿซฅ ๐Ÿค ๐Ÿฅด ๐Ÿคข ๐Ÿคฎ ๐Ÿคง ๐Ÿ˜ท ๐Ÿค’ ๐Ÿค• ๐Ÿค‘ ๐Ÿค  ๐Ÿ˜ˆ ๐Ÿ‘ฟ ๐Ÿ‘น ๐Ÿ‘บ ๐Ÿคก 
            ๐Ÿ’ฉ ๐Ÿ‘ป ๐Ÿ’€ ๐Ÿ‘ฝ ๐Ÿ‘พ ๐Ÿค– ๐ŸŽƒ ๐Ÿ˜บ ๐Ÿ˜ธ ๐Ÿ˜น ๐Ÿ˜ป ๐Ÿ˜ผ ๐Ÿ˜ฝ ๐Ÿ™€ ๐Ÿ˜ฟ ๐Ÿ˜พ 
            """;
    
    
        private static final String NOTO_COLOR_EMOJI_CSS =
                "https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap";
    
        private static final List<String> emojiStyleClasses = List.of(
                "default",
                "noto-color-emoji",
                "open-sans-emoji",
                "segoe-emoji",
                "apple-color-emoji"
        );
    
        @Override
        public void start(Stage stage) throws IOException {
            Font.loadFont(
                    "https://github.com/MorbZ/OpenSansEmoji/raw/master/OpenSansEmoji.ttf",
                    10
            );
            Font.loadFont(
                    "https://github.com/mrbvrz/segoe-ui/raw/master/font/seguiemj.ttf",
                    10
            );
    
            VBox layout = new VBox(10);
            layout.getChildren().addAll(
                    emojiStyleClasses.stream()
                            .map(this::emojiSample)
                            .toList()
            );
            layout.setPadding(new Insets(10));
    
            Scene scene = new Scene(layout);
            scene.getStylesheets().addAll(
                    NOTO_COLOR_EMOJI_CSS,
                    CSS
            );
            stage.setScene(scene);
            stage.show();
        }
    
        public Pane emojiSample(String styleClass) {
            TextArea textArea = new TextArea(EMOJI_TEXT);
            textArea.setPrefSize(750, 160);
            textArea.getStyleClass().add(styleClass);
    
            return new VBox(5,
                    new Label(styleClass),
                    textArea
            );
        }
    }
    

    Notes

    • From my testing (on Mac), the color Emojis only work with the default Mac Apple Color Emoji font and Mac appears to fall back to use that if the appropriate glyphs aren't in the requested font, but no other color fonts will work with JavaFX (they will be rendered monochrome or not at all).

    • If I run this code

      stage.setScene(new Scene(new TextArea("\uD83D\uDE00\uD83D\uDE03\uD83D\uDE04"))); 
      

      on a Mac, it works just fine, and the emojis you have in your question display. Also copy and paste of your emojis into the text area works fine as well as editing caret positioning, selection highlighting and the delete key.

    • On Mac the Emojis display in color, with the default system font. The standard "command + ctrl + space" keyboard sequence will bring up an emoji input selection in most Mac apps, but not in JavaFX apps. So I am not sure how to use the keyboard to enter new emojis.

    • To get multicolored emoji characters, you probably need to use a color font, e.g. on Windows, you could try "Segoe UI Emoji". However, from my testing on Mac, JavaFX didn't seem to handle color fonts (except the "Apple Color Emoji" font that ships with the OS).

    • I know the asker doesn't want to use a third-party library, but for others who come across this question who do, you could consider pavlobu / emoji-text-flow-javafx, mentioned in this similar question and answer, which "allow users to use extended TextFlow to display emojis. It helps to display consistent emoji images on different platforms where JavaFX application runs."

    • I also tried it on Mac with the OpenSansEmoji font -> output was similar to that in the question, but (most glyphs rendered monochrome, but glyphs rendered garbled in the question were displayed in color, probably just falling back to the default Apple Emoji Font, which works). Similarly for Segoe UI Emoji, which renders many more black and white glyphs as shown in the question, and color default Apple Emoji Fonts for the glyphs not rendered in Segoe. Noto Color Emoji rendered blank characters on Mac.

    • So what that means to me is that JavaFX can only render the color emojis from the default Apple Emoji Font on a Mac, but not a custom color font. Probably there is some special fallback on Macs to use the Apple Emoji font if the current font doesn't have the appropriate Emoji glyphs. Still, outside of that, color glyphs aren't rendered (similar to Linux), so it is probably a cross-platform limitation of JavaFX font processing and not Linux-specific.

    FAQ

    How did you get the behavior of rendered monochrome or not at all if the Apple Color Emoji font is used as a fallback? Is there a way to prevent the fallback?

    I didn't do anything to have that fallback behavior, it just seems to happen. You can see in the result that some emoji fonts are rendering monochrome for glyphs, which is coming from the emoji font. But when the emoji font does not seem to have an appropriate glyph you see color glyphs coming from the Apple emoji font.

    The Noto Color Emoji font has all the glyphs, but they don't render with JavaFX, ending up with blank text.

    Unrelated to JavaFX, the default emoji font used by Apple (Apple Emoji Font) can be replaced in the OS, see:

    https://superuser.com/questions/968509/is-is-possible-to-change-the-emoji-set-on-os-x

    The link (which is quite old), noted that the "Noto Color Emoji" font does not work on OS X, so the failure of the font may be at the OS level, rather than directly applicable to JavaFX.