In einem PDF können verschiedene Ebenen definiert werden. Ebenen heißen auf Englisch Layers. Diese Layers gibt es in vielen Zeichen- und Designprogrammen. So auch im Adobe Illustrator oder im Adobe Photoshop. Auf diesen Layern können Objekte gruppiert werden, die irgendwie zusammengehören. Man kann diese Ebenen sperren oder unsichtbar machen. Das hilft beim Arbeiten, man behält die Übersicht.
Mit den PDF Annotations / Form Fields hat man in einem PDF die Möglichkeit mit dem Benutzer zu interagieren. Da wäre es sehr praktisch, diese UI-Elemente auf Layern anzuordnen und in Abhängigkeit des Zustandes des Formulars sichtbar zu machen. In Windows Forms gibt es dazu das beispielsweise das TabControl.
Leider fehlt im Adobe Acrobat die Möglichkeit Ebenen zu erzeugen! Wie? Da oben ist doch ein Screenshot mit dem Layers Dialog? Tja, der kann aber nur Ebenen umschalten oder zusammenfassen.
Schade, Ebenen erzeugt man in der Adobe Welt mit dem Illustrator oder InDesign. Beide Tools können die internen Ebenen in ein PDF exportieren. Das geht auch mit einem ansonsten leeren Dokument. Sehr umständlich, aber wenn es den sein muss.
Das leere, aber mit Ebenen versehene PDF (Layered PDF) kann man in Acrobat laden und die Ebenen selektieren. Also wähle ich die gewünschte Ebene aus und erzeuge eine List Box. Ups, die landet nicht in der Ebene sondern im Dokument selbst. Was mache ich falsch? Wo kann man Objekte den Ebenen zuweisen? Irgendwann rückt die Acrobat Hilfe damit raus:
You can add content, such as review comments, stamps, or form fields, to layered documents just as you would to any other PDF document. However, the content is not added to a specific layer, even if that layer is selected when the content is added. Rather, the content is added to the entire document.
An der Stelle würde ich normalerweise aufgeben. Acrobat kann einfach mit Ebenen nicht umgehen. Fertig!
Ich habe aber bereits ein PDF mit Form Objekten auf Ebenen gesehen. Das PDF habe ich noch mal in Acrobat geladen. Keine Täuschung, es stimmt. Wie haben die das nur gemacht? Vielleicht mit JavaScript?
In der JavaScript SDK finden sich tatsächlich einige Methoden zur Manipulation von Layern. Nur dauert das ein bisschen. Außer in der Benutzeroberfläche heißen die Layers bei Acrobat nämlich Optional Content Groups (OCG). Aber auch in JavaScript kann man keine Objekte gezielt diesen OCGs zuweisen.
Mit diesem Terminus findet Google aber jetzt auch mehr technische Beiträge. Zum Beispiel diesen Trick: Buttons on Adobe PDF Layers. Es werden per InDesign CS3 Buttons auf Ebenen erzeugt, die beim PDF Export zu PDF Form Buttons werden. Die Ebeneninformation bleibt dabei erhalten. Leider schließt der Beitrag mit:
What about Text fields? Unfortunately we don’t yet have an option in InDesign to convert a text frame to an Acrobat text form field. Any fields you create in Acrobat will be visible on all layers.
Ich will aber meine List Box! Es geht, ich habe den Beweis! In der Adobe Acrobat User Community gibt es die Rubrik Ask an expert. Die Antwort zum Thema Move an object to an existing layer:
Unfortunately, you can't change layer data in Acrobat. You can neither add new layers to a PDF, move data between layers, or introduce new data on a given layer. You need to return to Illustrator and recreate the PDF if you want to change the layer data.
Jetzt kommt der finale C++ Hammer: In der Acrobat API Referenz findet sich die Methode PDAnnotSetOCMD. Damit kann man eine Annotation (Form Field) einem optional-content membership dictionary (OCMD) zuweisen. Dieses Dictionary stellt die Verbindung von OCG und Annotation dar. Genau was ich brauche. Nebenbei findet sich dann noch die Methode PDOCGCreate um Ebenen zu erzeugen.
Diese API steht aber nur einem C++ Acrobat Plug-In zur Verfügung. Aber auch hier hilft die SDK weiter: Adobe hat einen Snippet Runner beigelegt. Zusammen mit einer Visual Studio Solution und einigen Beispiel Snippets. Der Snippet Runner kompiliert zu einem Acrobat Plug-In, das man in das Acrobat Verzeichnis kopiert. Beim nächsten Start von Acrobat, startet das Plug-In mit und startet dient als Server. Auf diesen Server wiederum greift das Snippet Runner Common Interface 2.0 zu. Witzigerweise eine Adobe Flex 2 Applikation. Also quasi eine getarnte Flash Applikation:
Über dieses Interface kann man gezielt sein eigenen Snippets im Acrobat Plug-In Kontext laufen lassen. Der Code der Snippets ist dabei mit in den Server kompiliert. Das Frontend liefert den Auswahl Mechanismus und die Konsolen-Ausgabe.
Ich kann mich nicht erinnern, wann ich zuletzt in C++ unterwegs war. Es ist sehr lange her und war auch damals nur sehr kurz. Sprich, ich habe eigentlich keine Ahnung. Aber irgendwie hat es für diese Funktionalität gereicht und ich kann jetzt mit zwei Methoden meine Ebenen erzeugen und Form Objekte anhand deren Namen in diese Ebenen verschieben:
PDOCG ocgSubparts = CPTecCreateOCG("Subparts", true);
PDOCG ocgAssembly = CPTecCreateOCG("Assembly", false);
CPTecMoveAnnot("TestSubparts", ocgSubparts);
CPTecMoveAnnot("TestAssembly", ocgAssembly);
Der zweite Parameter bei CPTecCreateOCG gibt an, ob die Ebene beim Laden das PDFs sichtbar sein soll. Falls bereits eine Ebene mit dem gewünschten Namen vorhanden ist, wird diese zurückgegeben. Die zweite Methode CPTecMoveAnnot weist der Annotation mit dem übergebenenNamen die entsprechende Ebene zu. Damit ist ein mehrfacher Lauf des Snippets problemlos möglich.
Und so sieht das Ergebnis im PDF aus. Zwei List Boxen, die auf Ebenen liegen, die man später auch programmatisch umschalten kann.
Ist das Ergebnis den Aufwand wert? Ja klar. Ärgerlich wäre nur, wenn Acrobat 8.2 den Layer Dialog um diese Funktionen erweitern würde. Bis dahin kann ich ja noch grübeln, ob ich meine beiden C++ Plug-In Methoden vielleicht veröffentliche :)
Update:
Ich habe nicht den Code, sondern gleich ein fertiges Plug-in veröffentlicht: CPTec Layer Plugin für Acrobat