IDML

Mit IDML gibt es seit CS4 eine von Adobe vorgesehene Repräsentation von InDesign-Dokumenten in XML. Viele fragen mich, was das eigentlich bedeutet bzw. ob IDML für ihren Workflow relevant ist.

Zunächst einmal: Das Schnittstellenformat richtet sich an Solution-Provider und wird für den durchschnittlichen Anwender eher unbedeutend bleiben. Die eigentlichen Neuigkeiten sind, dass Adobe IDML im Gegensatz zu INX offiziell unterstützt, die Spezifikation veröffentlicht wurde und dass das Dateiformat deutlich übersichtlicher geworden ist.
Ganz grundsätzlich bleibt natürlich das Problem, dass ein extern generiertes IDML die Ergebnisse der InDesign-Satz-Engine praktisch nicht vorhersehen kann. Somit sind keine interaktiven Bedingungen und Gestaltungsregeln umsetzbar, die sich erst nach dem endgültigen Satz  auswerten lassen. Im Endeffekt erreicht man damit dann einen typischen XSL-FO Automatisierungsgrad. Für Kataloge und datenbankorientierte Produkte wahrscheinlich ein kleineres Problem.

IDML wird wahrscheinlich für die folgende, natürlich nicht vollständige, Sammlung von Aufgaben verwendet werden:

  • Dokumente außerhalb von InDesign erstellen und verarbeiten z.B. aus Datenbanken oder XML-Dateien.
  • Gut vorstellbar ist auch, dass Übersetzungstools auf der Basis von IDML arbeiten (so wie es jetzt schon Tools auf der Basis von INX gibt).
  • Vollautomatische Inhaltsanpassungen in Musterdokumenten, z.B. in Web-Applikationen.
  • Zusammenstellen von Dokumenten aus verschieden Teilkomponenten.
  • Einsatz innerhalb von XSLT-Workflows.

Um das zu erreichen hat sich Adobe einige Ziele für IDML gesetzt:

  • Vollständigkeit Alle Objekte, Attribute und Voreinstellungen sollen durch IDML repräsentiert werden können.
  • Lesbarkeit Gutes XML-Grammatiken sind auch von Entwicklern lesbar und verständlich, XML-Standard-Tools sollten sowieso keine Probleme haben.
  • Robustheit Sollte eine Selbstverständlichkeit sein.
  • Kompatibilität IDML soll ähnlich wie INX zur jeweils vorigen Version kompatibel sein.
  • Performance Die InDesign Standalone Applikation zu schlagen ist nicht so schwer :-)

Ganz interessant ist auch der Ansatz von Adobe, dass Skripting Objektmodell als Grundlage von IDML zu verwenden. Dies bedeutet, dass mit einem skriptbaren Plugin IDML mit den hinzugekommen Fähigkeiten erweitert werden kann. Dazu ist ein Schema-Generator vorgesehen, der für die jeweils aktuelle Konfiguration von InDesign ein Schema generiert. Mit dem so erstellten Relax-NG Schema kann man dann seine XML-Instanzen validieren.

Zur IDML Familie gehören noch die folgenden Formate, die letztlich auf der gleichen Grammatik beruhen:

  • IDMS (InDesign Markup Snippet) Zur Wiederverwendung von in InDesign gestalteten Objekten
  • ICML (InCopy Markup Language) Inhalte in InCopy bearbeiten
  • ICMA (InDesign Markup Assignment) Hier können Inhalte einem Autor zur Bearbeitung in InCopy zugewiesen werden.

Spätestens seit Microsofts Open XML Standard wissen wir allerdings, dass ungeprüfte Ankündigungen solcher Art nicht das Papier Wert sind auf dem sie stehen. Ich habe mich bis jetzt nur kurz mit IDML beschäftigen können. Erste Tests sind aber vielversprechend, wobei mir dabei natürlich zugutekommt, dass ich das Skripting Objektmodell schon kenne.

