Linienhintergrund automatisch erstellen

Wenn Sie Ihre Grafiken mit FrameMaker-Werkzeugen beschriften, werden Sie den Wunsch haben, die schwarzen Beschriftungslinien mit einer hellen Kontrastfarbe zu hinterlegen, damit dies auch auf dunklem Untergrund sichtbar bleibt. Zum Beispiel könnten Sie eine 1pt starke schwarze Linie mit einer 3pt weißen Linie hinterlegen wollen.

Voilá, das ist eine Aufgabe, die ich einmal mit FrameScript und ExtendScript angehen möchte, auch um die Gemeinsamkeiten und Unterschiede deutlich werden zu lassen. Wenn es Ihnen konkret hilft, fein!

Es stellt sich heraus, dass diese Teilaufgabe – wie vermutlich die meisten Aufgaben, die sich nur mit FrameMaker-Dokumenten beschäftigen – prinzipiell identisch gelöst wird. Ich gehe wie folgt vor:

  1. Prüfe die Ausgangssituation: Ein AFrame (verankerter Rahmen) oder eine Grafik darin muss ausgewählt sein.
  2. Entferne alle Linien, die 3pt und weiß sind, um bei wiederholtem Einsatz des Skripts nicht immer mehr Linien zu erzeugen.
  3. Suche nach allen Linien die 1pt und schwarz sind, hinter diese platziere eine 3pt weiße Linie.

In den Zeilen 9 und 12 der Skripte sehen Sie die Zeilen, in denen diese Vorgaben angepasst werden können.

FrameScript

01 Run CreateBackgroundLines pvDoc(ActiveDoc);
02 
03 Sub CreateBackgroundLines pvDoc
04  Local lvAFrame lvSrcProps lvTgtProps lvProps;
05  Local lvObj lvNextObj lvNewObj lvSrcColor lvTgtColor;
06 
07  
08  // fixed List of Properties: .BorderWidth, name of color
09  Set lvSrcProps = EUtl.StringList{1pt, 'Black'};
10 
11  // fixed List of Properties: .BorderWidth, name of color
12  Set lvTgtProps = EUtl.StringList{3pt, 'White'};
13  
14  If (pvDoc)
15      Set lvAFrame = GetAFrame{pvDoc};
16      If (not lvAFrame)
17          MsgBox 'Select AFrame or graphic in AFrame. ' Mode(Note);
18          LeaveScript;
19      EndIf
20      If (lvAFrame)
21          // Get colors
22          Get Object DocObject(pvDoc) Type(Color) Name(lvSrcProps[2]) 
23                      NewVar(lvSrcColor);
24          If (not lvSrcColor)
25              MsgBox 'Color "' + lvSrcProps[2] + '" not available.' Mode(Note);
26              LeaveScript;
27          EndIf
28          Get Object DocObject(pvDoc) Type(Color) Name(lvTgtProps[2]) 
29                      NewVar(lvTgtColor);
30          If (not lvTgtColor)
31              MsgBox 'Color "' + lvTgtProps[2] + '" not available.' Mode(Note);
32              LeaveScript;
33          EndIf
34 
35          Set Session.Displaying = False;
36          // delete existing with tgt properties
37          Set lvObj = lvAFrame.FirstGraphicInFrame;
38          Loop While(lvObj)
39              Set lvNextObj = lvObj.NextGraphicInFrame;
40              If (lvObj.ObjectName = 'Line')
41                  If (lvObj.BorderWidth = lvTgtProps[1] 
42                      and lvObj.Color.Name = lvTgtProps[2])
43                      // hit!
44                      Delete Object(lvObj);
45                  EndIf
46              EndIf
47              Set lvObj = lvNextObj;
48          EndLoop
49 
50          // find existing with src properties
51          Set lvObj = lvAFrame.FirstGraphicInFrame;
52          Loop While(lvObj)
53              Set lvNextObj = lvObj.NextGraphicInFrame;
54              If (lvObj.ObjectName = 'Line')
55                  If (lvObj.BorderWidth = lvSrcProps[1] 
56                      and lvObj.Color.Name = lvSrcProps[2])
57                      // hit!
58                      Set lvProps = lvObj.Properties;
59                      New Line ParentObject(lvAFrame) NewVar(lvNewObj);
60                      Set lvNewObj.Properties = lvProps;
61                      Set lvNewObj.BorderWidth = lvTgtProps[1];
62                      Set lvNewObj.Color = lvTgtColor;
63                      Set lvNewObj.NextGraphicInFrame = lvObj;
64                  EndIf
65              EndIf
66              Set lvObj = lvNextObj;
67          EndLoop
68          Set Session.Displaying = True;
69          Update DocObject(pvDoc) ReDisplay;
70          
71      EndIf
72  EndIf
73 EndSub
74 
75 Function GetAFrame pvDoc
76  Local lvObj;
77 
78  Set Result = False;
79  If (not pvDoc)
80      LeaveSub;
81  EndIf
82  Set lvObj = pvDoc.FirstSelectedGraphicInDoc;
83  If (lvObj)
84      Loop While(lvObj.ObjectName != 'AFrame')
85          Set lvObj = lvObj.FrameParent;
86      EndLoop
87      If (lvObj.ObjectName = 'AFrame')
88          Set Result = lvObj;
89      EndIf
90  EndIf
91  
92 EndFunction

