Verwalten von Fonts

Neben Raster- und Vektordaten enthalten viele Dokumente textuelle Inhalte, die mittels Fonts dargestellt werden. Verschiedene Formate bieten die Möglichkeit, Font-Datenströme innerhalb des Dokuments zu transportieren. In anderen Fällen enthält das Dokument lediglich Metadaten über den gewünschten Font. Der Font-Datenstrom selbst muss durch die ausführende Anwendung bereitgestellt werden.

Eingebettete Fonts

Um eine zuverlässige Anzeige der Dokumentinhalte zu erreichen, ist das Einbetten aller verwendeten Fonts der optimale Weg. Nachteilig ist, dass durch die zusätzlichen Daten die Dateigröße entsprechend ansteigt. Um den Anstieg zu begrenzen ist es möglich nur die relevanten Buchstaben eines Fonts einzubetten. Vorteile der Einbettung sind:

  • Reproduzierbare Anzeige unabhängig von der Ausführungsumgebung.

  • Zukunftssicherheit, insbesondere wenn Formate verwendet werden, die für die Langzeitarchivierung konzipiert sind. Ein solches Format ist PDF/A, das die Einbettung sämtlicher sichbarer Schrift vorschreibt.

  • Einfacheres Verwalten der Umgebung und somit weniger Aufwand für den Betrieb.

  • Weniger Implementierungsaufwand

Insbesondere im Umfeld der Langzeitarchivierung ist es somit empfehlenswert, nur solche Dokumente ins Archiv aufzunehmen, die sämtliche Fonts eingebettet haben. Wo dieser Ansatz verfolgt wird, ist kein weiteres Font Management notwendig. Da die Dokumentdaten alle benötigten Informationen mitbringen, kann die Anzeige durch jadice ohne weitere Maßnahmen erfolgen.

Referenzierte Fonts

Dokumente ohne eingebettete Fonts enthalten lediglich Metadaten über den zu verwendenden Font. Darunter fallen beispielsweise der Font Name und PostScript Name, Informationen zur Typ-Kategorisierung oder die Laufweiten der einzelnen Zeichen. Die vorhandenen Angaben variieren je nach Format und Dokument. Basierend auf diesen Informationen muss die anzeigende Anwendung einen passenden Font Datenstrom auffinden und zur Anzeige der Zeichen verwenden. Dieser Prozess wird Font-Substitution genannt; der ausgewählte Font nennt sich Substitut oder Ersatz-Font.

Die jadice document platform bietet feingranulare Möglichkeiten um zu definieren, welche Fonts zur Laufzeit verfügbar sind und welcher Datenstrom im Einzelfall als Substitut zur Darstellung ausgewählt wird. Für typische Anwendungsfälle existieren einfach anwendbare Default-Konfigurationen. Für weitergehende Anforderungen können dedizierte Anpassungen vorgenommen werden.

Das jadice Font Management ist unabhängig von der Font Logik der Java Virtual Machine, und geht in seinen Möglichkeiten an vielen Stellen darüber hinaus. Ein wichtiger Gesichtspunkt ist dabei die größere Unabhängigkeit vom Betriebssystem, auf dem die Anwendung läuft. Durch die Abstraktionen der Java Virtual Machine entstehen in diesem Bereich schwer nachvollziehbare und wenig beeinflussbare Zusammenhänge. Die jadice Font Management API wandelt dieses intransparente und von äußeren Einflüssen abhängige Verhalten zu einem explxiziten, steuerbaren Verhalten.

Die Definition aller in der Anwendung zur Verfügung stehenden Fonts erfolgt mittels einer jadice API innerhalb der Integration und ist damit unabhängig davon, was in der Java VM oder dem zugrundeliegenden Betriebssystem angeboten wird. Es stehen unverändert sämtliche Fonts und Ausprägungen zur Verfügung, die über die API durch die Integration definiert wurden.[53] Es findet keine unerwartete Emulation von Schriftschnitten oder intransparente Ersetzung durch Default-Fonts statt. Integrationen haben volle Anpassbarkeit und Kontrolle des Auswahl- und Ersetzungs-Prozesses der Fonts.

Der Ablageort der Font-Datenströme kann den Bedürfnissen der Integration flexibel angepasst werden. Neben lokal vorhandenen Fonts ist auch ein zentralisiertes Setup möglich. In verteilten Systemen kann dadurch beispielsweise sichergestellt werden, dass auf Client und Server dieselben Fonts zur Verfügung stehen und dieselbe Ersetzungslogik ausgeführt wird.

