Beispiele zur Verwaltung von Schriften

Beispiel 7.10. Synchrones Erzeugen eines FontManagers

Dieses Beispiel zeigt die Erstellung einer FontManager Instanz. Die ID ist ein frei wählbarer eindeutiger String, der zur späteren Identifikation (beispielsweise in Log-Ausgaben) dient. Die Erzeugung erfolgt synchron auf dem aktuellen Thread.

String id = "any unique identifier";
FontManagerFuture future = FontManagers.startBuilding(id)
        .synchronously()
        .withSystemFonts()
        .finish();
FontManager fontManager = future.get();

1

Da die Erzeugung synchron auf dem aktuellen Thread erfolgte, kehrt dieser Aufruf sofort zurück.


Beispiel 7.11. Asynchrones Erzeugen eines FontManagers

Ähnlich zum vorhergehenden Beispiel wird hier ebenfalls ein FontManager erzeugt. Inhalt und ID sind identisch. Die Erzeugung erfolgt jedoch asynchron im Hintergrund.

String id = "any unique identifier";
FontManagerFuture future = FontManagers.startBuilding(id)
        .asynchronously()
        .withSystemFonts()
        .finish();
…                                                                              (1)
FontManager fontManager = future.get();

1

Während der FontManager im Hintergrund erzeugt wird, kann auf dem aktuellen Thread weiterer Code ausgeführt werden.

2

Der Aufruf kehrt erst zurück, wenn die Erzeugung im Hintergrund vollständig abgeschlossen ist.


Beispiel 7.12. Erzeugen eines FontManagers mit den Standard-14 Fonts

Das folgende Beispiel zeigt die Erzeugung eines FontManagers, der Open Source Alternativen für die allgemein gebräuchlichen Standard-14 Fonts enthält. Die jar-Datei welche die Font-Dateien beinhaltet, muss dabei auf dem Klassenpfad liegen.

FontManagers.startBuilding("Standard-14 Font Manager")
        .asynchronously()
        .withStandard14()
        .finish();

Beispiel 7.13. Erzeugen eines FontManagers aus verschiedenen Quellen

Der folgende Quelltext zeigt einige Möglichkeiten zur Definition der Inhalte eines FontManagers. Die zu verwendeten Font-Dateien können auf verschiedene Wege bekannt gemacht werden.

FontManagers.startBuilding("big Font Manager")
        .asynchronously()
        .withStandard14()
        .withSystemFonts()
        .with(Paths.get("path/to/local/fonts"))
        .with(new File("additional/local/fonts"))
        .with(new FileInputStream("some/folder/arial.ttf"))
        .finish();

Beispiel 7.14. Typisches Pattern zur Implementation einer FontFactory

Das Beispiel zeigt das Grundgerüst einer typischen FontFactory Realisierung. Die asynchrone Erzeugung des FontManagers sollte generell möglichst frühzeitig im Programmablauf angestoßen werden. Der Einfachheit halber wird sie hier im Konstruktor dargestellt. Alternativ könnte das FontManagerFuture dem Konstruktor von außen übergeben werden. Da der Aufbau des FontManagers im Hintergrund läuft, kann parallel dazu die weitere Initialisierung der Anwendung durchgeführt werden.

Der Abruf des FontManagers vom FontManagerFuture erfolgt zum spätestmöglichen Zeitpunkt – also dann, wenn ein Font benötigt wird damit ein Dokument geladen werden kann. Falls die asynchrone Erzeugung des FontManagers zu diesem Zeitpunkt bereits abgeschlossen ist, entsteht keine zusätzliche Wartezeit. Andernfalls pausiert der Dokument-Ladevorgang bis der FontManager zur Verfügung steht. In diesem Fall kann es zu einmaligen, initialen Verzögerungen in der Dokumentanzeige kommen.

public class ExampleFontFactory implements FontFactory {
  final FontManagerFuture fontManagerFuture;

  public ExampleFontFactory() {
    fontManagerFuture = FontManagers.startBuilding("example font manager")
        .asynchronously()
        .withStandard14()
        .finish();
  }

  @Override
  public Font create(FontAttributeSet fontAttributeSet, Map<String, Object> scope) {
    Font resultingFont;
    try {
      final FontManager fontManager = fontManagerFuture.get();
      resultingFont = findTheBestFont(fontManager, fontAttributeSet, scope);
    } catch (InterruptedException | ExecutionException e) {
      resultingFont = new BasicFontFactory().create(fontAttributeSet, scope);
    }
    return resultingFont;
  }

