Neues vom Document: Pages, DocumentLayers und PageSegments

Falls Sie beim Lesen des Artikels Das jadice Document & Co. ein gewisses 'cliffhanger'-Gefühl beschlichen haben sollte, so war dies sicher nicht ganz unberechtigt, haben wir doch großspurig das 'Document & Co.' angekündigt, aber von den Compagnons bisher wenig gesehen. Diesen Missstand wird die heutige Folge hoffentlich beseitigen.

Seiten (Pages)

So wie das Document im Wesentlichen eine Sammlung von Seiten verkörpert, ist auch die Page zunächst primär ein Behälter: die Seite selbst hat sehr wenig "eigene" Eigenschaften. Das Wesentliche, den Seiteninhalt, tragen die PageSegments bei. Dennoch bot uns die Page genügend Potenzial, ihre API in der Version 5 gründlich aufzuräumen.

  • War die Page in der Version 4 noch eine Klasse, ist sie nun ein Interface. Die Basisimplementierung heißt, sicher nicht überraschend, BasicPage.
  • Darüber hinaus besitzt sie Methoden, die es erlauben, PageSegments, nach DocumentLayern indexiert, in der Page zu registrieren.
  • An Pages können PageListener registriert werden, die bei Änderungen an der Seite, d.h. ihren Inhalten oder Eigenschaften, benachrichtigt werden.
  • Die Page ist ein PropertiesProvider, stellt es Ihnen also frei, ihr beliebige Eigenschaften anzuheften.

Fast noch auffälliger als das, was die Page ist, ist aber was sie nicht (mehr) ist bzw. tut:

  • Die Page hat keinen Bezug mehr zum Document. Dies klingt zunächst vielleicht überraschend, ist aber tatsächlich so: eine Seite "weiß" nicht mehr, zu welchem Dokument sie gehört. Dies sorgt in manchen – zum Glück hinreichend seltenen – Fällen dafür, dass neben einer Page-Referenz auch das Document 'gemerkt' werden muss. Die Vorteile der Auflösung dieser Abhängigkeit überwiegen jedoch bei weitem. So ist beispielsweise die jetzt erlaubte gemeinsame Nutzung einer Seite in mehreren Dokumenten und das nun relativ unproblematisch mögliche "Herumreichen" von Seiten eine direkte Folge davon.
  • Eine ganze Reihe von Methoden, die lediglich zur Informationsbeschaffung dienten, aber nicht originär in das Aufgabenspektrum der Page fielen, haben wir, um das API übersichtlicher zu machen, in eine Klasse mit statischen Hilfsmethoden verschoben: Pages.getRenderedSize(Page, RenderControls) liefert die Größe des Renderings einer Page usw.
  • Die Page ist auch kein Abkömmling des RenderElement-Trees (der von jadice intern benutzte Szenengraphen für die Darstellung, der derzeit nicht Teil des öffentlichen APIs ist) mehr. Dies hilft, das API zu verschlanken, ohne die Nützlichkeit zu beeinträchtigen.

Dokumentebenen (DocumentLayers)

Die Aufgabe von DocumentLayers ist es, Ebenen eines Dokuments zu definieren, auf denen Seiten mit PageSegments besetzt sein können. In jadice 4 waren DocumentLayer sehr eigenständige, aber auch leider sehr eigensinnige Objekte. Die Identität eines Layers wurde durch die Identität der DocumentLayer-Instanz bestimmt. Zwar hatten DocumentLayers Namen, doch konnten ohne Weiteres zwei Layer gleichen Namens existieren, die aber zwei unterschiedliche Positionen im Ebenenstapel bezeichneten. Jedes Document hatte in der Regel einen eigenen Satz an DocumentLayers und legte darüber hinaus die Reihenfolge der Ebenen selbst fest. Dieses Verhalten war die zweite große Hürde, die das problemlose Verschieben von Seiten zwischen Dokumenten erschwerte oder verhinderte.

In jadice 5 gibt es künftig einen sehr begrenzten und globalen "Zoo" von DocumentLayers, deren Bestand strikt überwacht wird. Vielleicht ist Ihnen beim Document schon aufgefallen, dass dies nichts mehr mit DocumentLayers zu schaffen hat. Dies hat seine Richtigkeit: die Verwaltung der Ebenen erfolgt nun über nur zwei statischen Methoden in DocumentLayer: get(String) gibt einen Layer für einen gegebenen Namen zurück. Existiert der Layer noch nicht, wird er erzeugt. Dadurch ist sichergestellt, dass zu einem Layer-Namen immer nur eine einzige Instanz von DocumentLayer existiert. Mit get(String,int) kann zusätzlich die standardmäßige Sortierpriorität der einzelnen Ebenen vorgegeben werden, die allerdings nur dann greift, wenn die Ebene noch nicht existierte. Nur in Ausnahmefällen ist es jedoch überhaupt notwendig, eigene Ebenen zu deklarieren. Für die häufigsten Anwendungsfälle existieren in DocumentLayer entsprechende Konstanten, die bevorzugt verwendet werden sollten.
À propos Sortierung: War früher das Document für die Sortierung der Layer zuständig, ist diese Aufgabe nun vollständig den BaseRenderSettings übertragen worden. Dadurch können Seiten z.B. problemlos in zwei Ansichten mit unterschiedlicher Layer-Reihenfolge gerendert werden.

Seiteninhalte (PageSegments)