Die Definiton der Fonts und der Ersetzungs-Logik muss nicht zwingend statisch und global erfolgen. Mit Hilfe der API kann auf verschiedene Szenarien innerhalb der Anwendung angepasst reagiert werden. So könnte die Ersetzungslogik beispielsweise für verschiedene Fachbereiche, Dokumentarten oder -versionen unterschiedlich definiert sein. Auch die versionierte Verwaltung von Fonts, beispielsweise in einer Datenbank, wird damit ermöglicht. Passend zur jeweiligen Dokumentrevision kann über diesen Weg der zugehörige Font geladen werden.

Welche Teile der bestehenden API genutzt werden, und welche Fonts zur Verfügung stehen, liegt in der Verantwortung der jeweiligen Integration und wird von ihren spezifischen Anforderungen bestimmt. Die mitgelieferte Standard-Konfiguration deckt die gängigen Fälle ab. Da sie als Source Code mitgeliefert wird, bietet sie eine Ausgangsbasis für darüber hinaus gehende integrationsspezifische Anpassungen. Die folgenden Abschnitte diskutieren technische Zusammenhänge der API und zeigen dadurch Einsprungspunkte für solche Anpassungen auf.

Repräsentation von Fonts

Fonts werden als Datenströme persistiert. Etwas verallgemeinert wird ein solcher Datenstrom in jadice als Instanz von FontSource repräsentiert.

Ein Font Datenstrom enthält Daten, die in einem Font Format (wie beispielsweise TrueType oder Type1/PostScript) codiert sind. Standard Java Anwendungen verarbeiten Fonts über die API des Abstract Window Toolkit (AWT) und erstellen java.awt.Font Objekte aus den Datenströmen. Um die Anforderungen der oben beschriebenen referenzierten Fonts abzudecken, ist diese API nicht flexibel genug. Die jadice document platform beinhaltet daher eine eigene Font Engine. Schriftarten können darüber unabhängig von der AWT-Implementierung geladen, indexiert und verarbeitet werden. Durch die jadice document platform geladene Fonts werden durch Instanzen des Interface Font repräsentiert.

Eigenschaften von Fonts

Jeder Font verfügt über Eigenschaften, die ihn charakterisieren. Beispiele dafür sind sein Name oder PostScript Name, seine Schriftfamilie, oder Auszeichnungen des Schriftschnitts wie ›Fett‹ und ›Kursiv‹. Diese Eigenschaften können einem jadice document platform Font zugeordnet werden um ihn erkennbar zu machen. Dies gilt sowohl für die Repräsentation seines Datenstroms (FontSource) als auch für den geladenen Font.

Eine einzelne Eigenschaft wird typsicher durch eine konkrete Implementation von Attribute repräsentiert. Die Eigenschaften eines Fonts werden in einem fix zugeordneten FontAttributeSet gesammelt. Es enthält die Menge aller Attribute Objekte des Fonts, wobei von jedem Attribut-Typ höchstens eine Instanz vorkommen darf.

Für die typischerweise vorkommenden Font-Attribute existieren repräsentative Java Klassen, die im Folgenden zusammengefasst werden:

Namens-Attribute

Hierunter fällt der allgemeine FontName sowie die spezifischeren Attribute FamilyName und PostScriptName.

Schriftschnitt-Attribute

Neben den Attributen Plain, Bold und Italic fallen hierunter auch die Auszeichnungen Serif, Monospaced und Symbolic. Die Schriftschnitt-Attribute enthalten Boolsche Werte. Für jedes dieser Attribute existieren daher drei Zustände:

true: Die Eigenschaft ist vorhanden
false: Die Eigenschaft ist nicht vorhanden
Abwesenheit des Attributs im FontAttributeSet: Es wird keine Aussage zu dieser Eigenschaft getroffen.
Formatspezifische Attribute

Einzelne Formate können zusätzliche Informationen liefern, die durch spezifische Attribute Implementationen repräsentiert werden.

Die mitgelieferten Attribute verfügen über aussagekräftige toString()-Methoden, sodass Informationen über Fonts direkt ausgegeben werden können. Um eine gut lesbare API zu erhalten werden statt Konstruktoren entsprechende Fabrikmethoden angeboten, die für die Verwendung als statischer Import gedacht sind.

