2

Half of my reason for posting is to put something on here that can be useful for others, as there is next to nothing good online about PDF OCGs/Layers, but I also need some help getting this to work right. 90% good, still need 10% to be done.

I have a list of layers/OCGs, I want some of them to be nested. I want to turn off the group as a whole. I can nest layers/OCGs by separating into 2+ arrays and inserting the list I want nested into the base list.

If the first element of the nested array is a String, it creates a group in a tree structure. I can click the '+' to open and close the group, but there is no "eye" box to turn visibility on or off for the group.

If the first element is not a string, it just uses the previous element in the array as the parent, complete with folder Icon and visibility "eye" to click, but while clicking the "eye will turn of the parent layer/OCG, it will not turn off the underlying nested layers/OCGs.

This may be fixed if I knew how to create data correctly. Or perhaps how to set a parent OCG.

Any ideas?


Install this script by placing in: c:\Program Files (x86)\Adobe\Acrobat 10.0\Acrobat\Javascripts

It will create a menu item under Edit, or Document->Edit

An example PDF is here 767k Download locally to see layers.


/********************************
RSCivilTools by Andrew Binning
Version 01, 2-15-2012
********************************/
//Some code by:
// InDesign Fixups by Dave Merchant  - Creative Commons Share-alike license
// version 02, November 2010
// http://www.uvsar.com/go/indesignfixups

//create new submenu for the Acrobat 8/9 or X menus
//Determine version, assign menu location
var menuParent = (app.viewerVersion<10)? "DocumentProcessing":"Edit";
//Add the menu
app.addSubMenu({ cName:"RSCivilTools", cUser:"RSCivil Tools", cParent:menuParent, nPos:((app.viewerVersion<10)? 0:7) });
//Create a nested layer (tailored code for my specific Document.
app.addMenuItem({ cName:"RSCcreateNest", cUser:"Create Nest", cParent:"RSCivilTools", 
          cExec:"createNest();",
          cEnable:"event.rc = (event.target != null);", nPos:0 });
//Promote sub layers - Unravel the nests
app.addMenuItem({ cName:"RSCpromote", cUser:"Undo Nest", cParent:"RSCivilTools",
          cExec:"promoteOCG_handler(event.target);",
          cEnable:"event.rc = (event.target != null);", nPos:1 });
//Unlist Guides and Grids layer (from Adobe IN-Design)
app.addMenuItem({ cName:"RSCremGAG", cUser:"Unlist 'Guides and Grids'", cParent:"RSCivilTools",
          cExec:"removeGAG(event.target);",
          cEnable:"event.rc = (event.target != null);", nPos:2 });
//Set up layers ( this is tailored code for my specific Document)
app.addMenuItem({ cName:"RSCsetStates", cUser:"Set Layers", cParent:"RSCivilTools",
          cExec:"setStates();",
          cEnable:"event.rc = (event.target != null);", nPos:3 });
//Toggle this list of layers on and off (tailored code for my specific Document.
app.addMenuItem({ cName:"RSCtoggleCityLimits", cUser:"Toggle City Limits", cParent:"RSCivilTools",
          cExec:"toggleCityLimits();",
          cEnable:"event.rc = (event.target != null);", nPos:4 });
//Link to the company website         
app.addMenuItem({ cName:"RSCsite", cUser:"Website", cParent:"RSCivilTools", 
          cExec:"app.launchURL('http://rscivil.com');", nPos:5 });

//Add a button for this function - Add from Quick Tools 3rd party addons
app.addToolButton({
        cName: "RSCcreateNestButton",
        cExec: "createNest();",
        cTooltext: "Create Nest",
        cEnable: true,
        nPos: 0,
        cLabel: "Create Nest"
        })
//Add a button for this function - Add from Quick Tools 3rd party addons
app.addToolButton({
        cName: "promoteLayers",
        cExec: "promoteOCG_handler(event.target);",
        cTooltext: "Undo Nest",
        cEnable: true,
        nPos: 1,
        cLabel: "Undo Nest"

        })

//Creates a nested layer from a predetermined list
function createNest(){
    var layers = this.getOCGs();
    var newOrder = new Array();
    var cityLimits = new Array();
    var comps = new Array();

    /*******************************************************
    This is where I need help.
    By not setting the first element to a String it does not name the group. Instead it seems to attach it as a child to the previous element in the array.
    This is fine, except that when you turn off said parent it does not turn off the sub layers/OCGs.
    If I could figure out how to create an object and set it up as an element before my inserted array it might help.
    *******************************************************/
    //Commented for testing - Set first element of the array to a descriptive string (this will be the name of the nested group) 
    //cityLimits[0] = "City Limits";
    //Set first element of the array to a descriptive string (this will be the name of the nested group)
    comps[0] = "Comps";

    //Separate all layers/OCGs containing "CityLimits|" or "COMP" from the original list of layers/OCGs
    for (var i=0,j=0,k=0,l=1; i<layers.length; i++){
        if(layers[i].name.substr(0,11)==="CityLimits|"){
            cityLimits[j] = layers[i];  //separate CityLimits OCG
            j++;
        }
        else if(layers[i].name.substr(0,4)==="COMP"){
            comps[l] = layers[i];   //separate COMP OCG
            l++;
        }
        else{
            newOrder[k] = layers[i];    //cram everything else into a new array
            k++;
        }
    }
    //Insert the cityLimits array into the newOrder array at position 5, do not remove any elements (the element before this arbitrarily becomes the parent, but does not work correctly)
    newOrder.splice(5,0,cityLimits);
    //Append the comps array to the end of the newOrder array
    newOrder[newOrder.length] = comps;
    //set the newOrder array as the OCGOrder.
    this.setOCGOrder(newOrder);
}
//Code by Dave Merchant
//Handler for promoting OCGs out of nested layers
function promoteOCG_handler(oDoc) {

  var ocgOrder = oDoc.getOCGOrder();
  var hasNest = false;

  if (ocgOrder==null) {
    app.alert( "No layers in current file", 0, 0, "Cannot proceed");
  } else {
    for (var i=0; i<ocgOrder.length; i++) {
       if ((typeof(ocgOrder[i]) == "object") && (ocgOrder[i].length > 0)) hasNest = true;
    }
    if (hasNest)  {
      promoteOCGs(oDoc,ocgOrder);
    } else app.alert( "No nested layers in current file", 0, 0, "Cannot proceed");
  }
}
//Code by Dave Merchant
//Promote/unravel layers/OCG out of nests
function promoteOCGs(oDoc,ocgOrder) {

  // Removes the top-level OCG nest structure from a PDF, promoting all sub-OCGs to the top level.
  // Used to remove the nesting created when a PDF is exported from InDesign with "Create Acrobat Layers" checked.


  var oChk = { cMsg:"Unlist the 'Guides and Grids' layer?", bInitialValue:true, bAfterValue:false};

  //var cMesg = "This action will ungroup all nested layers. IT CANNOT BE UNDONE.\n\nDo you want to continue?";
  //var nRtn = app.alert({ cMsg:cMesg, nIcon:2, nType:2, cTitle:"Promote nested layers", oCheckbox:oChk});
  //if (nRtn == 4) {

    var newOrder = new Array();

    for (var i=0; i<ocgOrder.length; i++) {

     var oType = typeof(ocgOrder[i]);
     var oLeng = ocgOrder[i].length
     if ((oType == "object") && (oLeng > 0)) {      // it's a nest, do the promotions

        for (var j=0; j<oLeng; j++) {
           if ((typeof(ocgOrder[i][j]) == "object") && 
              (!oChk.bAfterValue || (ocgOrder[i][j].name != "Guides and Grids"))) newOrder.push(ocgOrder[i][j]);
        }

     } else if (!oChk.bAfterValue || (ocgOrder[i].name != "Guides and Grids")) newOrder.push(ocgOrder[i]);
    }
    oDoc.setOCGOrder( newOrder );
  //}
}


//Code by Dave Merchant
// Removes the listing for (sub)OCG named "Guides and Grids".
// Does NOT delete the layer, simply hides it from the sidebar display.
function removeGAG(oDoc) {
  var cMesg = "This action will unlist the 'Guides and Grids' layer from the sidebar but will NOT delete the ";
  cMesg += "layer itself. IT CANNOT BE UNDONE.\n\nDo you want to continue?";
  var nRtn = app.alert(cMesg, 2, 2, "Unlist 'Guides and Grids'");
  if (nRtn == 4) {

    var ocgOrder = oDoc.getOCGOrder();
    var newOrder = new Array();

    for (var i=0; i<ocgOrder.length; i++) {

     var oType = typeof(ocgOrder[i]);
     var oLeng = ocgOrder[i].length
     if ((oType == "object") && (oLeng > 0)) {      // it's a nest

        var subObj = new Array();
        for (var j=0; j<oLeng; j++) {
           if ((typeof(ocgOrder[i][j]) == "string") || (ocgOrder[i][j].name != "Guides and Grids")) subObj.push(ocgOrder[i][j]);
        }
        newOrder.push(subObj);

     } else if (ocgOrder[i].name != "Guides and Grids") newOrder.push(ocgOrder[i]);
    }
    oDoc.setOCGOrder( newOrder );
  }
}

