Wednesday, November 30, 2011

Determining an Object's Page

The script below works with the current selection. That selection can be an insertion point, a text range or a variety of objects including text frames, other frame types, graphic objects and equations.

It begins by determining the type of object selected. It handles, text selections, a selected table, or a selected graphic.

NOTE:  If there is a text selection that spans paragraphs, the beginning of the selection is used in determining the object chosen.

If there is an object selected, the script then determines its page frame. It uses the page frame to determine the actual page number and displays it using an alert.

The real work of the script is in determining the page frame. To do so it works its way up the object chain until it finds an unanchored frame whose frame parent is null. The function contains a while loop that terminates only when the object found is a frame with no frame parent. For each object type, it moves up the chain of objects. If the object found is:
  • A table, it uses the upper right most cell in the table.
  • A cell, paragraph or anchored frame, it moves up the chain to the object's text frame.
  • A text line, text frame, or a graphic shape, it moves up the chain to the object's frame parent.
The code is shown here followed by an example of its use.

function GetSelectedObject(doc) {
    var tRange, obj, type;
    obj =  null;
    tRange = doc.TextSelection;
    obj = tRange.beg.obj;
    if (!obj.ObjectValid()) {
        obj = doc.SelectedTbl;
        if (!obj.ObjectValid()) {
            obj = doc.FirstSelectedGraphicInDoc;
        }
    }
    return obj;
}

function FindPageFrame(doc, obj) {
    var frame, row, colNum, cell, objType;

    while (obj.ObjectValid()) {
        frame = obj;
        objType = obj.constructor.name;
        if (objType === "Tbl") {
            row = obj.TopRowSelection;
            colNum = row.RightColNum;
            cell = row.FirstCellInRow;
            obj = cell;
        }
        else if (objType === "Cell" || objType === "Pgf" ||
             objType === "AFrame") {
             obj = obj.InTextFrame;
         }
         else if (objType === "TextLine" ||  objType === "TextFrame" ||
                 objType === "UnanchoredFrame" || objType === "Arc" ||
                 objType === "Ellipse" || objType === "Group" ||
                 objType === "Inset" || objType === "Line" ||
                 objType === "Math" || objType === "Polygon" ||
                 objType === "Polyline" || objType === "Rectangle" ||
                 objType === "RoundRect") {
            obj = obj.FrameParent;
         }
    }//end while
    return frame;
}

var doc, frame, obj, page, pageNumStr;

doc = app.ActiveDoc;
obj = GetSelectedObject(doc);
if (obj.ObjectValid()) {
    frame = FindPageFrame(doc, obj);
    if (frame.ObjectValid()) {
        page = frame.PageFramePage;
        pageNumStr = page.PageNumString;
        Alert("Object is on page " + pageNumStr, Constants.FF_ALERT_CONTINUE_NOTE);
    }
}
else
{
        Alert("No selection",  Constants.FF_ALERT_CONTINUE_NOTE);
 }


Selection is a text line


Monday, November 28, 2011

Determining an Object's Type

If you find an object by navigating a list such that the lists of paragraphs, markers, pages, and the like, you know in advance what object type you will find. But what if you get an object because it represents, for example, the user selection? In such a case, there is uncertainty as to what you have found but to do anything with that found object, you need to definitively determine its object's type.

The FDK provides the function F_ApiGetObjectType() which returns the FO_ type of an object but there is no analog in ExtendScript. Your best option is to use the object’s constructor property name. (Thanks to Ian Proudfoot for this tip.)


The following script updates the find selection type script to determine the object type of the currently selected object, if any.

function GetSelectedObjectType(doc) {
    var tRange, obj, type;
 
    tRange = doc.TextSelection;
    obj = tRange.beg.obj;
    if (!obj.ObjectValid()) {
        obj = doc.SelectedTbl;
        if (!obj.ObjectValid()) {
            obj = doc.FirstSelectedGraphicInDoc;
        }
    }

    if (obj.ObjectValid()) {
        type = obj.constructor.name;
    }
    else {
        type = "None";
    }

    return type;
}

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






