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.
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.
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.
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.
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 AttributeFamilyName
undPostScriptName
. - Schriftschnitt-Attribute
-
Neben den Attributen
Plain
,Bold
undItalic
fallen hierunter auch die AuszeichnungenSerif
,Monospaced
undSymbolic
. Die Schriftschnitt-Attribute enthalten Boolsche Werte. Für jedes dieser Attribute existieren daher drei Zustände:true
: Die Eigenschaft ist vorhandenfalse
: Die Eigenschaft ist nicht vorhandenAbwesenheit 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.
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:
-
Font Management: Es wird allgemein (unabhängig von einem konkreten Dokument) definiert, welche Fonts zur Auswahl stehen sollen, und welche Eigenschaften diese haben.
-
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.
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 FontSource
s 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 FontSource
s 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 FontManager
s 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 FontManager
s einführen. Die mitgelieferten
Demo-Klassen (insbesondere der FileFontAttributesCache
) zeigen mögliche
Ansätze auf.
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.
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 Reader
s.[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]
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 derconfigureFontEnvironment()
-Methode verwendet. configureFontEnvironment()
-Methode-
Der Aufruf der Methode konfiguriert die
FontFactoryReaderSettings
Klasse mit denFontFactory
Implementationen, die in derDefaultFontFactorySettingsConfigurer
Klasse bereit gestellt werden. Zudem werden die annotationspezifischenAnnotationFontFactory
Implementationen in einerChainedAnnotationFontFactory
Instanz zusammengefasst und in derAnnotations
Klasse registriert.
Klasse DefaultSwingFontEnvironments
(Swing-Appbase-Modul)
getFontManagerFuture()
-Methode-
Die Methode ruft die
getFontManagerFuture()
-Methode aus derDefaultFontEnvironments
Klasse auf und gibt dieFontManagerFuture
Instanz zurück. configureFontEnvironment()
-Methode-
Die Methode ruft die
configureFontEnvironment()
-Methode aus derDefaultFontEnvironments
Klasse auf und führt die initiale Konfiguration durch. Die annotationspezifischenSwingAnnotationFontFactory
Implementationen werden in einerChainedSwingAnnotationFontFactory
Instanz zusammengefasst und in derAnnotations
Klasse registriert. Die zuvor registrierteChainedAnnotationFontFactory
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.
[54] In den Abschnitten Beispiel 7.10, „Synchrones Erzeugen eines FontManager
s“, Beispiel 7.11, „Asynchrones Erzeugen eines FontManager
s“, Beispiel 7.12, „Erzeugen eines FontManager
s mit den Standard-14 Fonts“ und Beispiel 7.13, „Erzeugen eines FontManager
s aus verschiedenen Quellen“
finden sich Nutzungsbeispiele der Builder
API.
[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 FontManager
s 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.
[58] Beispiele zum Thema Logging werden unter Beispiel 7.16, „Generieren von Log-Meldungen via Logger
“ und Beispiel 7.17, „Generieren von Log-Meldungen mit frei definierbarem Ziel“ gegeben.
[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.
[61] Ein Beispiel dazu findet sich unter Beispiel 7.18, „Setzen der FontFactory
für einen Lesevorgang“.
[62] Zusätzlich findet sich ein Beispiel unter Beispiel 7.19, „Setzen der FontFactory
für alle Lesevorgänge mittels
ComponentConfigurer
“.