Um eine Vorstellung vom Format zu bekommen lohnt ein Blick in eine IDML-Datei. Ganz ähnlich wie andere datenzentrierte XML-Formate ist die Datei ein ZIP-Container, der verschiedene, untereinander verlinkte, XML-Dateien enthält. Die wichtigsten Inhalte sind:

  • Eine Übersichtsdatei designmap.xml
  • Die Typdefinition mimetype
  • Encoding und Dateiaufbau im Ordner META-INF
  • Eine Datei pro Musterseite im Ordner MasterSpreads
  • Formatangaben, Schriften, Farben, Voreinstellungen im Ordner Resources
  • Die Inhaltsseiten und deren Objekte im Ordner Spreads
  • Der Eigentliche Inhalt im Ordner Content
  • Die XML-Tags des InDesign Dokuments befinden sich im Ordner XML

Die XML-Grammatik orientiert sich am Objektmodell. Als Beispiel dazu

<ParagraphStyleRange AppliedParagraphStyle="ParagraphStyle/Absatzformat">
   <CharacterStyleRange AppliedCharacterStyle="CharacterStyle/$ID/[No character style]">
     <Content>Hallo Welt ich bin </Content>
   </CharacterStyleRange>
   <CharacterStyleRange AppliedCharacterStyle="CharacterStyle/fett">
     <Content>fett</Content>
   </CharacterStyleRange>
</ParagraphStyleRange>

Im Prinzip recht ähnlich zu den Eigenschaften der Klasse TextStyleRange im Skripting.

Mehr Information findet in der InDesign developer documentation von Adobe. Hier ist das Kochbuch empfehlenswert. Sobald ich eigene Erfahrungen mit IDML gesammelt habe natürlich auch hier.

Absatzlinien, transparent

Die guten alten Absatzlinien sind eine ziemlich geniale Möglichkeit Absätze mit Farbhinterlegungen oder Schmuckelementen zu versehen. Leider kann man sie nicht transparent setzen, so dass sie immer den Hintergrund vollständig verdecken.

Soll nun wie im Beispiel ein Hintgerundfoto mit Hilfe der Absatzlinien etwas zurückgenommen werden, benötigt man ein kleines Skript, dass die Hinterlegungen an den entsprechenden Stellen in das Dokument zeichnet. Bei Umbruchverschiebungen laufen sie nicht mit, dann muss das Skript erneut ausgeführt werden.

Die Idee ist die Absatzlinien selber zu erstellen. Dazu lege ich im voraus ein Absatzformat fest, bei welchem die Absatzlinien gezeichnet werden sollen.

const PSTYLE = "u2";

Am Anfang müssen eventuell noch vorhandene alten Hinterlegungen gelöscht werden, dazu versehe ich sie beim Erstellen (im zweiten Code Block bei rectangle.add) mit einem bestimmten label.

const MARKER = "%hpScriptObject%";
for (i = _dok.allPageItems.length -1; i > 0 ;i--) {
_dok.allPageItems[i].label == MARKER ? _dok.allPageItems[i].remove() : undefined ;
}

Nun durchsucht das Skript alle Absätze des Dokuments und errechnet die Größe der transparenten Hinterlegungen dynamisch.

