Load Custom Fonts in JavaFX + Clojure


Question

As background, I realize there are about 5 other posts on Stack Overflow about this, bur I've looked at the responses and researched this for countless hours with no real solution. Otherwise I wouldn't have posted here.

I'm new to JavaFX, and I like Clojure, so I'm using chrisx's clj-javafx project from Github as a Clojure wrapper for JavaFX. Most operations work great, such as placing components on the scene/stage and styling them with CSS. There's just one issue: I want to import a custom font, and so far I haven't been able to.

I've tried multiple methods. The Java version of the first thing I tried is:

Font.loadFont(<myClass>.class.getResource("mpsesb.ttf").toExternalForm(), 14);

I tried to do that in Clojure using Java interop but I'm not sure how Clojure handles Java classes. Instead I used clojure.java.io.resource:

(use '[clojure.java.io :only [resource]])
(resource "mpsesb.tff")

And it prints the URL of the .ttf file just fine. Then I tried to use that URL and use it as a parameter in the Font.loadFont method, which didn't work. After playing around with seriously 100 permutations of dot operators and Java methods and classes, I got the following to not print any errors, unlike the other combinations I tried:

(Font/loadFont "fonts/ttf/mpsesb.ttf")

But it just returns nil, which according to the JavaFX API means the font didn't actually load. I even tried fake pathnames and they also returned nil without errors, so the Font/loadFont function seems less promising than I thought.

I finally tried using CSS to load the font instead of using Java to import it:

@font-face {
    font-family: 'MyrProSemiExtBold';
    src: url('file:/Users/<restOfPath>/mpsesb.ttf');
}
.label {
    -fx-font-size: 14px;
    -fx-font-weight: normal;
    -fx-font-family: 'MyrProSemiExtBold';
}

But no luck. The labels just show up as the default font (Lucida Grande) at 14 pt.

Thank you ahead of time for any suggestions you may have.

EDIT:

Thanks, @jewelsea. The first thing I did after reading what you wrote is get Java 8 + JDK 8 (build b101). I'm using Sublime Text, so I got SublimeREPL to recognize JavaFX using Maven like so in Terminal:

$ mvn deploy:deploy-file -DgroupId=local.oracle -DartifactId=javafxrt -Dversion=8.0 -Dpackaging=jar -Dfile=/System/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/l‌​ib/ext/jfxrt.jar -Durl=file:/Users/vaniificat/.m2/repository

Most of the JavaFX-referent code worked, but now the whole javafx.scene.control package doesn't work, which means no Labels, no Buttons, no TextFields, etc.... :

> (import javafx.scene.control.Button)
: NoClassDefFoundError Could not initialize class javafx.scene.control.Button
  java.lang.Class.forName0 (Class.java:-2)
> (import javafx.scene.control.Label)
: NoClassDefFoundError javafx.scene.control.Labeled
  java.lang.Class.forName0 (Class.java:-2)
> (import javafx.scene.control.TextField)
: NoClassDefFoundError javafx.scene.control.Control
  java.lang.Class.forName0 (Class.java:-2)
> (import '(javafx.scene.control Button Label PasswordField TextField))
: NoClassDefFoundError Could not initialize class javafx.scene.control.Button  
  java.lang.Class.forName0 (Class.java:-2)

A possible reference to these errors may be found at https://forums.oracle.com/thread/2526150. Basically the gist is that JavaFX 8 is only in beta so we can't expect it to work 100%. Hmm!

I opened an issue on JIRA: https://bugs.openjdk.java.net/browse/JDK-8093894. Once this gets addressed I can more fully explore @jewelsea 's other suggestions.

1
2
10/28/2016 10:30:58 AM

Update: As was pointed out, the original answer did not directly answer the OP's question. I've created a blog post about the original technique in case anyone is interested.

Here is a short program that does what you want.

(ns demo.core
  (:gen-class
   :extends javafx.application.Application)
  (:import
   [javafx.application Application]
   [javafx.event EventHandler]
   [javafx.scene Scene]
   [javafx.scene.control Button]
   [javafx.scene.layout StackPane]
   [javafx.scene.text Font])
  (:require [clojure.java.io :as jio]))

(defn- get-font-from-resource
  "Load the named font from a resource."
  [font-name]
  (let [prefix "demo/resources/"
        url (jio/resource (str prefix font-name))
        fnt (Font/loadFont (.toExternalForm url) 20.0)]
    fnt))

(defn -start
  "Build the application interface and start it up."
  [this stage]
  (let [root (StackPane.)
        scene (Scene. root 600 400)
        fnt (get-font-from-resource "ITCBLKAD.TTF")
        btn (Button. "Press Me!")]

     (.setOnAction btn
                   (reify EventHandler
                     (handle [this event]
                       (doto btn
                         (.setText (str "This is " (.getName fnt)))
                         (.setFont fnt)))))

    (.add (.getChildren root) btn)

    (doto stage
      (.setTitle "Font Loading Demo")
      (.setScene scene)
      (.show))))

(defn -main
  [& args]
  (Application/launch demo.core args))

In this project, I placed the font file in resources, a sub-directory of demo - where the Clojure source is stored - hence the "prefix" in the function get-font-from-resource.

It looks like the problem you might have been having with loadFont was in your conversion from the URL to the String form. The external form is an absolute path starting at the root directory on the drive.

Tip: You probably know this, but one thing that continually screws me up are methods in JavaFX that require double parameters, like Font/loadFont. I'm used to Java just promoting integer arguments to double. In Clojure, if you use an integer where a double is required, the program fails with a less than useful error message.

2
8/10/2013 6:26:28 PM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon