3
votes

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.

2
With Java 8, JavaFX classes are on the standard default classpath, so they should not be imported into a maven repository. I don't know how SublimeREPL works, but I would think that as long it is launched using a Java 8 JRE, that it would be able to find JavaFX classes automatically. The forum thread you link is unrelated to the class not found issues you are experiencing. Java8 is early access but by and large mostly functional - basic stuff like creating buttons is no problem. You may want to create a separate StackOverflow question on getting JavaFX 8 to work with Clojure.jewelsea

2 Answers

3
votes

These are mostly suggestions rather than definitive solutions

On @font-face

Use Java 8 if you can. It has support for the @font-face css notation (which earlier versions of JavaFX such as 2.2 do not). It also has a revamped font system which might be a bit more robust. Because the font face can be loaded via css in Java 8, you may not need to solve your loading resources relative to the class location issue.

On Font.loadFont

You should be able to use the Font.loadFont method as you are trying to use it. JavaFX 2.x has supported custom fonts from it's initial release. There are some steps for loading fonts in JavaFX in plain Java: How to embed .ttf fonts is JavaFx 2.2?. I know you have already looked at that and it hasn't helped - just including it here for reference so that somebody who really knows clojure but not JavaFX may help to troublehoot the issue.

Check that your target font is compatible

Double-check that the particular font that you are using loads and is used in a regular Java based JavaFX application (just to ensure that there is not something incompatible between the font file and the JavaFX system on your platform).

You may need further help

I don't know how loading relative resources works in clojure either (or maybe there is some other issue with your code) - but perhaps a clojure expert can post another answer that allows it to work. You may want to edit your question to include an sscce.

3
votes

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.