Ersetzen von Fonts

Für Dokumente, die keine eingebetteten Fonts mitbringen, müssen geeignete Substitute zur Laufzeit bereitgestellt werden. Das Bereitstellen von Ersatz-Fonts ist in jadice ein zweistufiger Prozess:

  1. Font Management: Es wird allgemein (unabhängig von einem konkreten Dokument) definiert, welche Fonts zur Auswahl stehen sollen, und welche Eigenschaften diese haben.

  2. Aus dieser Gesamtmenge wird für eine gegebene Font-Anfrage eines Dokuments eine konkrete Ersatzschrift gesucht und ausgewählt. Dieser Schritt nennt sich Font Substitution.

Beide Teile des Prozesses können durch Integratoren konfiguriert oder bei Bedarf in Teilen selbst implementiert werden.

Bereitstellen der verfügbaren Fonts

Um Ersatz-Fonts bereitstellen zu können müssen Font Datenströme durch die Integration zur Verfügung gestellt werden. Es ist möglich die Betriebssystem-Fonts direkt zu nutzen oder die Datenströme aus anderen Quellen zu beziehen. Um die verfügbaren Font-Datenströme zu verwalten, bietet jadice den FontManager an. Instanzen dieser Klasse halten eine Menge von FontSources vor – also Font Datenströme, die bei Bedarf geladen werden können. Zum Erstellungs-Zeitpunkt wird der FontManager mit Datenströmen befüllt und kann dafür selbstständig die zugehörigen Attribute ermitteln und FontSources erzeugen. Aus dem FontAttributeSet jeder FontSource ist ersichtlich, welcher Font aus dem Datenstrom entsteht.

Typischerweise existiert zur Laufzeit der Anwendung genau ein FontManager der beim Start der Anwendung erzeugt wird. Die Erzeugung erfolgt mit Hilfe einer Fluent API, deren Einsprungspunkt die Methode FontManagers.startBuilding(…) ist.[54] Die Erstellung des FontManagers kann synchron auf dem aktuellen Thread oder asynchron im Hintergrund über einen TaskService erfolgen. Zur späteren Identifizierbarkeit – insbesondere auch falls Probleme bei einer Initialisierung im Hintergrund auftreten sollten – wird für jeden FontManager ein Identifikationsstring festgelegt.

Instanzen von FontManager sind für die konkurrierende Verwendung konzipiert und daher nicht veränderlich. Eine Instanz steht erst dann zur Verfügung, wenn der Aufbau (gegebenenfalls im Hintergrund) vollständig abgeschlossen ist. Dies schützt vor fehlerhaften Abfragen durch unfertige Instanzen und befreit von der Last einer integrationsspezifischen Synchronisierung.

Um die Initialisierungsdauer zu verkürzen, können Integrationen einen Cache für die Inhalte des FontManagers einführen. Die mitgelieferten Demo-Klassen (insbesondere der FileFontAttributesCache) zeigen mögliche Ansätze auf.

Font Substitution

Um für das Rendering eines Dokuments eine konkrete Ersatzschrift bereitzustellen, wird das Interface FontFactory implementiert. Es erhält als Anfrage die Font Attribute, die aus dem Dokument bekannt sind. Es muss daraufhin den (geladenen) Font zurückgeben, der für das Rendering verwendet werden soll.

Im Optimalfall enthält der verwendete FontManager eine FontSource, deren FontAttributeSet exakt auf die angefragten Attribute passt. Ist dies nicht der Fall, so muss mittels einer integrationsspezifischen Auswahlstrategie ein geeignetes Substitut ermittelt werden. Eine auf diesem Wege ermittelte FontSource kann durch die Funktionalität aus FontSources geladen und somit in ein Font-Objekt umgewandelt werden.

Die FontFactory muss zwingend einen Font zurückgeben, da sonst das Rendering nicht durchgeführt werden kann. Die im Quellcode mitgelieferten AppBase Klassen enthalten verschiedene FontFactory-Implementationen, die im Zusammenspiel bekannte und gängige Fälle abdecken, sowie generische Substitute als Ausweichlösung anbieten. Im Besonderen hält die Standard14FontFactory Fonts für die gängigen Fälle Serif, Sans Serif und Monospaced jeweils Fonts in den Schriftschnitten Normal, Kursiv, Fett und Fett Kursiv bereit.[55]