Wednesday, November 23, 2011

Working with Selections

My plan is to write a script that determines the page location of the current user selection. This means figuring out what, if anything, in the document of interest is selected.

This script makes use of three document properties:
  • TextSelection returns the text range for the current text selection.
  • SelectedTbl returns the identifier of the selected table.
  • FirstSelectedGraphicInDoc returns the identifier of the first in a list of selected graphics objects. NextSelectedGraphicsInDoc, a property of the first object found, takes you to the next object in the list. 
Some things to note:
  • If there is an insertion point or a selected text range within a table cell, that table is not selected. For a table to be selected, the entire table, the entire table title, or an entire cell within the table must be selected.
  • The list of selected graphics in a document is an unordered list. This means that there is no telling which of a list of selected graphics might be returned by FirstSelectedGraphicInDoc . It might be the one the user selected first but do not count on that fact.
//returns a string indicating the type of object currently selected
function GetSelectedObjectType(doc) {
    var tRange, obj;
    tRange = doc.TextSelection;
    obj = tRange.beg.obj;
    if (obj.ObjectValid()) {
        return "Text Selection";
    }
    obj = doc.SelectedTbl;
    if (obj.ObjectValid()) {
        return "Selected Table";
    }
    obj = doc.FirstSelectedGraphicInDoc;
    if (obj.ObjectValid()) {
        return "Selected Graphic";
    }
    return "No selection";
}

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



Tuesday, November 22, 2011

Things that Reside in Text Frames

