Sunday, October 30, 2011

Deleting a Text Range

Deleting text means working with a text range. The range specifies the starting and ending paragraph or text line and the offset from each.

While a user must select text in order to delete it, a script does not need to do so. 

NOTE: A text range cannot span text lines but can span paragraphs. Any selection within a flow must be contiguous but can begin in one paragraph and end in another.

The following example shows how to delete the third through the fifth characters in the first paragraph of the main flow. Selecting the third character means starting the range at an offset of 2, just before that character.

var doc = app.ActiveDoc;
var mainflow = doc.MainFlowInDoc;
var tframe = mainflow.FirstTextFrameInFlow;
var pgf = tframe.FirstPgf;

var tRange= new TextRange();
tRange.beg.obj = pgf;
tRange.beg.offset = 2;
tRange.end.obj = pgf;
tRange.end.offset = 5;
doc.DeleteText(tRange);

Here are the before and afters of  a simple test:
Before running the script









After running the script

Saturday, October 29, 2011

Adding Text at a Location in Text

Just as user typing appears at the insertion point, a script adds text at a text location.

A text location consists of an object identifier (either an paragraph or a text line) and an offset from the start of that object.

The following example inserts text at the beginning of the first paragraph in the active document.

var doc = app.ActiveDoc;

var mainflow = doc.MainFlowInDoc;
var tframe = mainflow.FirstTextFrameInFlow;
var pgf = tframe.FirstPgf;

var tLoc= new TextLoc(); //create the text location object
tLoc.obj = pgf; //make it a paragraph
tLoc.offset = 0; // insert at the start of the paragraph
doc.AddText (tLoc, "Izzy ");

If you start out with the following document











and run the script, you get









If you change the text location to 3 (tLoc.offset = 3; ), you get the following:




Calculating Offsets

To work with text you need to understand how FrameMaker uses offset. While your scripts can work with text in flows or even documents, offset is always from either the start of a paragraph of text line.

Counting offset
Use the following rules in calculating the offset of a location in text:
• The start of a paragraph or text line has offset 0.
• Each character adds offset of 1.
• Anchors of any type have an offset of 1.
• Element boundaries have offset of 1.

The following have no offset:
• Paragraph begins.
• Line begins and ends.

But, paragraph ends have offset and must be selected to
change paragraph defaults. The end of flow marker cannot be selected either by an end user or by a script. It has no offset.

Working with Text

Sooner or later, you are likely to write a script that works with text. Some things you might want to do are:
  • Determine the content of a range of text.
  • Add text to a document.
  • Remove text from a document.
  • Alter the formatting of text.
  • Work with objects that are anchored in text such as tables, markers, or graphic.
Where is text found?
Lots of places including:
  • table cells
  • structure elements
  • flows
  • footnotes
  • paragraphs
  • text frames
  • text lines
  • sub columns
  • text insets of various types
  • variables
  • cross references
What is found in text?
Text contains the alphabetic, numeric, and special characters that make up document content. But text can also contain anchored objects. Those are the tables, anchored frames, markers, cross references that can be inserted at locations between characters.

Text is different
Text, unlike documents, paragraphs, graphics and just about everything else that makes up a FrameMaker document is not an object. There is no FO_Text object. Instead, text within paragraphs, text lines and other objects that contain text are processed as text items. (The details will be the subject of a later post.)

Text is formatted
Text all appears in a given font, at a particular size and style. It contains line breaks and page breaks and other information that reflects how it looks on the page. When you work with text, you will need to be able to determine how it is formatted and, if desired, change that formatting. Text items also give you the information needed to tackle those tasks.