Die Anfragen nach Substituten enthalten Angaben, die aus den zu lesenden Dokumenten stammen. Bei der Implementation von FontFactory Klassen ist daher zu bedenken, dass diese Angaben nicht immer vollständig und schlüssig sein müssen.[56]

Um größere Flexibilität bei der Implementation zu erreichen wird die ChainedFontFactory angeboten. Sie nimmt mehrere weitere FontFactory Instanzen entgegen und gibt die Anfrage nacheinander an diese weiter. Falls sie eine Anfrage nicht beantworten können, geben sie null zurück. Wird ein Font an die ChainedFontFactory zurückgegeben, so gibt sie diesen als Gesamtergebnis zurück. Zu beachten ist, dass die letzte FontFactory der Kette zwingend einen Font zurückliefern muss da andernfalls das Ergebnis der ChainedFontFactory null wäre. Durch diesen mehrstufigen Ansatz kann sich jede Factory Implementation auf einen spezifischen Typ von Anfragen konzentrieren. Die Umsetzung wird somit einfacher und der Code bleibt leichter überschaubar.[57]

Sowohl während der Implementation als auch zur Fehlersuche sind Log-Ausgaben oftmals hilfreich. Aus diesem Grund existiert für FontFactory-Implementationen ein vorbereiteter Logging Mechanismus, der bei Bedarf aktiviert werden kann. Um Logging zu ermöglichen, steht das Interface LoggingFontFactory zur Verfügung. Einfache FontFactory Klassen müssen es nicht zwingend selbst implementieren, da mit der DelegateLoggingFontFactory eine Default-Variante bereitsteht. Sie nimmt eine gewöhnliche FontFactory entgegen und fügt die Logging-Funktionalität hinzu. Es werden die Eingangsdaten, sowie der Rückgabewert in der Log Nachricht ausgegeben. Um Log Ausgaben initial anzufordern, muss vor die Haupt-FontFactory – typischerweise eine ChainedFontFactory – eine ActivateLoggingFontFactory geschaltet werden. Sie fordert die Log-Ausgaben an und definiert ihr Ziel – typischerweise der Standard Output Stream der Anwendung.[58]

Die bisher beschriebenen mitgelieferten FontFactory-Implementationen decken typische Fälle ab. Bei Bedarf können Erweiterungen vorgenommen oder neue Klassen implementiert werden. Da das FontFactory Interface bewusst einfach gehalten wurde, sind auch grundlegend andere Umsetzungen möglich. So könnte beispielsweise das Finden von Substituten in einer (lokalen) Datenbank erfolgen.[59]

Bei Änderungen und alternativen Ansätzen ist zu beachten, dass die FontFactory angefragt wird während ein Ladevorgang läuft. Der Code ist somit zeitkritisch. Jegliche Zeit, die für die Suche nach Substituten investiert wird, verlängert den Lade- und Rendering-Vorgang dementsprechend. Dadurch kann sich die Anwendung für Nutzer zäh anfühlen.

Bekanntmachen der Ersetzungslogik

Um während eines Ladevorgangs Ersatz-Fonts ermitteln zu können, muss eine FontFactory zur Vefügung stehen. Diese wird über die gültigen ReaderControls in einer Instanz von FontFactoryReaderSettings transportiert.[60] Die ReaderControls eines Lesevorgangs werden von dessen Reader verwaltet und können abgefragt werden. Änderungen gelten für alle nachfolgenden Lesevorgänge des Readers.[61]

Oftmals kann dieselbe FontFactory für alle Ladevorgänge genutzt werden. In diesem Fall kann die FontFactory als Default festgelegt und auf die manuelle Erstellung von FontFactoryReaderSettings verzichtet werden. Die Default FontFactory wird auf jeder neuen Instanz von FontFactoryReaderSettings gesetzt ohne dass ein weiterer Eingriff notwendig ist. Mit der Klasse FontFactorySettingsConfigurer zeigen die jadice Demo-Integrationen wie ein solcher Default gesetzt werden kann. Der Configurer muss mit Hilfe der Registrierungs-Methoden aus Jadice einmalig der Konfigurations-Logik bekannt gemacht werden.[62]

API Standardkonfiguration für Fonts

Folgende Klassen bieten eine Konfiguration für die mitgelieferten Standard-14 Fonts an. Die Klassen sind Teil der offiziellen API und können für die Integration verwendet werden.