  private Font findTheBestFont(FontManager fontManager, 
      FontAttributeSet fontAttributeSet,
      Map<String, Object> scope) {
    …                                                                          (1)
  }
}

1

Implementations-spezifische Logik um einen geeigneten Font zu ermitteln.


Beispiel 7.15. Verwendung der ChainedFontFactory

Die ChainedFontFactory ist ein Hilfsmittel zur Trennung von Zuständigkeiten in FontFactory-Implementationen. Der folgende Quelltext zeigt die Erstellung einer ChainedFontFactory, die im Nachgang für Dokument-Ladevorgänge nutzbar ist.

Die ChainedFontFactory nimmt in ihrem Konstruktor weitere FontFactory-Instanzen entgegen. Sie reagiert auf Font-Anfragen, indem sie diese nacheinander an die übergebenen Instanzen delegiert. Sobald ein Wert ungleich null vorliegt, wird dieser als Ergebnis der ChainedFontFactory zurückgegeben. Die verbleibenden Instanzen werden nicht mehr befragt.

Da die FontFactory, die für einen Ladevorgang registriert ist, zwingend einen validen Font zurückgeben muss, sollte dies durch die übergebenen Instanzen sichergestellt werden. Die ChainedFontFactory selbst nimmt keine Validierung vor. Dies bedeutet, dass die angefragten Instanzen null zurückgeben dürfen um damit zu signalisieren, dass weitere Instanzen befragt werden sollen. Die letzte FontFactory muss jedoch zwingend einen Font zurückgeben damit das Gesamtergebnis nicht null wird. Im Beispiel kommt die BasicFontFactory zum Einsatz, die unabhängig von der Anfrage stets einen Fallback-Font zurückliefert.

FontManagerFuture fontManagerFuture = …;
FontFactory fontFactory = new ChainedFontFactory(
    new FuzzyStandard14FontFactory(fontManagerFuture),
    new FuzzyStyleFontFactory(fontManagerFuture),
    new BasicFontFactory()                                                     (1)
);

1

Die BasicFontFactory gibt immer einen Font zurück. Da die FontFactory, die für den Lesevorgang registriert ist, immer einen Font zurückgeben muss, empfiehlt es sich eine Chain zu verwenden, in der die BasicFontFactory als letzte Instanz registriert ist.


Beispiel 7.16. Generieren von Log-Meldungen via Logger

Zur Entwicklungszeit und im Fehlerfall ist es hilfreich, Informationen darüber zu erhalten, welche Entscheidungen im Font-Auswahlprozess getroffen werden. Insbesondere wenn eine ChainedFontFactory zum Einsatz kommt, steigt die Gesamtkomplexität durch das Zusammenspiel der verschiedenen Klassen. Das Beispiel zeigt, wie Log-Ausgaben bei Bedarf angefordert werden können. Falls kein Logging aktiviert ist, wird der Logging-Code nie durchlaufen und es entsteht somit kein unnötiger Mehraufwand.

FontFactory fontFactory = …;

Logger logger = LoggerFactory.getLogger(getClass());
if (logger.isDebugEnabled()) {
  fontFactory = new ActivateLoggingFontFactory(fontFactory, logger);           (1)
}

1

Falls der verwendete Logger mindestens auf Level DEBUG steht, wird das Logging von Anfragen an die FontFactory aktiviert. Die ActivateLoggingFontFactory umhüllt eine andere FontFactory und initialisiert das Generieren von Log-Meldungen.


Beispiel 7.17. Generieren von Log-Meldungen mit frei definierbarem Ziel

Ähnlich wie im vorhergehenden Beispiel werden auch hier Log-Meldungen generiert. Zusätzlich wird die Möglichkeit genutzt, das Ziel der Log-Strings selbst zu bestimmen. Es muss nicht zwingend ein Logger verwendet werden. Stattdessen kommt hier der Standard Error Stream zum Einsatz. Auch Logging in eine Datenbank oder ein anderes entferntes System wäre möglich. Da der Code während des Ladevorgangs ausgeführt wird, muss dafür gesorgt werden, dass möglichst wenig Zeit investiert wird. Langlaufende Operationen, wie beispielsweise eine Netzwerk-Kommunikation, müssen auf einen anderen Thread ausgelagert werden.

FontFactory fontFactory = …;

LogSink systemErrLogSink = new LogSink() {                                     (1)
  @Override
  public void log(String logMessage) {
    System.err.println(logMessage);
  }
};