Locations and Ranges
To work with text, you need to understand the concepts of text location and text range. You can think of a text location as analogous to an insertion point and a text range as analogous to a text selection. (But a text range can be selected or not selected!)

    Friday, October 28, 2011

    Error codes: when are they actually set?

    The function PrintFAErrno() displays the current value of FA_errno not as a number but as a constant. This is convenient in understanding what went wrong in your code.

    I did a little experimenting and found some surprising results. If I give the following correct code, I get an error code of FE_Success as expected.

    var doc = app.ActiveDoc.FirstPgfInDoc;
    PrintFAErrno();


    If I change my code as shown below, I expect a non-zero error code as documents do not have the property NextPgfInDoc. (That is a paragraph property.) My tests show, however, that FA_errno remains FE_Success.

    var doc = app.ActiveDoc.NextPgfInDoc;
    PrintFAErrno();

    So what is going on? I am not quite sure. While I distinctly recall seeing actual error codes as I developed other examples, I find that right now I am unable to get anything other than FE_Success. My error? ESTK problem? I fear I have made some subtle mistake. I will be keeping this issue in mind as I go forward and will report on my findings.


    Thursday, October 27, 2011

    Debugging with Error Codes

    The FDK and the ESTK use the global variable FA_errno to indicate whether or not a plug-in or script has executed correctly in the sense that all function calls were well-formed and  executed correctly in a formal sense. 

    Initially FA_errno has the value 0 (Constants.FE_Success). FE_Success has the value 0. All other possible values of FA_errno are negative integers.

    The value of  FA_errno remains FE_Success so long as no error occurs. 

    You can view the current value of FA_errno in the ESTK Data Browser. Just be sure to connect to FrameMaker first.




    If an error takes place, FA_errno is reset to a non-zero value. That value remains until another error causes it to be reset or until you reset it yourself.

    The ESTK lists 111 error code constants on pages 59-65 of the FrameMaker 10 Scripting Guide.  Individual functions that set error codes have detailed information within the function documentation.
      What sort of things cause an error code to be set? For example, you might
      • Attempt to set a value for  a read-only property. (Constants.FE_ReadOnly)
      • Attempt to get the value of a property that the object in question does not possess. (Constants.FE_BadPropType)
      • Pass an invalid object identifier to a function. (Constants.FE_BadObjId)
      • Pass the wrong object type of a function (Constants.FE_NotFrame, is one possibility)
      • Request an operation that cannot be carried out.(Constants.FE_BadOperation)

      Tuesday, October 25, 2011

      Ungrouping graphics

      Ungrouping a graphic is just a matter of setting its GroupParent property to zero.

      graphic.GroupParent = 0;

      The graphic being ungrouped may itself be group.  Ungrouping its members is easy, however, as when looping through all of the graphics in a frame, you find both ordinary graphics and groups.

      This code ungroups any grouped graphics within an anchored frame by setting all GroupParent properties to zero.

      group = doc.NewGraphicObject(Constants.FO_Group, aFrame); 
      graphic = aFrame.FirstGraphicInFrame;
      while (graphic.ObjectValid()) {
              graphic.GroupParent = 0;
              graphic = graphic.NextGraphicInFrame
              }

      Grouping Graphics

      The following example shows how to group graphic objects within an anchored frame. It works on the selected graphic. If two or more graphics are selected, it asks the user to select just one.

      Recall that grouped graphics within FrameMaker can be manipulated as a unit.  While this script works with anchored frames, it could have worked with unanchored frames or both.

      For example, assume that the selected graphic contains the following shapes, each ungrouped as indicated by the fact that they can be selected individually.

      The script groups them into a graphic that can be worked with as a unit as indicated by the change in the graphic handles.

      The script first determines if there is in fact a selected graphic using the document property FirstSelectedGraphicInDoc. If there is indeed such a graphic, it then determines if that graphic is an anchored frame.

      Once a selected anchored frame is detected, the script creates a group object using the document method NewGraphicObject(). It passes in the object type and the ID of an anchored frame.

      The script them loops through the list of graphics in the frame. Each object found is assigned the group parent just created. When the script completes the graphics within that frame are part of a single group.

      doc = app.ActiveDoc;
      aFrame = doc.FirstSelectedGraphicInDoc;
         
      if (!aFrame.ObjectValid()) {
          Alert("Select an anchored frame and try again.", Constants.FF_ALERT_CONTINUE_WARN);
          }
      else if (aFrame.type != Constants.FO_AFrame) {
          Alert("Selected object is not an anchored frame. Adjust your selection and try again.",
          Constants.FF_ALERT_CONTINUE_WARN);
          }
      else {  // we have an anchored frame
          group = doc.NewGraphicObject(Constants.FO_Group, aFrame);
          graphic = aFrame.FirstGraphicInFrame;
          while (graphic.ObjectValid()) {
              graphic.GroupParent = group;
              graphic = graphic.NextGraphicInFrame
              }
           Alert("All graphics in this anchored frame have been grouped.",
              Constants.FF_ALERT_CONTINUE_NOTE);
          }

      NOTE: If there are already grouped graphics within the anchored frame, they are treated no differently than the rectangle, ellipse or oval. The group becomes a nested group.

      NOTE: Although they are graphics, anchored frames cannot be grouped. If there was a nested anchored frame, setting its group parent would have no effect.

      Friday, October 21, 2011

      Import by Reference

      Two important pieces of information about any imported graphic are :
      • The full pathname of the imported file.
      • The DPI at which the file was imported. 
      The following code uses  Err() to display this information in the FrameMaker console. 

      The file name is just a string while the DPI is an integer. To display it in the console, requires that it be converted to a string. 

      var doc= app.ActiveDoc;
      ListGraphics (doc);

      function ListGraphics(doc)
      {
           var graphic = doc.FirstGraphicInDoc;
           while (graphic) {
               if (graphic.type == Constants.FO_Inset) {
                   Err(graphic.InsetFile);
                   Err("     ");
                   Err(graphic.InsetDpi + "");
                   Err("\n");
                   }
               graphic = graphic.NextGraphicInDoc;
            }
       }

      Here is the output from a file with just one imported graphic:


      Adding the code line graphic.InsetDpi = 2400; within the loop, resets the image's DPI making it smaller. The console now shows:


      NOTE: The console does not get cleared between successive runs of your script. Closing it clears the content.

      Thursday, October 20, 2011

      A Peculiar Fact about Frame Graphic Object Properties

      If you poke around in the ESTK documentation, you will find that objects such as FO_Inset and FO_AFrame have properties such as ArrowLength, ArrowScaleFactor, and others that seem not to fit the object in question.

      These are some of a large set of common graphic object properties. (This fact is clearer in the FDK documentation if you care to take a look at it.) All graphic objects have these properties but in some their value has no meaning. 


      Wednesday, October 19, 2011

      Counting Insets

      Insets are just imported images. They can be still images or video. From the point of view of the FDK or ESTK scripting, they are a subtype of graphic object.

      The list of all graphics in a document is an unordered list. The first graphic in the list is found using the document property  FirstGraphicInDoc. Subsequent graphics can be found using the graphic object property NextGraphicInDoc.

      Note: A single insert can have multiple facets. 

      Note: The only graphics you can work with in flow order are those that are anchored in text. It is possible to work with selected graphics, graphics on a page by page basis, or those within a particular frame.


      Rectangles and other shapes that can be created with the FrameMaker built-in drawing tool are also graphic objects as are text frames both anchored and unanchored. Thus, graphic objects have a type property that makes it possible to distinguish between the differing types.

      This script defines a function to count the graphics in the document of interest,

      var doc= app.ActiveDoc; 
      var count = CountGraphics (doc); 
      Alert(count, Constants.FF_ALERT_CONTINUE_NOTE);

      function CountGraphics(doc)
      {
          var count = 0;
          var graphic = doc.FirstGraphicInDoc;
          while (graphic) {
               if (graphic.type == Constants.FO_Inset)
                  count++;
               graphic = graphic.NextGraphicInDoc;
          }
          count = count+''; //concatenate empty string to convert
          return (count);   
       }

      Where's the text?

      Having delved into paragraphs and paragraph formats, text might seem like the next logical topic. I am going to defer the discussion of text for a while for a number of reasons.

      First, in FrameMaker documents, text can appear in paragraphs or in text lines.  Text lines are actually a type of graphic object. So graphics need to come before text.

      Text also contains anchored objects such as markers, tables, anchored frames. A basic understanding of these is helpful when working with text. 

      Finally, in the FDK at least, text is managed quite differently from everything else. The F_ApiGetText() function works with text items rather than text objects. These text items contains the actually text characters but also information on changes in text formatting. This makes working with text a bit harder than working with other document objects. I suspect the same will hold true with scripting.

      So, if you are hoping to hear about text in detail, hang onto your hats, it is coming!



      Tuesday, October 18, 2011

      Updating a Paragraph Format

      This post looks at how to update an existing paragraph format and then, having made several updates, apply those updates to all paragraphs that use that format.

      As the paragraph format exists, the script uses its name to get its object:
      var pgfFmt = doc.GetNamedObject(Constants.FO_PgfFmt, "Body");

      The script then changes several properties of the format:
      • The paragraph is set to be auto-numbered.
      • An autonumbering string is defined. (This is the definition string.)
      • Capitalization is set to upper case.
      • Change bars are turned on.
      These changes only update the paragraph format. To apply the changes to all of the existing paragraph formats, it helps to think about how an end user would accomplish this task. (File>Import Formats ... from the current document into itself with check Paragraph Formats and remove Other Format/Layout Overrides chosen.)

      In ExtendScript, this translates to the SimpleImportFormats method with the following parameters:
      var err = doc.SimpleImportFormats(doc, Constants.FF_UFF_PGF|
      Constants.FF_UFF_REMOVE_EXCEPTION);


      IMPORTANT: The FrameMaker ESTK documentation calls for ORing the constants. The FDK documentation calls for a bitwise or and, based on my observations, that appears to be correct here as well.

      Use the Portrait template in your testing or any other template that has a Body paragraph format defined. Your file must be saved or the import of formats cannot succeed. 

      var doc = app.ActiveDoc;
      var tframe = doc.MainFlowInDoc.FirstTextFrameInFlow;

      var pgfFmt = doc.GetNamedObject(Constants.FO_PgfFmt, "Body");
      if (pgfFmt.ObjectValid) {
          pgfFmt.PgfIsAutoNum = true;
          pgfFmt.AutoNumString= "<n+>. ";
          pgfFmt.Capitalization = Constants.FV_CAPITAL_CASE_UPPER;
          pgfFmt.ChangeBar = true;
         
          var formatFlags = Constants.FF_UFF_PGF | Constants.FF_UFF_REMOVE_EXCEPTIONS;
          doc.SimpleImportFormats (doc, formatFlags);
      }
      else
          Err("Format not found");





      Monday, October 17, 2011

      Working with Paragraph Formats

      A paragraph's format can updated by setting its Name property to a new value.That action changes the name of the paragraph format but it does not add a new paragraph format to the catalog. (If the new format already exists, that catalog format would be used.)

      It is possible to programmatically add new formats to the catalog using a script. Paragraph formats are "named" objects. In the FDK, F_ApiNewNamedObject() can be used to create one. In ExtendScript, you need to use NewNamedObject() and pass in the object type and the name for the new object.

      A paragraph format created in this way has a default set of properties. You can set the properties to the desired state one at a time. In a later post, I will discuss other ways to give an object a desired set of properties.

      In summary, this example creates a new paragraph format named Para and then changes any paragraph in the main flow with the format Body to the format Para

      var doc = app.ActiveDoc;
      var tframe = doc.MainFlowInDoc.FirstTextFrameInFlow;
      var pgf = tframe.FirstPgf;

      //create a new paragraph format
      var pgfFmt = doc.NewNamedObject(Constants.FO_PgfFmt, "Para");

      while (pgf.ObjectValid()) {
          if ( pgf.Name =="Body")
              pgf.Name = "Para";
          pgf = pgf.NextPgfInFlow;
          }

      Sunday, October 16, 2011

      Getting and Setting a Paragraph Property

      It is an easy change to have the count paragraphs script update the paragraph instead. In this example, all paragraph text is underlined.  All you need to know to make this change is the name of the Underlining paragraph property and its possible values:
      • Constants.FV_CB_NO_UNDERLINE
      • Constants.FV_CB_SINGLE_UNDERLINE
      • Constants.FV_CB_DOUBLE_UNDERLINE
      • Constants.FV_CB_NUMERIC_UNDERLINE
      Here is the updated code:

      var doc = app.ActiveDoc;

      var mainflow = doc.MainFlowInDoc;
      var tframe = mainflow.FirstTextFrameInFlow;
      var pgf = tframe.FirstPgf;
      while (pgf.ObjectValid()) {
          pgf.Underlining = Constants.FV_CB_SINGLE_UNDERLINE;
          pgf = pgf.NextPgfInFlow;
          }

      Note: The change just made was to the paragraph and not the paragraph format. There is an asterisk next to each format name in the status area at the lower left as shown below. This reflects the fact that the format has been over-ridden.


      While you are unlikely to want to add underlining to a paragraph, perhaps you might want to remove it. To do so, first determine if the paragraph is underlined and then change the property setting as desired.

      var doc = app.ActiveDoc;

      var mainflow = doc.MainFlowInDoc;
      var tframe = mainflow.FirstTextFrameInFlow;
      var pgf = tframe.FirstPgf;
      while (pgf.ObjectValid()) {
          if ( pgf.Underlining = Constants.FV_CB_SINGLE_UNDERLINE)
              pgf.Underlining = Constants.FV_CB_NO_UNDERLINE;
          pgf = pgf.NextPgfInFlow;
          }

      Note: This script removes any underlining not applied with a character format. Underlining that stems from the application of a character format remains as is.

      Saturday, October 15, 2011

      Counting Paragraphs in the Main Flow

      While there may be times when you need to work with all of the paragraphs in a document, it is much more likely that you want to get the paragraphs in a few in the order in reading order. You could, if you wish, get all of the paragraphs in all of the flows in order. This post deals with the most likely scenario: getting all of the paragraphs in the main flow in the order in which they would print.

      What is the main flow? The main flow is just like any other named flow in a document. It is the default flow for the language version use. Thus:
      • When a table of contents or index is generated, the generated text is put into the main flow of the the generated document.
      • When the Compare Documents command is run, the summary text is placed in the main flow.
      For English documents, the default flow is Flow A. (If there are several autoconnect flows in the document with the default flow tag, the main flow is the one in the backmost text frame on a Right master page.)

      The FDK provides a document property that allows plug-in to get the main flow easily: FP_MainFlowInDoc. In ExtendScript, it is thus MainFlowInDoc.

      The FirstPgf and LastPgf properties allow scripts to locate the first and last paragraphs in a flow but, oddly enough, these are not flow properties but text frame properties. So to find the start (or end) of this list, you need to get the first text frame within the main flow (or the last text frame if you wish to navigate from bottom to top.) Use the FirstTextFrameInFlow flow  property (or LastTextFrameInFlow flow  property).

      Note:  Text frames do not span pages. When a text frame is full, FrameMaker creates a new text frame on the next page. That frame is connected to and remains within the same flow as the original text frame.

      To get successive paragraphs, you need not worry about text frame boundaries (assuming they are of no interest to you). You can simply get the next paragraph (or previous) in the flow you are traversing.


      var doc = app.ActiveDoc;
      var count = 0;

      var mainflow = doc.MainFlowInDoc;
      var tframe = mainflow.FirstTextFrameInFlow;
      var pgf = tframe.FirstPgf;
      while (pgf.ObjectValid()) {
          count++;
          pgf = pgf.NextPgfInFlow;
          }

      count = count+''; //Concatenate empty string to convert
      Alert(count, Constants.FF_ALERT_CONTINUE_NOTE);

      Now if I run the script on the empty Portrait template I get the following:


      The end of flow marker counts as a paragraph. The paragraphs on the master and reference pages as well as in headers and footers, are no longer counted.

      Try it yourself on a longer document with multiple paragraphs of text in the main flow.












      Thursday, October 13, 2011

      Counting Paragraphs in a Document

      You may have considering counting words but who wants to counts paragraphs? Perhaps no one. But to count paragraphs, you have to find them all. Therefore counting paragraphs provides a simple exercise in traversing a list. Once the paragraphs are counted/found, something more interesting can be learned about them or done to them.

      Note:  You can access a word counting utility from the File>Utilities>Document Reports command. This utility is implemented as an FDK plug-in.

      Start by getting the active document and by setting the count variable to 0. Next get the first paragraph in the list of unordered paragraph. It is a document property. If you start typing from memory, the ESTK nicely prompts you with the possible completions. (NICE!)



      The scripts calls for a while loop. Stop when Frame runs out of paragraphs. Here is where the FDK programmer in me gets into trouble. I am tempted to write while (pgf) assuming that pgf goes to zero when there are no more. That does not hold in ExtendScript. (If you goof like I did you will end up with FrameMaker in an infinite loop. At that point kill it using the Windows Task Manager.)

      while (pgf.ObjectIsValid()) determines whether the object exists in the current document.

      Each time through the loop the count is incremented, the attempts to find a next paragraph. Once again, the auto-completion feature in the ESTK is helpful.

      Now all that remains is to report the count.

      count = count+''; //concatendate empty string to convert
      Alert(count);

      The completed script is shown here:

      var doc = app.ActiveDoc;
      var count = 0;

      var pgf = doc.FirstPgfInDoc;
      while (pgf.ObjectValid()) {
          count++;
          pgf = pgf.NextPgfInDoc;
       }

      count = count+''; //Concatenate empty string to convert
      Alert(count);




       If you run this script with the Portrait template, you get the following:


      Think of all of those master and reference page paragraphs and you will understand how the number got to be so large.

      Wednesday, October 12, 2011

      Paragraphs, Ordered and Unordered

      Frame documents contain paragraphs, graphics, flows, frames, and other stuff. With the exception of elements (as in XMLtype elements in structured documents), these things are organized into lists.

      Some of these lists are ordered and some have no predictable order. Consider paragraphs. Every FrameMaker document has a list of paragraphs that is unordered. At first glance, you may be surprised that this list has no order but a bit of reflection reveals that paragraphs have order within flows and not within documents. Thus a script can get the paragraphs within a specific flow in the order in which they appear in the document or it can get the paragraphs in the document (a larger set) in an unpredictable order.

      What can you do with paragraphs that are out of order? You can do just about anything that can be done to a paragraph that does not depend upon its specific location within the document.

      If you were to look at the Frame Developer Kit (FDK) documentation, you would see that there is no table that separates the ordered and unordered lists. How can you tell which is which? The simplest method is to check to see if the list is linked backward and forward (that is, if there are next and prev properties). Such a list is ordered. A list with only a next property is unordered.

      Lets get specific using paragraphs as an example. (To look this up yourself, see the section for FO_Pgf in the FDK Programmer's Reference.) In the FDK the paragraph object has these "object pointer" properties:
      FP_NextPgfInDoc  //Next paragraph in the unordered list for this doc
      FP_NextPgfInFlow //Next paragraph in the ordered list for this flow
      FP_PrevPgfInFlow //Previous paragraph in unordered list for this flow

      So can you access the start of these lists?
      • In the case of the unordered list, the first is a document property, FP_FirstPgfInDoc.
      • In the case of the ordered list, the first is the text frame property FP_FirstPgf. There is also a FP_LastPgf property allowing for traversal of the list from last to first as well as first to last. (If you were expecting a flow property, you are likely not alone. Paragraphs within flows will get a more detailed treatment in a later entry.)
      All of these properties have an analog in the scripting world. If you rely on rule of thumb given in the FramemMaker Scripting Guide, you can easily calculate the appropriate name.
      Remove the FP_ prefix before using the properties in scripts.
      FirstPgfInDoc //First paragraph in unordered list for this doc
      NextPgfInDoc  //Next paragraph in the unordered list for this doc

      FirstPgf      //First paragraph in ordered list for this flow
      LastPgf       //Last paragraph in ordered list for this flow
      NextPgfInFlow //Next paragraph in the ordered list for this flow
      PrevPgfInFlow //Previous paragraph in unordered list for this flow

      Too many years of FDK programming led me to revert back to FDK docs and remembered knowledge. If you are completely new to all of this, you can skip FDK-think and go directly to the Scripting Guide. Chapter 4 contains the Object Reference. Here you will also learn the important fact that the data type for these properties is Pgf. (In the FDK, it would be F_ObjHandleT.)

      Hooking up the Toolkit

      To use the ExtendScript Toolkit with FrameMaker, do the following:
      1. Launch ExtendScript. The following window appears.
      2.  In the popup at the upper-right choose Adobe FrameMaker 10.
      3. Click on the red icon (two links, one broken) to connect the toolkit to FrameMaker 10.
      4. If FrameMaker is already running, ExtendScript connects to the running instance. If not, the following dialog appears. Click Yes to connect.
         
      5. To verify that your installation works, enter the code for Hello World in the code window. Click on the run icon (sideways green triangle) to launch the script.
      6. Locate the FrameMaker application to view the alert. (Yes, it is annoying that it does not automatically come to the top.)

      Tuesday, October 11, 2011

      The Active Document

      One way or another, nearly all FDK plug-ins and ESTK scripts, work with documents. (The most likely exception to this rule might be a script that worked strictly with book level information.)

      Before a script can do anything with a document two things need to be true:
      • The document must be open. (Open and visible on screen are not the same thing.)
      • The script need to grab hold of the document's object or, in FDK parlance, identifier.

      Once you have found the object that represents a document, you can access the objects that make up that document (the paragraphs, graphics, flows, frames and so forth).

      It is possible to open documents using a script but for now, lets work with a document that is already open. In fact, lets work with not just any open document but with the document that has the user focus. This is the document where words typed at the keyboard would go and to which any other user action would apply. That document is known as the active document.

      Use the following code to get the document object:

      var doc = app.ActiveDoc; 

      NOTE:  app is the application object. Its function is essentially the same as that of the FV_SessionId identifier in an FDK plug-in.

      Use the following code to determine the document's file name and then display that name using an alert:

      var name = doc.Name;
      Alert(name, Constants.FF_ALERT_CONTINUE_NOTE);



       Remember, your active document must be saved to have a name!




       

      Getting Ready to Use FrameMaker with the ESTK

      You could, if you are truly brave, author your .jsx script files in any text editor. You could then run them using the Scripts menu Run command. But the rest of us will want to use the ExtendScript Toolkit. That means downloading the ExtendScript Toolkit OMV. (OMV is short for Object Model Viewer).

      This means downloading and installing the OMV file. You can navigate to the download using the built-in Help. Look for the  FrameMaker Developer Center. For convenience, I am including a link here:

      FrameMaker 10 ExtendScript Toolkit OMV

      The setup instructions tell you where to locate the OMV file. In my default FrameMaker 10 installation, I found that I had to create the directories named. When installing the OMV, be sure to place it in the location that matches the version of the ESTK you intend to use.

      Here is my OMV file:




      Monday, October 10, 2011

      Hello Again

      Simple though it may be, the Hello World code has one cryptic aspect: why the second parameter with the value "1"? The comment tells part of the tale. But it omits the fact that, in this case, Cancel is the default option.

      Alert("Hello World!", 1); /* FM Alert With OK and Cancel buttons */



      If I change the parameter to "0", I get an alert box with OK and Cancel buttons with OK as the default.



      Consulting the documentation (page 649-650 of the Scripting Guide) reveals that the first parameter is the message to display and the second determines the type of message box uses. A less cryptic version of Adobe's Hello World is shown here using the appropriate constant value:

      Alert("Hello World!", Constants.FF_ALERT_CANCEL_DEFAULT);

      My own Hello World might better be written:

      Alert("Hello World!", Constants.FF_ALERT_OK_DEFAULT);

      There are actually six different alert box types, each with a corresponding constant. The number is easier to type but the constant, once learned, will make your code clearer and more maintainable. 

      By the way, the Alert() method returns 0, if the user clicks OK and -1 otherwise.  

      Sunday, October 9, 2011

      Running a (Very Simple) Script

      FrameMaker 10 ships with a number of sample ESTK scripts. You can run these scripts using the File>Script>Run command. After selecting that command, you are prompted to locate the script of interest. The samples can be found in your FM install directory under Samples/ScriptsAndUtilities.


      Double click on the Hello World folder to display the .jsx file that is the actual script.



      Click on Open to view  the dialog box opened by the script. This is the result of the trivial script, Alert("Hello World!", 1);

      Document Architecture

      To create ExtendScript scripts that  manipulate the content of FrameMaker documents, you need to know the parts that make up a FrameMaker document and how they inter-relate.

      If you are familiar with FrameMaker from an end-user perspective, you already know something about how the FDK, and ExtendScript access the content within FrameMaker documents. To write a script, however, you have to stop thinking about how an end user might accomplish a task and think instead about how a program might access an object that it wants to inspect or change.

      Here are some things you need to know before getting started with scripting:
      • FrameMaker edits documents. Groups of documents can be aggregated into books. 
      • Documents can be structured or unstructured. (Structure in FrameMaker is really just an overlay on an unstructured FrameMaker document. More about that later.)
      • The name FrameMaker is a reference to the idea of a frame as a container for document content. All content is a FrameMaker document is ultimately part of  some frame.
      • Frames  can contain flows. Flows can be thought of as the containers for rivers of text. Such text can span page boundaries.
      • Frames can be anchored or unanchored. Anchored frames are tied to a position in text. Unanchored frames are placed in a specific coordinate position on a page.
      • Frames contain graphic objects and other frames. These can be the circles, rectangles, and other shapes you might draw with the built-in graphics tools. Graphics also can be anchored and unanchored text frames (containers for sequences of paragraphs that can themselves contain text), text lines (such as the callout text you might use in an image), insets (imported graphics), groups of graphic objects (created using the Group tool), equations created with the Equation Editor.
        Why do you need to know what objects exists and how they inter-relate? Very simply, to work with these objects using the ExtendScript Tool Kit (ESTK), you need to locate them first. Once you have "hold" of an object, you can learn about its characteristics (properties) and change them as desired.That is the key to the usefulness of the ESTK.

          Saturday, October 8, 2011

          Easier to Code? Easy to Code?

          The Adobe FrameMaker 10 Scripting Guide states that FrameMaker ExtendScript scripts "act as wrappers to the FDK and hide the complexity of using FDK functions from users." From this we can also infer that these scripts are no more powerful than the plug-ins one might create with the FDK. Thus, for an FDK programmer to migrate to this new scripting tool, there should be (after some learning curve) a greater ease of tool development. What might account for this ease?

          The scripts are created in ExtendScript, essentially an augmented version of JavaScript. That means they are interpreted rather than compiled. There is no need to mess with include files or to link libraries. That should be a boon to those just getting started although they offer no benefit to experienced FDK programmers.

          The FDK plugin development requires that you have the Microsoft Visual C++ development environment. To create scripts, you are likely to want to have Adobe's ExtendScript Toolkit. The ExtendScript Toolkit is easier to get started with but does it provide sufficient support for debugging? For now, I leave this unanswered. (I need to do some more heavy duty development work before I make a judgment.)

          What about the code? Here is Adobe's "Hello World!" written in ExtendScript: 

          Alert("Hello World!", 1); /* FM Alert With OK and Cancel buttons */

          Here is the same program written for the FDK:

          VoidT F_ApiInitialize(IntT init)
          {
          switch(init) {
          case FA_Init_First:
          F_ApiAlert((StringT) "Hello World!", FF_ALERT_OK_DEFAULT);
          break; 
          }

          At first glance, ExtendScript wins the ease of use battle hands down. (All that extra FDK code is telling FrameMaker that the alert should appear before FrameMaker shows itself to the user. The alert box produced has OK and Cancel buttons with OK as the default choice.)

          Will this advantage hold up in the face of more complex programming tasks? That is the $64,000 question. In future posts, I will be taking common FDK programming paradigms and rewriting them in ExtendScript. With a few of those under my belt, I will have a more informed opinion.

          Friday, October 7, 2011

          There's a New Scripting Tool in the Box

          The introduction ExtendScript support in FrameMaker 10 offers new possibilities for augmenting the built-in capabilities of the Adobe FrameMaker product. FrameMaker has long been extensible but prior to release 10, extending FrameMaker meant mastering the intricacies of the Frame Developer Kit (FDK).

          The FDK extends the capabilities of FrameMaker by allowing for the creation of C language plug-ins. Such plug-ins have great power as they can be used to mimic just about any action a user might take, thus offering vast automation potential. FDK plug-in also can be integrated into the FrameMaker menu structure making FDK commands operate as if they were part of the out-of-the box product.

          Developing such plug-ins requires knowledge of the C programming language. The development process entails writing, compiling, and debugging code using Microsoft Visual C development tools. The FDK developer needs to master a voluminous set of objects, properties, functions along with complex data structures. As C has no built-in garbage collection, FDK programmers also have to deal with memory management, a task which when bungled is all but sure to lead to unpredictable and catastrophic crashes.

          The challenges of FDK plug-in development meant that only a small fraction of Frame users attempted plug-in creation. Plug-ins became something large corporations commissioned and less well-heeled users purchased from third-party developers.

          The addition of ExtendScript scripting to FrameMaker 10 not only makes extending FrameMaker potentially easier, but it also moves FrameMaker further along the road to full-fledged integration with the larger set of Adobe products. (Adobe acquired FrameMaker in 1995.) A major step came in release 9 with a major UI redesign that made FrameMaker look and feel more like Adobe products such as Photoshop and InDesign. I cannot help but believe that this bodes well for FrameMaker's future and for its ability to "play well" other Adobe products.

          My focus here, however, is not on interoperability but on the promise implicit in ExtendScript support that FrameMaker users now will be able to construct scripts that have the power of FDK plug-ins without the need for heavy-duty programming skills.

          I would be remiss not to mention that the third-party created FrameScript has long offered an alternative to FDK plug-in development. (See http://www.framescript.com for more information.) I will not, however, be discussing FrameScript. My focus is strictly on taking a very close look at Adobe's ExtendScript implementation of FDK functionality.

          I have been an FDK programmer for many years. I started learning the FDK, even before its public release, as I prepared the first FrameMaker training class for Frame Technology. Having taught FDK developer classes too many times to count, in many states and several countries, and having  developed numerous FrameMaker plug-ins for corporate users, the F_Api prefix that starts of FDK functions is hard-wired in my brain.  Thus, I cannot help but view the ExtendScript toolkit from the perspective of an FDK programmer.

          But while, I may frequently reference FDK code, my intention is that anyone who is new to the ExtendScript FrameMaker toolkit will find this blog helpful. I plan to look at common-place tasks in FrameMaker customization and show how they can be scripted using ExtendScript. I will provide code examples and commentary. When I digress into areas of interest only to FDK programmers, I will provide ample warning. I will assume that you know end-user FrameMaker and that you have some scripting experience but not that you are familiar with ExtendScript or even JavaScript. I will be posting irregularly, as I come up with useful examples and information. So, please stay tuned.