Klasse DefaultFontEnvironments (Core-Appbase-Modul)

getFontManagerFuture()-Methode

Der Aufruf der Methode stellt eine FontManagerFuture Instanz (synchron ausgeführt) zur Verfügung, die mit Standard-14 Fonts konfiguriert ist. Diese Instanz wird in der configureFontEnvironment()-Methode verwendet.

configureFontEnvironment()-Methode

Der Aufruf der Methode konfiguriert die FontFactoryReaderSettings Klasse mit den FontFactory Implementationen, die in der DefaultFontFactorySettingsConfigurer Klasse bereit gestellt werden. Zudem werden die annotationspezifischen AnnotationFontFactory Implementationen in einer ChainedAnnotationFontFactory Instanz zusammengefasst und in der Annotations Klasse registriert.

Klasse DefaultSwingFontEnvironments (Swing-Appbase-Modul)

getFontManagerFuture()-Methode

Die Methode ruft die getFontManagerFuture()-Methode aus der DefaultFontEnvironments Klasse auf und gibt die FontManagerFuture Instanz zurück.

configureFontEnvironment()-Methode

Die Methode ruft die configureFontEnvironment()-Methode aus der DefaultFontEnvironments Klasse auf und führt die initiale Konfiguration durch. Die annotationspezifischen SwingAnnotationFontFactory Implementationen werden in einer ChainedSwingAnnotationFontFactory Instanz zusammengefasst und in der Annotations Klasse registriert. Die zuvor registrierte ChainedAnnotationFontFactory Instanz wird überschrieben.



[53] Dies gilt auch für unerwartete, falsche oder fehlende Angaben innerhalb eines Fonts. Es wird nicht versucht, Werte automatisch zu verändern oder aus anderen Angaben abzuleiten. Alle Werte werden so übernommen, wie sie im Font Datenstrom definiert sind. Sollte die Veränderung von Font-Eigenschaften in Einzelfällen notwendig sein um bestimmte Ziele zu erreichen, können solche Anpassungen im Rahmen einer Integration für konkret bekannte Fälle und unter kontrollierten Bedingungen vorgenommen werden.

[55] Manche Dateiformate, wie beispielsweise PDF, erwarten von lesenden Anwendungen explizit dass gewisse Fonts zur Verfügung stehen. Da es sich um 14 Schriften beziehungsweise Schriftschnitte handelt, werden sie Standard-14 Fonts genannt. Die Originalschriften stehen unter einer kommerziellen Lizenz – es existieren aber ähnliche Alternativen aus dem Open Source Bereich. Im Lieferumfang der jadice document platform befindet sich ein JAR, das solche Alternativen enthält. Wenn es sich auf dem Klassenpfad befindet, können die Fonts mit Hilfe der FontFactory geladen werden. In diesem Fall beantwortet die Standard14FontFactory Anfragen nach den ursprünglichen Standard-14 Fonts, indem sie den passenden Alternativ-Font zurückgibt. Das Beispiel unter Beispiel 7.12, „Erzeugen eines FontManagers mit den Standard-14 Fonts“ zeigt die Verwendung der Standard14FontFactory.

[56] Dies ist insbesondere dann zu erwarten, wenn die zu lesenden Dokumente aus vielen verschiedenen Quellen stammen. Die jeweiligen Generatoren werden unterschiedliche Angaben ablegen oder aufgrund von Fehlern sogar irreführende Werte eintragen. Es muss daher eine geeignete Heuristik entwickelt werden, die für die typischen Dokumente einer Integration geeignete Resultate liefert. Im Zweifelsfall sollte der Rückgriff auf typische Standard-Fonts erfolgen.

Falls in einer Integration klar ist, welche Dokumente zu erwarten sind und welche Angaben sie machen, kann die Implementation entsprechend einfacher ausfallen. Doch auch in diesen Fällen sollte ein generischer Font für unerwartete Fälle angeboten werden.

[57] Ein Beispiel zur Verwendung der ChainedFontFactory liegt unter Beispiel 7.15, „Verwendung der ChainedFontFactory vor.

[59] Unter Beispiel 7.14, „Typisches Pattern zur Implementation einer FontFactory wird das typische Pattern zur Implementation einer FontFactory gezeigt.

[60] Reader Controls sind eine spezielle Ausprägung von ProcessingControls. Das Konzept wird unter „Processing Controls und Settings“ diskutiert.

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