ExtendScript

Es bedurfte nur minimaler Maßnahmen, um eine zeilengleiche Fassung mit ExtendScript zu erstellen:

01 CreateBackgroundLines (app.ActiveDoc);
02 
03 function CreateBackgroundLines (pvDoc) {
04  var lvAFrame, lvSrcProps, lvTgtProps, lvProps;
05  var lvObj, lvNextObj, lvNewObj, lvSrcColor, lvTgtColor;
06  var pt = 65536;
07  
08  // fixed List of Properties: .BorderWidth, name of color
09  lvSrcProps = new Array(null, 1 * pt, 'Schwarz');
10 
11  // fixed List of Properties: .BorderWidth, name of color
12  lvTgtProps = new Array(null, 3 * pt, 'Weiß');
13  
14  if (pvDoc.ObjectValid()) {
15      lvAFrame = GetAFrame(pvDoc);
16      if (!lvAFrame) {
17          alert('Select AFrame or any graphic in AFrame. ');
18          return false;
19      }
20      if (lvAFrame.ObjectValid()) {
21          // Get colors
22          lvSrcColor = pvDoc.GetNamedColor(lvSrcProps[2]);
23 
24          if (!lvSrcColor.ObjectValid()) {
25              alert('Color "' + lvSrcProps[2] + '" not available in document.');
26              return false;
27          }
28          lvTgtColor = pvDoc.GetNamedColor(lvTgtProps[2]);
29 
30          if (!lvTgtColor.ObjectValid()) {
31              alert('Color "' + lvTgtProps[2] + '" not available in document.');
32              return false;
33          }
34 
35          app.Displaying = false;
36          // delete existing with tgt properties
37          lvObj = lvAFrame.FirstGraphicInFrame;
38          while(lvObj.ObjectValid()) {
39              lvNextObj = lvObj.NextGraphicInFrame;
40              if (lvObj.type == Constants.FO_Line) {
41                  if (lvObj.BorderWidth == lvTgtProps[1] 
42                      && lvObj.Color.Name == lvTgtProps[2]) {
43                      // hit!
44                      lvObj.Delete();
45                  }
46              }
47              lvObj = lvNextObj;
48          }
49 
50          // find existing with src properties
51          lvObj = lvAFrame.FirstGraphicInFrame;
52          while(lvObj.ObjectValid()) {
53              lvNextObj = lvObj.NextGraphicInFrame;
54              if (lvObj.type == Constants.FO_Line) {
55                  if (lvObj.BorderWidth == lvSrcProps[1] 
56                      && lvObj.Color.Name == lvSrcProps[2]) {
57                      // hit!
58                      lvProps = lvObj.GetProps();
59                      lvNewObj = pvDoc.NewLine(lvAFrame);
60                      lvNewObj.SetProps(lvProps);
61                      lvNewObj.BorderWidth = lvTgtProps[1];
62                      lvNewObj.Color = lvTgtColor;
63                      lvNewObj.NextGraphicInFrame = lvObj;
64                  }
65              }
66              lvObj = lvNextObj;
67          }
68          app.Displaying = true;
69          pvDoc.Redisplay();
70          return true;
71      }
72  }
73 }
74 
75 function GetAFrame (pvDoc) {
76  var lvObj, Result;
77 
78  Result = false;
79  if (!pvDoc.ObjectValid()) {
80      return Result;
81  }
82  lvObj = pvDoc.FirstSelectedGraphicInDoc;
83  if (lvObj.ObjectValid()) {
84      while(lvObj.type != Constants.FO_AFrame) {
85          lvObj = lvObj.FrameParent;
86      }
87      if (lvObj.type == Constants.FO_AFrame) {
88          Result = lvObj;
89      }
90  }
91  return Result;
92 }