String loggingFontFactoryID = "My Logging Font Factory";                       (2)
final ActivateLoggingFontFactory activateLoggingFontFactory = 
    new ActivateLoggingFontFactory(fontFactory, 
        systemErrLogSink, 
        loggingFontFactoryID);

1

Ziel der Log-Ausgaben. Die anonyme Klasse leitet alle Ausgaben nach System.err weiter, könnte aber auch andere Ziele ansprechen.

2

Optionaler, frei wählbarer Identifikations-String. Insbesondere falls mehrere FontFactory Instanzen existieren, können sie durch diese ID in den Log-Ausgaben identifiziert werden.

3

Umhüllen der eigentlichen FontFactory um Log-Ausgaben zu aktivieren. Sämtliche Aufrufe, die über die ActivateLoggingFontFactory laufen, generieren automatisch Log-Meldungen.


Beispiel 7.18. Setzen der FontFactory für einen Lesevorgang

Das Beispiel zeigt, wie eine FontFactory für alle folgenden Lesevorgänge einer Reader-Instanz gesetzt werden kann. Für andere Reader-Instanzen muss die unabhängig davon erneut erfolgen.

FontFactory fontFactory = …;

Reader reader = …;
FontFactoryReaderSettings settings = 
    reader.getSettings(FontFactoryReaderSettings.class);
settings.setFontFactory(fontFactory);

reader.read(…);

1

Die veränderten Settings sind für alle weiteren read(…)-Aufrufe gültig.


Beispiel 7.19. Setzen der FontFactory für alle Lesevorgänge mittels ComponentConfigurer

Um nicht jede Reader-Instanz einzeln konfigurieren zu müssen, existiert ein allgemeingültiger Konfigurationsweg. Dazu wird ein ComponentConfigurer definiert, der vor der ersten Erzeugung einer Reader-Instanz registriert sein muss. Die Registrierung erfolgt global über die Methoden der Klasse Jadice. Als JUnit Test formuliert zeigt das Beispiel, dass der ComponentConfigurer für jede Instanz von FontFactoryReaderSettings, die über die Methoden der Klasse Jadice erzeugt wird, ausgeführt wird. Da der Reader seine Settings-Objekte auf diesem Weg erzeugt, ist die FontFactory-Instanz für jeden Lesevorgang gesetzt.

@Test
public void testThat_configurer_configuresFontFactoryReaderSettings() {
  final FontFactory fontFactory = new BasicFontFactory();

  ComponentConfigurer<FontFactoryReaderSettings> configurer =
      new ComponentConfigurer<FontFactoryReaderSettings>() {
        @Override
        public void configure(FontFactoryReaderSettings settings) {
          settings.setFontFactory(fontFactory);
        }
      };                                                                       (1)

  Jadice.registerComponentConfigurer(configurer, 
      FontFactoryReaderSettings.class);                                        (2)
  
  FontFactoryReaderSettings settings1 = 
      Jadice.create(FontFactoryReaderSettings.class);
  assertThat(settings1.getFontFactory(), is(sameInstance(fontFactory)));
  
  Reader reader = new Reader();
  FontFactoryReaderSettings settings2 = 
      reader.getSettings(FontFactoryReaderSettings.class);
  assertThat(settings2.getFontFactory(), is(sameInstance(fontFactory)));
}

1

Configurer als anonyme Klasse, die auf jeder Instanz von FontFactoryReaderSettings dieselbe FontFactory setzt.

2

Globales Registrieren des Configurers. Er wird nun für jede Instanz aufgerufen, die über den Jadice.create(…) Mechanismus erzeugt wird. Dies betrifft im Besonderen die FontFactoryReaderSettings, die von Reader-Instanzen automatisch erzeugt werden.


Beispiel 7.20. Beispielkonfiguration für Fonts

Eine beispielhafte Komplett-Konfiguration für das Laden von Fonts wird in den Klassen FontEnvironments SwingFontEnvironments bereitgestellt.

Für die Integration des Konfigurationsbeispiels genügt ein Aufruf der Konfiguration zu Beginn der Anwendung:

FontEnvironments.configureFontEnvironment();

Sollten zudem noch GUI-Komponenten verwendet werden kann man die Beispielkonfiguration wie folgt übernehmen:

SwingFontEnvironments.configureFontEnvironment();

[jadice document platform Version 5.5.12.1: Dokumentation für Entwickler. Veröffentlicht: 2021-08-17]