// ... alle Stories, und Paragraphs einsammeln
if (_p.appliedParagraphStyle.name == PSTYLE) {
    for (j =0; j < _p.lines.length; j++) {
        var _x1 = _p.lines[j].characters[0].horizontalOffset;
        var _x2 = _p.lines[j].characters[-1].endHorizontalOffset;
        var _y2 = + offset + _p.lines[j].baseline ;
        var _y1;
        if (_p.lines[j].leading == Leading.AUTO ) {
            // Zeilenabstand selber berechnen 
            _y1 = _y2 +  _p.pointSize * (_p.autoLeading/100) *-1;
        }
        else {
            _y1 = _y2 +  _p.lines[j].leading * -1;
        }
        _dok.rectangles.add( undefined, LocationOptions.AFTER ,
                             _p.parentTextFrames[0] ,
                           { appliedObjectStyle:objStyle ,
                             geometricBounds:[_y1,_x1,_y2,_x2], label:MARKER } );
}

Interessant ist die in CS3 neu hinzugekommene Eigenschaft Character.endHorizontalOffset. Früher musste diese Position des letzten Buchstabens noch performanceraubend und mühsam errechnet werden.

Das vollständige Skript kann hier heruntergeladen werden. Zum Testen wird ein InDesign-Dokument mit den Absatzformat u2 benötigt – alternativ kann der Code natürlich auch direkt angepasst werden.

XML Export und Reimport

Der Import von XML-Daten in InDesign richtet sich ja nach der etwas eigenwilligen Interpretation von XML durch Adobe. Wobei die grundlegende Problematik des Whitespace-Handling im XML-Standard zu wünschen übrig lässt und den Applikationsanbieter bezüglich des Renderings etwas im Regen stehen lässt. Hat man die Probleme beim Import umschifft – im wesentlichen muss beachtet werden, dass jeglicher Whitespace im InDesign Dokument umgesetzt wird – erlebt man beim Export die nächste Überraschung.

Die Betrifft im wesentlich zwei Bereiche:

  1. Der Unicode Codepoint des Zeilenwechsel wird in der Info-Palette von InDesign als  &#x000D (Carriage Return) der Soft-Return als  &#x000A (Line Feed) angezeigt. Im exportieren XML Dokument wird für den Zeilenwechsel der etwas unübliche Paragraph Separator &#x2029 für einen Soft-Return der Line Separator &#x2028 verwendet. Eigentlich ist die Idee gar nicht schlecht eindeutige und dafür vorgesehene Unicode Zeichen zu verwenden. Problematisch ist dabei jedoch, dass die Zeichen von fast keinem Programm interpretiert werden und somit in Text-/XML-Editoren bestenfalls als Kästchen dargestellt werden.
    Außerdem sind es keine zugelassenen Whitespace Zeichen. Da in InDesign die Formatierung von Block-Level-Elementen sinnvollerweise über eben diese Zeichen geregelt wird, steht man vor dem Dilemma entweder nicht valide Daten zu bekommen oder die Zeilenwechsel in Text-Elementen unterzubringen – wo man sie allerdings auch nicht haben will.
    Um valide Daten zu bekommen hilft ab CS3 die Option im Exportdialog die Zeilenwechsel als Leerzeichen auszugeben.
  2. Der zweite Problembereich sind die Spezialzeichen von InDesign die teilweise gar nicht in Unicode abbildbar sind. Diese werden nämlich einfach verworfen oder bei entsprechende Export Option als Leerzeichen umgesetzt. Glücklicherweise betrifft das nur wenige Zeichen:
    1. Den nach Rechts austreibenden Tabulator
    2. Das Einzug Hier Zeichen
    3. Das Endzeichen für geschachtelte Formate
    4. Alle manuellen Spalten- und Seitenwechsel.
    5. Automatisch generierte Seitenzahlen und Section Marker

In beiden Fällen hätten sich Processing-Instrucions angeboten, damit wäre eine exportierte XML-Datei identisch wieder reimportierbar geworden. Und ganz nebenbei wäre mit einem solchen Set von Processing Instrucitons der Import von XML-Daten übersichtlich und prozesssicher umsetzbar.

Für eine Übersicht über die verwendbaren Sonderzeichen innerhalb von InDesing habe ich ein kleines Skript entworfen welches den SpecialCharacters Enumerator auswertet. Enstanden ist ein Dokument mit allen InDesign-Sonderzeichen. Dieses habe ich dann als XML exportiert, mit einem kleinen XSLT um die Unicode Codepoints erweitert und dann reimportiert. Das Ergebnis kann man hier herunterladen.

Sanftes spationieren

Zunächst einmal: Der Begriff Spationierung wird nicht einheitlich verwendet, laut Typolexikon und meiner Erfahrung wird damit sowohl das Verändern von Wortzwischenräumen (WZR) als auch die Schriftweitenveränderung bezeichnet.

Die Schriftweite/Tracking lässt sich mit ALT + PFEIL NACH LINKS/RECHTS verändern, allerdings ist darauf zu achten zunächst in den Voreinstellungen unter Einheiten, Kerning den Wert 1 einzutragen.

Im deutschen Schriftsatz ist das eigentlich nicht erwünscht – seit dem Einzug von DTP allerdings ziemlich weit verbreitet – dort werden stattdessen die Wortzwischenräume verändert. In InDesign auch kein Problem mit den ALT + SHIFT + STRG + J wird das Menu mit den Werten für den Blocksatz aufgerufen. Der Wert für das Optimun wird entgegen der Beschriftung auch für den Rau-/Flattersatz verwendet. Die Tastenkombination für die Veränderung ist etwas unbekannter, aber auch kein Problem: WZR vegrößern STRG + ALT + ß bzw. mit STRG + ALT + BACKSPACE wieder verringern. Allerdings ist der Wert meines Wissens nicht in der Benutzeroberfläche oder via Skripting auslesbar.

In beiden Fällen gilt: Ungeduldige drücken dazu die SHIFT Taste, das ganze kann mit STRG + ALT + Q wieder aufgelöst werden.

Soweit zu den Built-in-Features, aber hier gehts ja um praktische Verbesserungen. Das folgende Skript bringt eine Zeile unter minimaler Veränderung des Wortzwischenraums ein. Das Skript orientiert sich am Minimalwert der Blocksatzeinstellugen, die je nach typografischen Gefühl des Benutzers eingestellt sein sollten.

Das Ergebnis lässt sich über die Blocksatzeinstellungen. kontrollieren, wenn es nicht gefällt einfach einmal Rückgängig ausführen.

// Der Cursor muss in einem Absatz positioniert sein:
if (app.selection[0].constructor.name == "InsertionPoint") {
    var par = app.selection[0].paragraphs[0];
    var lines = par.lines.length;
    var oldDesiredWordSpacing = par.desiredWordSpacing;
    var cDesiredWordSpacing = par.desiredWordSpacing;

    // WordSpacing solange verringern bis eine Zeile weniger
    while (par.lines.length == lines) {
        // Das etwas unübersichtliche Konzpet erlaubt danach ein
        // einmaliges STRG + Z für das Rückgängig
        cDesiredWordSpacing--;
        par.desiredWordSpacing = cDesiredWordSpacing;
        if (par.lines.length == lines) {
            par.desiredWordSpacing = oldDesiredWordSpacing;
        }
        // Das Skript orientiert sich am minimumWordSpacing
        // Wert aus den Blocksatzeinstellungen.
        // Falls der Minimalwert unterschritten wird,
        // wird der Originalwert wieder hergestellt.
        if (par.desiredWordSpacing == par.minimumWordSpacing)  {
            par.desiredWordSpacing = oldDesiredWordSpacing;
            exit(1);
        }
    }
    exit(0);
}

Eine interessante Möglichkeit wäre es, eine Textbereich oder Textrahmen auszuwählen, und dort das Skript den Absatz finden zu lassen der den geringsten Eingriff benötigt.

Die Idee für das Skript basiert auf einer Forumsdiskussion. Das Ergebnis dort war ein Skript von Teus de Jong welches die Spationieurng verringert. Im Download habe ich ein Skript mit beiden Optionen.

Suchen, bearbeiten und ersetzen

Eine meiner Lieblingsneuerungen bei InDesign CS3 war die Möglichkeit mit Hilfe von Regulären Ausdrücken Suche/Ersetze-Vorgänge durchzuführen. Hier soll es aber nicht um die erweiterten Möglichkeiten der GREP-Funktion gehen.
Oftmals egibt sich in der Praxis die Anforderung, zunächst bestimmte Stellen in einem Dokument zu finden, diese dann allerdings nicht einfach zu ersetzen, sondern vorher nach bestimmten Regeln zu bearbeiten.
Dazu habe ich ein recht übersichtliches FindAndDo-Script geschrieben, in dem dann nur noch die Suchbedingung und die Verarbeitungsanweisung eingetragen werden muss.
Ein nicht zu vernachlässigender Punkt ist die rückwärts laufende For-Schleife: Wenn auf der Trefferliste Ersetzungen vorgenommen werden die eine veränderte Anzahl an Zeichen aufweisen, ändert sich der Index des Elternelements auf dem gesucht wurde. Die Folge ist, dass die darauf folgenden Ersetzungen an falschen Stellen ausgeführt werden (dem ursprünglichen Index).
Vielleicht mal das letzte Beispiel aus der Praxis damit es klarer wird: Alle Seitenverweise in einem Dokument waren vom Verlag per Hand aufgelöst worden, nun verschob sich der Umbruch ab Seite 106 um vier Seiten. Mit der normalen Suchfunktion hätte das bedeutet, alle Seitenverweise von Hand durchzugehen und je nach Postion zu verädern oder zu ignorieren. Bei 500 Verweisen wäre das ganze recht zeitaufwändig geworden, das Skript sieht dann so aus:

    // Zunächst alte Suchoptionen löschen, ich verwende hier die Grep Suche
    app.changeGrepPreferences = NothingEnum.nothing;
    app.findGrepPreferences = NothingEnum.nothing;
    // Nach Seitenverweisen des Typs (S. 99) suchen
    app.findGrepPreferences.findWhat = "\\(S..([0-9]+?)\\)";
    var erg = app.activeDocument.findGrep ();	

    for (var i = erg.length - 1; i >= 0; i--) {
        // Die Seitenzahl extrahieren
        var string = erg[i].contents.match (/[0-9]+/) ;
        // Wenn die Seitenzahl größer 106 ist wird sie erhöht
        if (parseInt(string) >= 106 ) {
            string = (parseInt(string) + 4);
        }
        erg[i].contents = "(S. " +  string + ")";
    }

PS: Die Seitenauflösung von Hand wäre auch überflüssig gewesen, wenn der Workflow von Anfang an sinnvoll geplant gewesen wäre …
PPS: Bei der Übergabe von Grep Suchanfragen müssen die Javascript String Konventionen beachtet werden, d.h. im besonderen die Forward-Slashes und Quotes müssen mit dem Forward-Slashe escaped werden (z.B. //). Ich empfehle im DEBUG Modus die Übergabe an das Suchfeld zu testen.

Hello world!

Und noch ein Blog zu InDesign. Ich werde hier in loser Folge von meiner Arbeit mit InDesign in halb- und vollautomatisierten Publishing-Workflows berichten. Die meisten Projekte sind eine Kombination aus XML, InDesign-Scripten und manchmal InCopy. Die vielen Randthemen von Content-Generation über XSLT bis hin zu Roundtripping werde ich des öfteren streifen.
Die üblichen FAQ sind meiner Ansicht nach schon recht gut im Netz zu finden – dazu werde ich wohl eher das Connect-Widget ein wenig pflegen.

Jetzt Alleinstellungsmerkmale zu definieren wäre in Anbetracht des (noch nicht vorhandenen) Contents ein wenig vermessen. Da die Beschreibung von ganzen Workflows ein wenig lang wäre (und ich schon genug vorm Rechner sitze) werde ich mich auf Interessante Aspekte und Herangehensweisen beschränken. Weiterhin werde ich für mich und die Nachwelt interessante Diskussionen aus dem Adobe InDesign Scripting Forum zusammenfassen. as ganze dann gesprenkelt mit Code Stückchen und Scripten.

Fangen wir mal an:

Das klassische hello world Skript ist in InDesign etwas länger:

    // helloworld.jsx
    // Neues Dokument erstellen
    var _dokument = app.documents.add();
    // Textframe mit Text Hallo Welt befüllen
    var tf = _dokument.pages[0].textFrames.add();
    tf.geometricBounds = [10,10,100,100];
    tf.insertionPoints[0].contents = "Hallo Welt!";

Ich vergaß zu erwähnen: Alle Skripte werden in JavaScript veröffentlicht und laufen auf der Mac und Windows Version von InDesign.