The previous post discussed the fact that graphic objects have a FrameParent property that returns their containing frame. Objects that can appear in text have a similar property that returns their containing text frame. These objects are markers, cross references, variables, anchored frames, and text insets of various types
    While cross references, variables, and text insets contribute to the document content, markers do not. Anchored frames do not contribute to paragraph content although they can add considerable document content. For that reason, markers and anchored frames exist at a location in text while the other three have an associated text range that delineates the start and end of their content. Thus there are the following object/property pairings:
    • Markers and anchored frames have the TextLoc property that returns an offset from the start of the containing table cell or paragraph.
    • Cross references, variables, and the various text inset objects (Ti*) have the TextRange property that provides a starting and and ending text location.
    Once you know a text location or range, you can easily get the identifier of the paragraph or table cell that starts (or ends) the text location. Those objects have the InTextFrame property that takes you to the (graphic object) text frame.

    NOTE: Anchored frames have the InTextFrame property as well as the TextLoc property allowing you to go directly learn the idenfier of their text frame when desired.



    Monday, November 21, 2011

    Graphics and their FrameParent

    The page frame that holds all of a page's content is a special instance of a graphic object. But what exactly are graphic objects?

    In FrameMaker, anything that has handles when selected is a graphic object. This includes the following object types:
    • AFrame
      An anchored frame that is tied to a specific text location.
    • UnanchoredFrame
      A frame that is placed directly on the page.
    • Line, Arc, Rectangle, Ellipse, RoundRect, Polyline, Polygon
      The simple geometric shapes that can be created with the FrameMaker drawing tools
    • Group
      An invisible object that allows a set of other graphic objects in the same frame to be moved or otherwise managed as a set.
    • TextLine
      A line of text created with the FrameMaker drawing tools.
    • TextFrame
      Container for a text flow.
    • Inset
      An imported graphic or file that was imported into a FrameMaker document.
    • Math
      An equation created with the FrameMaker equation editor.
    All graphic objects appear within frames, whether anchored or unanchored. A graphic's FrameParent property provides the link to the containing frame. The page frame, as it has no containing frame has a FrameParent that is null.

    Sunday, November 20, 2011

    The Page Frame

    Pages object do not contain the text and graphics that appear on them. Instead, pages have a PageFrame property that specifies the identifier of the page frame that contains those objects. (Everything that prints in a FrameMaker document is within one page frame or another.)

    The page frame is an invisible unanchored frame whose dimensions match that of the page. A page frame has the object type FO_UnanchoredFrame. If you have a page object, you can get to the page frame using the PageFrame property. There is a corresponding property PageFramePage that takes you from the page frame back to the page.

    The following illustration shows this relationship. It uses FDK FP_ prefixes for property names as I have recycled it from some old FDK training materials. Drop them to get the corresponding ESTK names.  All page types have this property, not just body pages.



    These properties are important if you need to determine what page an object appears on or conversely what objects appear on a given page. I will go into these problems in more detail in my next several posts.

    Saturday, November 19, 2011

    Navigating by Page

    Moving through a document's pages is a straight-forward process. The first page, whether a body, master or reference, is a document property:
    • FirstBodyPageInDoc
    • FirstMasterPageInDoc
    • FirstRefPageInDoc
    As pages are ordered, you can also start at the end:
    • LastBodyPageInDoc
    • LastMasterPageInDoc
    • LastRefPageInDoc
    Each page has a PageNext and PagePrev property that allows you to move to the next page in the sequence. Remember that these are page properties.

    Once you locate the page of interest, you can make it the current page. The following script makes the first reference page the active page.

    var doc, firstRef;
    doc = app.ActiveDoc;
    firstRef = doc.FirstRefPageInDoc;
    doc.CurrentPage = firstRef;


    If you run this script on the Portrait template, the following page will appear on screen:


    Thursday, November 17, 2011

    Working with the Current Page

    You can easily locate a document's current page using the CurrentPage document property. The current page is the one that is currently displayed on the screen and not the one with the insertion point.

    Once you have the current page, you can access page number information:
    • PageNum is an integer indicating the position of the page within the document. The count starts at zero.
    • PageNumString is the number that appears in the page footer. It is defined by the variable building block <$curpagenum>.
    The script below defines two functions:
    • GetPageNumber() returns the relative page number within the document.
    • GetPageString() returns the portion of the printed page number determined by the <$curpagenum> building block. (In my experiments, redefining the Current Page # variable did not impact the value returned by this function.)
    Each function is called on the active document. The string printed in the FrameMaker console reflects the values associated with the page currently on the screen.

    var doc, pageNum, pageString;
    doc = app.ActiveDoc;

    function GetPageNumber(doc) {
        var page = doc.CurrentPage.PageNum;
        return page;
    }

    function GetPageString(doc) {
        var pageStr = doc.CurrentPage.PageNumString;
        return pageStr;
    }

    pageNum = GetPageNumber(doc);
    pageString = GetPageString(doc);

    Console("Offset in pages from start of document is " + pageNum);
    Console("Page number that appears on printed page is  " + pageString);

    The following output illustrates the fact that each of these functions likely returns a different value for the same document page even accounting for the difference in value type.




    Page Types

    FrameMaker employs four different page types. Three of these will be familiar to end users of FrameMaker:
    • Body pages (FO_BodyPage)
    • Master pages (FO_MasterPage)
    • Reference pages (FO_Reference Page)
    A fourth page type is the  hidden page (FO_Hidden). There is only one such page and it holds hidden conditional text. While users cannot access this page, FDK plug-ins and scripts can.

    There is no generic FO_Page object.

    Body, master and reference pages can be made visible on the screen while hidden pages cannot be viewed in this manner.

    NOTE: It is possible for a script to work with object on pages that are not currently displayed on screen.

    Wednesday, November 16, 2011

    Creating a Variable

    Variables fall into a category anchored formatted object. Tables and cross references also belong to this group. They are similar to anchored objects (markers) but creating them requires that you specify a format as well as an object type and text location.

    In the case of variables, the format is simply the name it is know by in the user interface. You can view a list by using the  Special>Variables command from the FrameMaker menu bar.



    The following script adds the Current Date (Long) at the start of the first paragraph in the main flow.

    var doc = app.ActiveDoc;
    var flow = doc.MainFlowInDoc;
    var tFrame = flow.FirstTextFrameInFlow;
    var pgf = tFrame.FirstPgf;

    function createVariable(doc, pgf, offset, type, format) {
        var tLoc, variable;
        tLoc = new TextLoc(pgf, offset);
        variable = doc.NewAnchoredFormattedObject(type, format, tLoc);
        return 1;
    }

    createVariable(doc, pgf, 0, Constants.FO_Var, "Current Date (Long)", "Index");


    Tuesday, November 15, 2011

    Creating an Index Marker

    Markers are an instance of anchored objects. As anchored objects they are tied to a text location. Creating a marker means specifying a paragraph and an offset from the start of that paragraph. You also need to provide the same information a user might when creating such a marker. That means you must specify:
    • The marker type
    • The marker text
    Use the same type name as appears in the user interface in the Marker Type pop-up menu. The marker text must also mirror that a user would enter.

    The example below creates an index marker at the start of the first paragraph in the main flow.



    var doc = app.ActiveDoc;
    var flow = doc.MainFlowInDoc;
    var tFrame = flow.FirstTextFrameInFlow;
    var pgf = tFrame.FirstPgf;

    function createMarker(doc, pgf, offset, type, text) {
        var tLoc, marker;
        tLoc = new TextLoc(pgf, offset);
        marker = doc.NewAnchoredObject(Constants.FO_Marker, tLoc);
        marker.MarkerType = type;
        marker.MarkerText = text;
        return 1;
    }

    createMarker(doc, pgf, 0, "Index", "animal:aardvark");

    Sunday, November 13, 2011

    Creating a New Paragraph

    While paragraph formats have names, paragraphs do not. They fall into a class known as series objects. A series object is any object, other than graphic object, that occurs in an ordered list. Pages and book components are also series objects.

    Call NewSeriesObject() to create a paragraph or other series object. As series objects are part of ordered lists,you need to specify the type of object to create and the identifier of its predecessor in the list.

    If you want the new paragraph to appear at the start of the flow, specify the flow object for its predecessor. For other objects, specify 0.

    //add a paragraph at the start of the specified flow
    doc.NewSeriesObject(Constants.FO_Pgf, flow);


    //add a paragraph after the paragraph specified
    doc.NewSeriesObject(Constants.FO_Pgf, pgf); 
    W9ACUGJMJ4Y9

    Updating Paragraph Format Properties Using Property List

    Once you have created a paragraph format (or other named object), you very likely will want to modify the default properties automatically set when the object was created. You could do so one at a time but this can be tedious. It can also be hard to figure out just the right values for each property.

    If your new format is similar to that of an existing format, you can use the properties of the existing format as a starting point. You can then explicitly set only those properties that are different.

    This example bases a new (or existing) format on an existing paragraph format. It changes only the underlining property.

    This script relies on the GetProps() and SetProps() methods which work with the properties of an object as a group.

    NOTE: You can base the properties of a new paragraph format on those of a paragraph. This is the case even though the sets of properties do not exactly line up. Any mismatches are ignored.

    The following function was called with:
    createPgfFmt(doc, "Heading0", "Heading1")

    /* Create or update a paragraph format with the name "newName". Set the format properties based on those of the "basedOnName" format. Change the underlining to be single.
    */
    function createPgfFmt(doc, newName, basedOnName)
    {
    //determine if the format already exists
    var newPgfFmt = doc.GetNamedObject(Constants.FO_PgfFmt, newName);
    if (!newPgfFmt.ObjectValid()) {
    //create it if necessary
    var newPgfFmt = doc.NewNamedObject(Constants.FO_PgfFmt, newName);
    if (!newPgfFmt.ObjectValid())
    return(0);
    }
    //get the based on format
    var existingPgfFmt = doc.GetNamedObject(Constants.FO_PgfFmt, basedOnName);
    if (!existingPgfFmt.ObjectValid())
    return (0);
    //Get all properties of existing format
    var props = existingPgfFmt.GetProps();
    //find underlining in the array of properties
    var index = GetPropIndex(props, Constants.FP_Underlining);
    //update the underling value
    props[index].propVal.ival = Constants.FV_CB_SINGLE_UNDERLINE;
    //update the new format's properties to match the old, save for underlining
    newPgfFmt.SetProps (props);
    return(1);
    }


    Friday, November 11, 2011

    Creating a new Paragraph Format

    Paragraphs are, in FDK parlance, named objects. Use the NewNamedObject() to create one. Pass in the type of object and the name you wish to use.

    var doc = app.ActiveDoc;
    pgfFmt = doc.NewNamedObject (Constants.FO_PgfFmt, "My Fmt");

    The newly created format has default properties.


    You can create a long list of objects the same way including colors, condition formats, and character formats. (Look for the Name property.)

    Wednesday, November 9, 2011

    Deallocating Text Items

    The short answer is you don't need to if you are using the ESTK. Read on for the full saga.

    The first words that came into my head when I learned about the ExtendKit toolkit for FrameMaker were garbage collection. Writing FDK programs is (mostly) a lot of fun but figuring out how to correctly allocate and deallocate memory is tricky and error prone. JavaScript purports to free one from those complexities.

    So I was a bit surprised to see that the Scripting Guide documentation for GetText() and methods such as GetProps(), Save() and others contain notes that are similar to that shown below.
    "Note: The returned TextItems structure references memory that is allocated by the method. Use the DeallocateTextItems() method to free this memory when you are done with using it."

    So apparently memory management is still something script writers must contend with.

    If you call GetText(), do you absolutely need to call the DeallocateTextItems() method to free the memory used when you are done? If you are getting a small number of text items, you are very likely safe if you forget or don't bother to deallocate. But if you call GetText() on a large quantity of text (possibly in  many documents), failure to deallocate can be a problem. This is also the case if a script might be called many, many times before FrameMaker is restarted.

    The FDK has the F_ApiBailOut() command which purports to direct a plug-in to stop running and give back its memory when it completes a command. I was never confident this did anything on Windows and there appears to be no analog in ExtendScript. Only Adobe knows how the scripts are implemented at that level. All one can be sure of is that when the user exits FrameMaker, any memory used by scripts is freed.

    So, I tried to be a good citizen and deallocate my text items:

    var tItems = mainflow.GetText(Constants.FTI_CharPropsChange | Constants.FTI_TextObjId |
    Constants.FTI_String);
    PrintTextItems(tItems);
    app.DeallocateTextItems(tItems);//WRONG

    But I ran into a problem. I could not find a single example of the appropriate use of DeallocateTextItems()or the other deallocate methods anywhere. and my attempt to use this function produced the following error:



    What is going on? Is this a documentation error or my error? I have yet to figure this one out.

    Sunday, November 6, 2011

    Viewing Text Items


    Use the GetText() method to work with the content of paragraphs or other objects that contain text. The method takes a single parameter that indicates the type of text items you are interested in. There is a long list of possibilities. See the Scripting Guide for full details. The method returns an array of text items.

    The example that follows request text items in the main flow and request the following item types:
    • Indicators of places where the character properties of the text change. (FTI_CharPropsChange)
    • The identifier of the paragraph or text line to which the offset of each text items in the array is calculated. (FTI_TextObjId)
    • The strings that make up the text. (FTI_String)
    Why is it important to know the identifier of the object containing the text? While the code queries the flow, making any changes requires knowing the identifier of the paragraph (or text line) in which the text resides.

    What about the text strings? If I run my script on a paragraph that has no changes to character properties will I get a single string? The answer is maybe. The FDK has a string limit size of 255 so no string will be larger than 255 characters. But, there is no guarantee that the text items will have the largest supported string size consistent with the information requested. In fact, if you call GetText() on a file that has been heavily edited, it is likely that you will get back a number of smaller text strings. There may be other reasons for this phenomenon that are not apparent to those not privy to the internal workings of the FrameMaker product. In short, GetText() gives you all of the text strings in their order of appearance but there is no guarantee that the number of text items is minimal.

    Imagine that GetText() is run, as shown in the code that follows, on the document below. The call is followed by a request that the text items found be displayed in the FrameMaker console.

    NOTE: The constant values or OR'd to produce the appropriate flag parameter.

    var tItems = mainflow.GetText(Constants.FTI_CharPropsChange | Constants.FTI_TextObjId |
    Constants.FTI_String); 
    PrintTextItems(tItems);

    The file



    The text items


    The first number displayed is the offset from the paragraph start. In the case of FTI_TextObjId, you see the actual object identifier. In the case of FTI_CharPropsChange, the hex value indicates the type of change that took place. FTI_String items are followed by the actual string text.

    Saturday, November 5, 2011

    A very important FDK include file

    I have found that using the Scripting Guide, it is sometimes not easy (or maybe even possible) to map constant names to their numeric values.

    You can easily find such information if you have a copy of the FDK include file fapidefs.h. To get it you need to download the FDK but I think you will find it is worth the trouble to do so.

    NOTE: Go to http://www.adobe.com/devnet/framemaker.html for the download. Look in the FDK10/include directory for the file.

    Here you will find such things as the values for error codes, hex values for Frame flags (FF_*), scripting values (FS_*), initializations (FA_Init*,) notificaitons (FA_Note*) and more. These will help you decipher values you see in the ExtendScript data browser. As the file is easy to scan, it may also give you a sense of what is possible in certain situations. Just be aware that the file may contain unsupported features. In addition, I have not verified that the FrameScript wrappers for the FDK support everything.

    fapidefs.h is a code file intended to be viewed in Microsoft Visual C++ but, as it is a text file, you can view it in any editor that can read plain text including, of course, FrameMaker.


    I include some snippets from this file below just to show you its format.
    ...
    /*Settings for FA_errno */
    #define FE_Success            0   /* All's well */
    #define FE_Transport          -1  /* Communications is falling apart */
    #define FE_BadDocId           -2   /* Illegal Document or Book */
    #define FE_BadObjId           -3   /* Illegal Object */
    ...
    /* Initializations */
    #define FA_Init_First             1  
    #define FA_Init_Subsequent        2
    #define FA_Init_TakeControl       3  
    #define FA_Init_DocReport         4  

    /* Notifications */
    #define FA_Note_PreOpenDoc        1  
    #define FA_Note_PostOpenDoc       2  
    #define FA_Note_PreOpenMIF        3  
    #define FA_Note_PostOpenMIF       4  
    #define FA_Note_PreSaveDoc        5  
    #define FA_Note_PostSaveDoc       6  
    #define FA_Note_PreSaveMIF        7   
    ...

    Friday, November 4, 2011

    Preventing Fireworks

    A script that makes numerous changes to a document can create a flurry of screen activity that can be disturbing to a user. Successive cuts, copies, pastes, formatting changes, and the like can create "screen fireworks." Fortunately there is a way to save the user from viewing the changes as they happen: set the app property Displaying (session property in the FDK) to false.

    You should also consider setting the app Reformatting property to false. This saves time as it tells FrameMaker not to reformat pages after each edit.

    IMPORTANT: Don't forget to turn Displaying and Reformatting back to true when you are finished or you will leave the end user in the lurch!

    When you are done, you need to explicitly reformat and then redisplay the document.

    app.Displaying = false; //turn off screen redisplay
    app.Reformatting = false; //turn off document reformatting

    /*this does 100 pastes of the clipboard contents but any screen 
    intensive operation could appear here */
    for (var i=0; i <100; i++)
        doc.Paste(0); //paste the text
    app.Displaying = true; //turn back on
    app.Reformatting = true;

    doc.Reformat();//update the formatting in the document you changed
    doc.Redisplay();//redisplay the document

    Thursday, November 3, 2011

    Cut, Copy, Paste

    Once you are able to make a selection in a document, you are able to use the same cut, copy, and paste commands an end user of FrameMaker has available. This turns out to be very powerful if you need to rearrange or remove text. For example, cutting and pasting a row in a table is far easier than deleting its components and then attempting to reconstruct them. The same is true if you need to rearrange elements in a structured document.

    The Cut(), Copy(), and Paste() command all work with the current selection and each takes a single parameter: flags that indicate how the operation should proceed. A flag of 0 indicates that the operation should proceed without any interactive alerts or warnings.

    A simple copy and paste example follows.

    IMPORTANT:  I took care to ensure that my test document has a second paragraph. In the real world, that condition might not hold. You need to verify that the paragraph exists before attempting the paste. I will discuss how to create a new paragraph in an upcoming past.

    //get the first paragraph in the active document
    var doc = app.ActiveDoc;
    var mainflow = doc.MainFlowInDoc;
    var tframe = mainflow.FirstTextFrameInFlow;
    var pgf = tframe.FirstPgf;

    var tRange= new TextRange(); //create a text range
    //set the range to be the first paragraph
    tRange.beg.obj = pgf;
    tRange.beg.offset = 0;
    tRange.end.obj = pgf;
    tRange.end.offset = Constants.FV_OBJ_END_OFFSET;
    doc.TextSelection = tRange;
    doc.Copy(0); //copy the selected range
    //select insertion point at the start of the next paragraph
    tRange.beg.obj = pgf.NextPgfInFlow;
    tRange.beg.offset = 0;
    tRange.end = tRange.beg;
    doc.TextSelection = tRange;
    doc.Paste(0); //paste the text


    Before paste
    After paste

    Wednesday, November 2, 2011

    Selecting Text

    Once you understand text ranges, selecting text is easy. The following script selects the first paragraph in the active document.

    Setting an insertion point is also easy; just make the beginning and ending text locations the same. (In other words, an insertion point is a text range and not a text location.)

    Note the use of the constant FV_OBJ_END_OFFSET to determine the end of the paragraph. It includes all contents and the end of paragraph marker.

    //get the first paragraph in the active document
    var doc = app.ActiveDoc;
    var mainflow = doc.MainFlowInDoc;
    var tframe = mainflow.FirstTextFrameInFlow;
    var pgf = tframe.FirstPgf;

    var tRange= new TextRange(); //create a text range
    //set the range to be the first paragraph
    tRange.beg.obj = pgf;
    tRange.beg.offset = 0;
    tRange.end.obj = pgf;
    tRange.end.offset = Constants.FV_OBJ_END_OFFSET;
    //make the selection
    doc.TextSelection = tRange;

    Here is a paragraph (with a quote from Lewis Carroll), selected using the script.


    Tuesday, November 1, 2011

    Error Code When Deleting a Range and Some Buggy Behavior

    If you attempt to delete a range that is beyond what exists in the document, you get an error code as is appropriate.

    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 = 100; // END OFFSET OUT OF BOUNDS
    doc.DeleteText(tRange);

    As the ending offset is out of range and I get the error code -21 (FE_OffsetNotFound) for the document shown. No text gets deleted.


    If I change the code to tRange.end.offset = 10;, the following happens:

    In summary, the "345" and the end of paragraph marker get deleted from the first paragraph and the "abcd" from the line that follows. That is eight characters in all as line ends and start paragraphs do not have offset (at least not in the FDK). The error code is now back to 0 (FE_Success).

    I have two problems with this behavior:
    • The delete should not have gone beyond the paragraph specified.
    • The offset calculation is incorrect.