Während die bisher genannten Klassen und Schnittstellen rein strukturelle Aufgaben wahrnehmen, kommen wir nun zum eigentlichen Träger der visuellen Information des Dokumentes, zum PageSegment. Fast jedes von jadice unterstützte Dokumentformat bringt eine einene Implementierung von PageSegment mit – nur fast deswegen, weil sich beispielsweise alle Annotationsformate ein gemeinsames AnnotationPageSegment teilen. Die visuellen Dokumentinhalte werden von jadice in einem Szenengraphen repräsentiert, dessen Wurzelklasse die Klasse RenderElement bildet. Doch halt! Jetzt, wo es gerade interessant wird, müssen wir schon wieder abbrechen, denn zum gegenwärtigen Zeitpunkt können und wollen wir noch keine Garantien für die API-Stabilität des Szenengraphen geben. Das RenderElement und die abgeleiteten Klassen sind nicht Bestandteil der öffentlichen API und somit – leider – "off limits". Doch zum Glück hat das PageSegment einige weitere interessante Eigenschaften, deren Betrachtung lohnenswert ist.

Da wäre zunächst einmal der Zusammenhang zwischen Pages und PageSegments. Im Gegensatz zur Page hat das PageSegment einen Verweis auf das Elternobjekt, nämlich die Seite. Im Umkehrschluss bedeutet dies: Ein PageSegment kann nicht Kind mehrerer Seiten sein. Zwar ist es legal, ein PageSegment, das gerade an einer Seite "befestigt" ist, einer anderen Seite hinzuzufügen, dabei wird jedoch die Verbindung zur ursprünglichen Seite automatisch gelöst. Sollten Sie einmal auf einen Anwendungsfall stoßen, der das Hinzufügen eines PageSegments zu mehreren Seiten zwingend erfordert, so lassen Sie uns dies bitte wissen.
Als nächstes tragen PageSegments geometrische Eigenschaften. Sie haben eine Größe (getSize()) bzw. einen Bildbereich (getBounds()), die jeweils in Basiseinheiten (1/100 pt) ausgedrückt sind. Erst die Vereinigung aller Größen bzw. Bildbereiche der PageSegments einer Seite legt die Größe bzw. den Bildbereich der Seite selbst fest, weshalb eine leere Seite (ohne PageSegments) auch keine Größe besitzt.
Bereits besprochen wurden zwei weitere Eigenschaften des PageSegments: Es ist ein PropertiesProvider, erlaubt also das Anheften integrationsspezifischer Eigenschaften und ein MetadataProvider, stellt also Metadaten bereit. Die Metadaten der PageSegments sind dabei nur lesbar, nicht beschreibbar (das PageSegment implementiert nicht MutableMetadataProvider), da die Metadaten des PageSegments generell vom Dateiformat bereitgestellt werden und somit das PageSegment diejenigen Metadaten enthält, die im Dokumentdatenstrom für die durch das PageSegment repräsentierte Seite hinterlegt sind. Integrationsspezifische Metadaten trägt nicht das PageSegment selbst, diese können aber mit Hilfe einer PageSegmentSource transportiert werden.

Die PageSegmentSource

Jedes PageSegment stammt aus irgend einer Quelle - meist in Form eines Datenstromes, der ein Dokument enthält. Die PageSegmentSource beschreibt nun, wie ihr Name besagt, die Quelle, aus der ein PageSegment stammt. Wird ein Multi-Page-Dokument geladen, so verweisen in der Regel alle PageSegments des Dokuments auf ganz ähnliche PageSegmentSources. Was gibt es über den Herkunft einers PageSegments zu sagen?
    •  getStream() liefert den Datenstrom, aus dem das PageSegment ursprünglich geladen wurde. Gemeint ist hier der gesamte Datenstrom, also ggf. ein Multi-Page-Dokument, aus dem mehrere PageSegments geladen wurden, nicht aber ein irgendwie gearteter "Sub-Datenstrom", der einem bestimmten PageSegment zuordenbar wäre.
    •  getPageIndex() liefert den (0-basierten) Seitenindex eines Multi-Page-Dokuments aus dem das PageSegment geladen wurde. Wir benötigen diesen, weil der Zusammenhang zwischen den Seiten eines Dokuments und deren Herkunft nicht starr ist, also ein bestimmter Seitenindex im Dokument nicht zwingend mit einem Seitenindex in einem Qualldatenstrom korrespondiert.
    •  getFormat() liefert das com.levigo.jadice.document.Format des Datenstromes, aus dem das PageSegment stammt. Je nach konkreter Implementierung von Format kann dies noch zusätzliche Informationen über das geladene Dokument enthalten.
    •  Bleiben noch getProperties() und
    •  getMetadata(), die in gewohnter Manier integrationsspezifische Eigenschaften sowie Metadaten bereitstellen.
Warum nun schon wieder Properties und Metadaten, wo doch schon das PageSegment diese trägt? Ganz einfach: die Properties und Metadaten der PageSegmentSource beziehen sich auf den gesamten Datenstrom, aus dem das PageSegment geladen wurde. Alle PageSegmentSources aller PageSegments, die aus einem Datenstrom geladen wurden, tragen dieselben Eigenschaften und Metadaten. Diese werden in der Regel zum Zeitpunkt des Ladens durch den Integrator (via Reader-API) bereitgestellt. Eine Anwendung dafür ist z.B. der Transport von Indexdaten eines Archivsystems, das den Dokumentdatenstrom bereitgestellt hat. Jedes PageSegment hingegen hat eigene Properties und seine Metadaten sind überdies nicht veränderbar.