Package limn.graphics

Class Fonts

java.lang.Object
limn.graphics.Fonts

public final class Fonts extends Object
Process-wide font configuration (mirrors the TextRulers / Ui facade lifecycle). Holds the installed FontCatalog (the list of usable families) and the current default family that Font.DEFAULT_FAMILY resolves to, so an application can switch its UI font at runtime.

Letting the user choose the font takes three calls: available() for the list, rebuilt from observeChanges(java.lang.Runnable) because the list grows when the operating system's families have been found, and setDefaultFamilyWhenResident(java.lang.String) for the choice, which reads the family's regular and bold faces on a worker and switches once they are in memory, so the frame that shows the new font reads no file for them. setDefaultFamily(java.lang.String) switches at once and leaves that read to the next frame. preload(java.lang.String) reads a family ahead without switching to it.

Both the catalog becoming available (system-font enumeration finishes) and a default-family change notify listeners on the calling thread; scenes subscribe to re-layout, and the backend's font store subscribes to drop its resolution cache. Mutators are meant to be called on the UI thread.

  • Method Details

    • installCatalog

      public static void installCatalog(@Nullable FontCatalog newCatalog)
      Installs the backend's catalog (e.g. once system-font enumeration completes).
    • uninstallCatalog

      public static void uninstallCatalog(FontCatalog current)
      Resets to FontCatalog.EMPTY (backend shutdown).
    • installLoader

      public static void installLoader(@Nullable FontLoader newLoader)
      Installs the backend's file loader (backend startup).
    • uninstallLoader

      public static void uninstallLoader(FontLoader current)
      Resets to FontLoader.UNAVAILABLE (backend shutdown). A setDefaultFamilyWhenResident(java.lang.String) still waiting on that loader's read is forgotten: its backend will not deliver it, and it switches nothing.
    • load

      public static String load(Path file)
      Registers a font file the application ships and returns the family name it declares, so the family can then be named in a Font or in setDefaultFamily(java.lang.String).

      UI thread. Synchronous, and deliberately so; there is no asynchronous form. What this reads is the file's name table, which is kilobytes: the glyph outlines are parsed on first use, exactly as they are for an operating-system face, so the large read this facade could offload is not the read it performs. Offloading it would also defeat the point of the call, because the family has to be registered before the caller builds the first widget that names it; an asynchronous form would hand back a name that resolves to the default face until some later frame, which is the silent-wrong-font failure this API exists to remove. The parse of the faces themselves can leave the frame that first draws them: preload(java.lang.String) the family it returns.

      A relative path resolves against the process working directory. That makes the call dependent on where the process was started, so an application that ships a typeface should resolve its own path from something it controls rather than assuming a directory.

      Throws:
      UnsupportedOperationException - if no backend is installed (nothing here can parse a font file; see FontLoader)
      MalformedContentException - if the file carries no usable face
    • preload

      public static CompletionStage<Void> preload(String family)
      Makes family's regular and bold faces resident without reading a font file on the UI thread, so that the first measure or paint in that family finds them in memory instead of reading them inside its frame. It does not switch to the family; see setDefaultFamilyWhenResident(java.lang.String) for that.

      UI thread. The backend reads on its worker pool, and the returned stage completes on the UI thread, on a later turn and never inside this call, once the faces are resident. It completes the same way when there is nothing to read: a family the backend carries itself (Font.DEFAULT_FAMILY among them), one already resident, one the operating system does not have, a blank name, and any family when no backend has installed a font loader, as under a headless test's runtime. It never completes exceptionally: a face that cannot be read is logged and left out, and the family then resolves as it would have without this call.

      Asking for a family whose read is already under way shares that read, and a read once started is not withdrawn: what it produces joins the faces already resident, where it is worth the same to whoever names the family next. Italic faces are not read ahead, because regular and bold are what a UI draws on the frame after a switch. A backend keeps a bounded number of faces resident, so preload a family shortly before it is wanted, not at startup for every family a list could offer.

      If the backend shuts down first, the stage does not complete: there is no UI thread left to complete it on.

      Parameters:
      family - the family to read ahead, matched as a Font matches it
      Returns:
      a stage completed on the UI thread once the faces are resident
      Throws:
      IllegalStateException - off the UI thread, or when no UiRuntime is installed
    • available

      public static List<String> available()
      Returns:
      the available families (bundled + system), or empty when headless. Reads no file and does not block. It is the installed catalog's current answer, which may be a partial one: a backend that enumerates the operating system does so in the background and installs a fuller catalog when it finishes. Rebuild a family list from a change listener rather than reading this once at startup and trusting it.
    • defaultFamily

      public static String defaultFamily()
      Returns:
      the family Font.DEFAULT_FAMILY currently resolves to
    • setDefaultFamily

      public static void setDefaultFamily(@Nullable String family)
      Sets the family the default UI font resolves to; null/blank restores Font.DEFAULT_FAMILY. No-op if unchanged. Notifies listeners so the UI re-lays-out in the new font. UI thread.

      A switch that setDefaultFamilyWhenResident(java.lang.String) is still waiting to make is abandoned by this call, whether or not the name changes: the later choice wins.

      This call itself reads nothing: it stores a name and runs the listeners. The cost it schedules is the part worth knowing. A backend resolves families to font files lazily and drops its resolution cache when this fires, so the next measure or paint resolves the new family, and if its faces are not already resident, that resolution reads and parses a font file (tens of megabytes for a CJK face) on the UI thread, inside that frame.

      setDefaultFamilyWhenResident(java.lang.String) is the form that keeps that read out of the frame, and the one a font picker or a settings screen calls: it reads the faces on a worker and makes this same switch once they are resident. This form is for a switch that has to have happened when the call returns: before the first window opens, or in a test or a capture that draws next. Called from a menu or a settings dialog, its one long frame reads as the switch happening; never call it from an animation, a drag, or anything that must stay at frame rate. It costs once per face: switching back to a family already read is cheap, and so is switching to one preload(java.lang.String) has made resident.

    • setDefaultFamilyWhenResident

      public static CompletionStage<Boolean> setDefaultFamilyWhenResident(@Nullable String family)
      Sets the family the default UI font resolves to once its faces are resident: reads them on a worker as preload(java.lang.String) does, then makes the switch setDefaultFamily(java.lang.String) makes, on the UI thread, so the frame that shows the new font reads no file for its regular or bold text; an italic face, not read ahead, is still read where it is first drawn. null/blank restores Font.DEFAULT_FAMILY. The call a font picker and a settings screen make.

      UI thread. Nothing changes inside this call: until the faces are resident, text stays in the family it was in. The returned stage completes on the UI thread, on a later turn and never inside this call, with true once family is the default family, which is right after the switch has notified listeners (they are not notified when it already was), or with false when a later setDefaultFamily or setDefaultFamilyWhenResident came first and this one switched nothing.

      The later choice wins. A font picker moving through a list asks once per entry, and only the last request switches: an earlier one whose read lands afterwards leaves the default family where the later call put it, or is about to, and answers false. The overtaken request is withdrawn from the backend, whose read cannot be called back once started, and what that read produces disturbs nothing: it lays nothing out again, and the backend keeps it only in room it can make without letting go of a face in use, so stepping back to that family is cheap while the backend has room for it, and costs another read off the UI thread when it had none. Asking again for the family a pending call names replaces that call too: the first stage answers false and the second true, and the backend reads the faces once for both.

      A family the operating system does not have is switched to as setDefaultFamily would switch to it, and then resolves to the toolkit's embedded face. If the backend shuts down before the faces are resident, nothing switches and the stage does not complete.

      There is nothing to start: the name does not end in Async because the toolkit keeps that suffix for an unstarted Work. This call has started when it returns and answers with a stage, as a dialog's show does, and what withdraws it is the next choice rather than a cancel.

      Parameters:
      family - the family to switch to, or null/blank for Font.DEFAULT_FAMILY
      Returns:
      a stage completed on the UI thread with whether this call's family became the default family
      Throws:
      IllegalStateException - off the UI thread, or when no UiRuntime is installed
    • observeChanges

      public static Subscription observeChanges(Runnable listener)
      Subscribes to catalog and default-family changes.
      Parameters:
      listener - what to run when either moves
      Returns:
      a handle that unsubscribes; cancelling it twice is a no-op. UI thread