Unterschiede

Es gibt natürlich einige generell Unterschiede in der Syntax bei FrameScript (FS) und ExtendScript (ES), wie zum Beispiel die Funktionsaufrufe, die if-then-else-Verzweigungen und loop-while-Schleifen, auf die ich nicht eingehen möchte. Auch erfordert JavaScript und folglich ES Disziplin bei der Großkleinschreibung, FS erlaubt beliebige Schreibweisen von Kommandos, Eigenschaften und Variablenbezeichnern. Viel interessanter sind die feinen Unterschiede beim Zugriff auf FrameMaker.

  • Z. 1: Der Zugriff auf das Session-Objekt ist bei FS implizit, bei ES muss app. vorangestellt werden.
  • Z. 6: Bei Maßangaben rechnet ES mit 1/65636 eines Punkts, also müssen Angaben in pt immer mit diesem Wert multipliziert werden.
  • Z. 9/12: Um später den gleichen Index beim Zugriff auf die Listen verwenden zu können, ES aber die Indizierung immer bei 0 beginnt, habe ich hier einen an sich überflüssigen Eintrag null hinzugefügt.
  • Z. 14: Während bei FS ungültige Objektreferenzen in If-Abfragen automatisch als false bewertet werden, muss bei ES immer noch .ObjectValid() angehängt werden.
  • Z. 22/28: Die ES-Methoden .GetNamedXXX sind eleganter als in FS das Kommando Get Object Type(XXX), zudem ist dies auch eine Abkürzung gegenüber dem FDK.
  • Z. 40/54: Um den Typ eines Objekts zu ermitteln, kann ich bei FS die Eigenschaft .ObjectName für einen String-Vergleich verwenden, bei ES vergleiche ich .type mit einer vordefinierten Konstanten; ich könnte statt Constants.FO_Line auch 17 angeben, das würde die Lesbarkeit des Skripts aber erschweren.
  • Z. 58/60: Statt einer einfache Zuweisung geschieht der Zugriff auf die EIgenschaften eines Objekts bei ES mit den Methoden .GetProps() und .SetProps(props).

Bei der vorliegenden Aufgabenstellung halten sich die Unterschiede also in Grenzen. Und das ist durchaus erfreulich, wenn da nicht ein eklatanter Mangel an Dokumentation für ExtendScript wäre…

Lizenz und Download

Beide Fassungen des Skripts finden Sie in der Datei CreateBackgroundLines.zip.

Selbstverständlich gibt es eine Vielzahl von möglichen Optimierungen, zum Beispiel ist das Verhalten bei Linien mit Pfeilspitzen suboptimal… machen Sie mit?

2 Kommentare

  1. Posted 4. Februar 2011 at 16:33 | Permalink

    Hallo Hr. Müller-Hillebrand!
    Soweit ok, aber ich habe grundsätzlich das Bedürfnis, blaue, schwarze etc. Farben von Linien mit einer hellen Seele zu versehen. Hierbei müssen auch Linienenden als Pfeil ausgebildet werden können.
    Mit freundlichem Gruß
    Ulrich König

  2. Posted 5. Februar 2011 at 16:43 | Permalink

    Es ist spannend, wie unterschiedlich die Anwendungsfälle sind. Einmal soll ein kontrastierender Hintergrund erstellt werden, ein andermal eine helle »Seele«. Die Regeln müssen eindeutig sind, dann steht einer weitergehenden, individuell abgestimmten Automatisierung nichts im Wege; ich erstelle dafür gerne ein Angebot 🙂