//Just a list of layers I want to toggle on or off (document specific)
function togList(name){
    if(name.substr(0,11)==="CityLimits|")
        return true;
    return false;
}
//Just a list of layers I want to set an initial state to off (document specific)
function offList(name){
    var lOff =  new Array();
    lOff[0] = "ADT";
    lOff[1] = "CityLimits|1900";
    lOff[2] = "CityLimits|1910";
    lOff[3] = "CityLimits|1920";
    lOff[4] = "CityLimits|1930";
    lOff[5] = "CityLimits|1940";
    lOff[6] = "CityLimits|1950";
    lOff[7] = "CityLimits|1960";
    lOff[8] = "CityLimits|1970";
    lOff[9] = "CityLimits|1975";
    lOff[10] = "CityLimits|1980";
    lOff[11] = "CityLimits|1985";
    lOff[12] = "CityLimits|1990";
    lOff[13] = "CityLimits|1995";
    lOff[14] = "CityLimits|2000";
    lOff[15] = "CityLimits|2005";
    lOff[16] = "CityLimits|2006";
    lOff[17] = "CityLimits|2007";
    lOff[18] = "CityLimits|2008";
    lOff[19] = "CityLimits|2009";
    lOff[20] = "CityLimits|2010";
    lOff[21] = "CityLimits|2011";
    lOff[29] = "Distance Circles 1";
    lOff[30] = "Distance Circles 2";
    lOff[31] = "landuse|Agriculture";
    lOff[32] = "landuse|Industrial";
    lOff[33] = "Landmark Labels Locations";
    lOff[34] = "landmarks|Locations";

    for (var i=0; i<lOff.length; i++){
        if(lOff[i] === name)
            return true;
    }
    return false;
}

//Checks all layers against a list and toggles them off or on (document specific)
function toggleCityLimits(){
   var layers = this.getOCGs();
    for (var i=0; i<layers.length; i++){
        var tog = true;
        if(togList(layers[i].name)){
            if(layers[i].state)
                tog = false;
            layers[i].state = tog; //toggle layer
        }
    }
}

//Checks all layers against a list and sets the initial state and current state to off (state=visibility) (document specific)
function setStates(){
   var layers = this.getOCGs();
    for (var i=0; i<layers.length; i++){
        var tog = true;
        if(offList(layers[i].name))
            tog = false;
        layers[i].state = tog;      //turn off layer
        layers[i].initState = tog;  //set initial visibilty to off
    }
}
Andrew
  • 69
  • 1
  • 10
  • Just FYI I had to download the PDF file locally in order to see the layers, but after I did, I see what you mean. – Jared Feb 15 '12 at 19:19

1 Answers1

2

The tree of layers displayed in the Acrobat (what your code above builds) does not necessarily match the tree of layers content defined in the PDF page.
A PDF layer inside the page content is defined like this:

/OC /layerid /BDC
...
/EMC

In the page content you can have 2 layers side-by-side:

/OC /layer1id /BDC
...
/EMC
/OC /layer2id /BDC
...
/EMC

but in the layers pane in Acrobat you can have layer2 as a child of layer1 because the /Order array where this hierarchy is defined is independent of the page content.
In order for the layer2 to be hidden automatically when layer1 (its parent) is hidden, they have to be defined like this in the page content:

/OC /layer1id /BDC
...
/OC /layer2id /BDC
...
/EMC
/EMC

The content of layer2 must be physically included in layer1.
Your problem appears because all the layers in the page content are built as side-by-side, not as child-parent and changing the /Order array does not affect the page content.
The solution I see, although I'm not very familiar with Adobe Acrobat JavaScript and I do not know if it is possible, is to attach a JavaScript function to each parent layer, function that is executed when the layer visibility changes and change also the visibility of all child layers.

iPDFdev
  • 5,229
  • 2
  • 17
  • 18
  • Yes that is one idea I had. Just detect the parent layer change and script it. Maybe I can find a way of restructuring the page with javascript, so the layers are nested instead of side by side. Thanks for answer! – Andrew Feb 16 '12 at 20:49