﻿// Copyright 2015.  Adobe Systems, Incorporated.  All rights reserved.
// This script will create a file from each artboard and then export to a PDF Presentation
// Written by Sandra Voelker
// ZStrings and auto layout by Tom Ruark
 
/*
@@@BUILDINFO@@@ Artboards To PDF.jsx 1.0.0.
*/
 
/*
 
// BEGIN__HARVEST_EXCEPTION_ZSTRING
 
<javascriptresource>
    <name>$$$/JavaScripts/ArtboardsToPDF/Menu=Artboards to PDF...</name>
    <category>scriptexport</category>
    <menu>export</menu>
    
    <eventid>12fb03a7-e9af-426a-8377-3d423d7303e6</eventid>
    <enableinfo>PSHOP_DocHasArtboards</enableinfo>
    <terminology>
                <![CDATA[<< /Version 1
                             /Events <<
                              /12fb03a7-e9af-426a-8377-3d423d7303e6 [($$$/JavaScripts/ArtboardsToPDF/Action=Artboard to PDF) /noDirectParam <<
                              >>]
                             >>
                          >> ]]>
     </terminology>
</javascriptresource>
 
// END__HARVEST_EXCEPTION_ZSTRING
 
*/
 
// enable double clicking from the Macintosh Finder or the Windows Explorer
#target photoshop
 
// debug level: 0-2 (0:disable, 1:break on error, 2:break at beginning)
// $.level =   0;
// debugger; // launch debugger on next line
 
// on localized builds we pull the $$$/Strings from a .dat file, see documentation for more details
$.localize = true;
 
//=================================================================
// Globals
//=================================================================
 

 
// ok and cancel button
var runButtonID = 1;
var cancelButtonID = 2;
 
 // UI strings to be localized
var strTitle = localize("$$$/JavaScripts/ArtboardToPSD/Title=Arboards To PDF");
var strButtonRun = localize("$$$/JavaScripts/ArtboardToPSD/Run=Run");
var strButtonCancel = localize("$$$/JavaScripts/ArtboardToPSD/Cancel=Cancel");
var strHelpText = localize("$$$/JavaScripts/ArtboardToPSD/HelpText=Please specify the location where you would like your PDF to be saved.  Photoshop will launch 'PDF Presentation' in order to convert each artboard into a PDF page.");
var strLabelDestination = localize("$$$/JavaScripts/ArtboardToPSD/Destination=Destination:");
var strButtonBrowse = localize("$$$/JavaScripts/ArtboardToPSD/Browse=&Browse...");
var strCheckboxSelectionOnly = localize("$$$/JavaScripts/ArtboardToPSD/Selected=&Export Selected Artboards Only");
var strPanelSlideShowOptions = localize("$$$/JavaScripts/ArtboardToPSD/SlideShow=Slideshow Options:");
var strLabelAdvaceEvery = localize("$$$/JavaScripts/ArtboardToPSD/AdvanceEvery=&Advance Every");
var strLabelSecond = localize("$$$/JavaScripts/ArtboardToPSD/Seconds=Seconds");
var strLabelLoopAfterLastPage = localize("$$$/JavaScripts/ArtboardToPSD/Loop=&Loop after last page");
var strAlertSpecifyDestination = localize("$$$/JavaScripts/ArtboardToPSD/SpecifyDestination=Please specify destination.");
var strAlertIncorrectDestination = localize("$$$/JavaScripts/ArtboardToPSD/IncorrectDestination=Unable to export. PDF destination does not exist on system.");
var strTitleSelectDestination = localize("$$$/JavaScripts/ArtboardToPSD/SelectDestination=Select Destination");
var strAlertDocumentMustBeOpened = localize("$$$/JavaScripts/ArtboardToPSD/OneDocument=You must have a document open to export.");
var strAlertNoArtboardsFound = localize("$$$/JavaScripts/ArtboardToPSD/Noartbrd=No artboards found in document.");
var strAlertWasSuccessful = localize("$$$/JavaScripts/ArtboardToPSD/Success= was successful.");
var strAlertFailed = localize("$$$/JavaScripts/ArtboardToPSD/Fail= failed.");
var strMessage = localize("$$$/JavaScripts/ArtboardToPSD/Message=Artboard To PDF action settings");
var strEditTextDestinationLength = localize("$$$/locale_specific/JavaScripts/ArtboardToPSD/EditTextDestinationLength=160" );
var strEditTextSecondsLength = localize("$$$/locale_specific/JavaScripts/ArtboardToPSD/EditTextSecondsLength=25" );
 var strAlertNoArtboardsSelected= localize("$$$/JavaScripts/ArtboardToPSD/Noartbrdsel=Selected layer was not an artboard.");
 var strAlertSaveDocument= localize("$$$/JavaScripts/ArtboardToPSD/Filenotsaved= Active document has not been saved.  Please save before exporting to PDF.");
 
 
 // make sure all calcualtions are done in pixels. 
  var ru= app.preferences.rulerUnits;
 app.preferences.rulerUnits = Units.PIXELS;
 
main();

// reset to whatever settings user had when done. 
     app.preferences.rulerUnits = ru;
     
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
 
 
 
///////////////////////////////////////////////////////////////////////////////
// Function: settingDialog
// Usage: create the main dialog
// Input: settings to initialize the dialog with , ExportInfo object
// Return: true if ok, false if cancel
///////////////////////////////////////////////////////////////////////////////
function settingDialog(exportInfo)
{
    dlgMain = new Window("dialog", strTitle);
   
    // match our dialog background color to the host application
    dlgMain.graphics.backgroundColor = dlgMain.graphics.newBrush (dlgMain.graphics.BrushType.THEME_COLOR, "appDialogBackground");
 
                dlgMain.orientation = 'column';
                dlgMain.alignChildren = 'left';
               
                // -- top of the dialog, first line
    dlgMain.add("statictext", undefined, strLabelDestination);
 
                // -- two groups, one for left and one for right ok, cancel
                dlgMain.grpTop = dlgMain.add("group");
                dlgMain.grpTop.orientation = 'row';
                dlgMain.grpTop.alignChildren = 'top';
                dlgMain.grpTop.alignment = 'fill';
 
                // -- group contains four lines
                dlgMain.grpTopLeft = dlgMain.grpTop.add("group");
                dlgMain.grpTopLeft.orientation = 'column';
                dlgMain.grpTopLeft.alignChildren = 'left';
                dlgMain.grpTopLeft.alignment = 'fill';
               
                // -- the second line in the dialog
                dlgMain.grpSecondLine = dlgMain.grpTopLeft.add("group");
                dlgMain.grpSecondLine.orientation = 'row';
                dlgMain.grpSecondLine.alignChildren = 'center';
 
    dlgMain.etDestination = dlgMain.grpSecondLine.add("edittext", undefined, exportInfo.destination.toString());
    dlgMain.etDestination.preferredSize.width = StrToIntWithDefault( strEditTextDestinationLength, 160 );
 
    dlgMain.btnBrowse = dlgMain.grpSecondLine.add("button", undefined, strButtonBrowse);
    dlgMain.btnBrowse.onClick = btnBrowseOnClick;
 
                // -- the third line in the dialog
    dlgMain.cbSelection = dlgMain.grpTopLeft.add("checkbox", undefined, strCheckboxSelectionOnly);
    dlgMain.cbSelection.value = exportInfo.selectionOnly;
 
                // -- the fourth line is a panel
                dlgMain.pnlSlideShow = dlgMain.grpTopLeft.add("panel");
                dlgMain.pnlSlideShow.orientation = 'column';
                dlgMain.pnlSlideShow.alignChildren = 'left';
                dlgMain.pnlSlideShow.alignment = 'fill';
                dlgMain.pnlSlideShow.text = strPanelSlideShowOptions;
               
                // -- the fifth line, three things in row orientation
                dlgMain.grpFifthLine = dlgMain.pnlSlideShow.add("group");
                dlgMain.grpFifthLine.orientation = 'row';
                dlgMain.grpFifthLine.alignChildren = 'center';
                dlgMain.grpFifthLine.alignment = 'left';
               
    dlgMain.cbAdvance = dlgMain.grpFifthLine.add( "checkbox", undefined, strLabelAdvaceEvery );
    dlgMain.cbAdvance.value = exportInfo.ssoAdvance;
 
    dlgMain.etSeconds = dlgMain.grpFifthLine.add("edittext", undefined, exportInfo.ssoSeconds.toString());
    dlgMain.etSeconds.preferredSize.width = StrToIntWithDefault( strEditTextSecondsLength, 25 );
 
    dlgMain.grpFifthLine.add("statictext", undefined, strLabelSecond);
               
    dlgMain.cbLoop = dlgMain.pnlSlideShow.add( "checkbox", undefined, strLabelLoopAfterLastPage );
    dlgMain.cbLoop.value = exportInfo.ssoLoop;
 
                // the right side of the dialog, the ok and cancel buttons
                dlgMain.grpTopRight = dlgMain.grpTop.add("group");
                dlgMain.grpTopRight.orientation = 'column';
                dlgMain.grpTopRight.alignChildren = 'fill';
               
                dlgMain.btnRun = dlgMain.grpTopRight.add("button", undefined, strButtonRun );
    dlgMain.btnRun.onClick = btnRunOnClick;
 
                dlgMain.btnCancel = dlgMain.grpTopRight.add("button", undefined, strButtonCancel );
    dlgMain.btnCancel.onClick = function() {
                                var d = this;
                                while (d.type != 'dialog') {
                                                d = d.parent;
                                }
                                d.close(cancelButtonID);
                }
 
                dlgMain.defaultElement = dlgMain.btnRun;
                dlgMain.cancelElement = dlgMain.btnCancel;
 
                // the bottom of the dialog
                dlgMain.grpBottom = dlgMain.add("group");
                dlgMain.grpBottom.orientation = 'column';
                dlgMain.grpBottom.alignChildren = 'left';
                dlgMain.grpBottom.alignment = 'fill';
   
    dlgMain.pnlHelp = dlgMain.grpBottom.add("panel");
    dlgMain.pnlHelp.alignment = 'fill';
 
    dlgMain.etHelp = dlgMain.pnlHelp.add("statictext", undefined, strHelpText, {multiline:true});
    dlgMain.etHelp.alignment = 'fill';
 
                // do not allow anything except for numbers 0-9
                //dlgMain.etSeconds.addEventListener ('keydown', NumericEditKeyboardHandler);
 
                // in case we double clicked the file
    app.bringToFront();
 
    dlgMain.center();
 
    var result = dlgMain.show();
 
    if (cancelButtonID == result) {
                                return result;
                }
   
    // get setting from dialog
    exportInfo.destination = dlgMain.etDestination.text;
    exportInfo.selectionOnly = dlgMain.cbSelection.value;
    exportInfo.ssoAdvance = dlgMain.cbAdvance.value;
    exportInfo.ssoSeconds = dlgMain.etSeconds.text;
    exportInfo.ssoLoop = dlgMain.cbLoop.value;
 
    var pdf = ".pdf";
   
    if ((File.fs === "Macintosh" ) && (exportInfo.destination.toLowerCase().lastIndexOf("/") == -1))
    {
        exportInfo.destination = Folder.myDocuments + "/" + exportInfo.destination;
        
    }

    if ((exportInfo.destination.length - pdf.length) != exportInfo.destination.toLowerCase().lastIndexOf(pdf )) {
        exportInfo.destination += pdf; // add ".pdf" if there is no PDF extension
    }
 
    return result;
}
 
 
///////////////////////////////////////////////////////////////////////////////
// Function: btnRunOnClick
// Usage: routine is assigned to the onClick method of the run button
// Input: checks the dialog params and closes with the dialog with true
// Return: <none>, dialog is closed with true on success
///////////////////////////////////////////////////////////////////////////////
function btnRunOnClick()
{
    // check if the setting is properly
    var destination = dlgMain.etDestination.text;
    if (destination.length == 0) {
        alert(strAlertSpecifyDestination);
        return;
    }

 	var destPath = Folder(destination).parent;
	if (!destPath.exists) 
	{
	    alert(strAlertIncorrectDestination + "\n" +destPath );
        return;	
	} 
 
                // find the dialog in this auto layout mess
                var d = this;
                while (d.type != 'dialog') {
                                d = d.parent;
                }
                d.close(runButtonID);
}
 
 
///////////////////////////////////////////////////////////////////////////////
// Function: btnBrowseOnClick
// Usage: routine is assigned to the onClick method of the browse button
// Input: pop the selectDialog, and get a folder
// Return: <none>, sets the destination edit text
///////////////////////////////////////////////////////////////////////////////
function btnBrowseOnClick()
{
    var selFile = File.saveDialog(strTitleSelectDestination);
    if ( selFile != null ) {
        dlgMain.etDestination.text = selFile.fsName;
    }
                dlgMain.defaultElement.active = true;
    return;
}
 
 
///////////////////////////////////////////////////////////////////////////////
// Function: ExportInfo
// Usage: object for holding the dialog parameters
// Input: <none>
// Return: object holding the export info
///////////////////////////////////////////////////////////////////////////////
function ExportInfo() {

    this.destination = (app.activeDocument.path + "/" + app.activeDocument.name.substr(0,app.activeDocument.name.lastIndexOf("."))+".pdf" )
    //this.tempLocation = new String("");
    this.selectionOnly = false;
    this.ssoAdvance = false;
    this.ssoSeconds = 5;
    this.ssoLoop = false;    
}
 
 
///////////////////////////////////////////////////////////////////////////////
// Function: setTempFolder
// Usage: create a temp folder using random numbers
// Input: export info object
// Return: tempLocation of the object is set
///////////////////////////////////////////////////////////////////////////////
function setTempFolder(exportInfo)
{
    var folder = Folder.temp; // File(exportInfo.destination).parent;
    while(true) {   // set temporary folder with random name
        exportInfo.tempLocation = folder.toString() + "/temp" + Math.floor(Math.random()*10000);
        var testFolder = new Folder(exportInfo.tempLocation);
        if (!testFolder.exists) {
            testFolder.create();
            break;
        }
    }
}
 
 
///////////////////////////////////////////////////////////////////////////////
// Function: zeroSuppress
// Usage: create a string padded with 0's
// Input: num and number of digits to pad
// Return: zero padded num
///////////////////////////////////////////////////////////////////////////////
function zeroSuppress (num, digit) {
    var tmp = num.toString();
    while(tmp.length < digit)   tmp = "0" + tmp;
    return tmp
}
 

 
///////////////////////////////////////////////////////////////////////////////
// Function: main
// Usage: the main routine for this JavaScript
// Input: <none>
// Return: <none>
///////////////////////////////////////////////////////////////////////////////
function main()
{

    if ( app.documents.length <= 0 ) {
        if ( DialogModes.NO != app.playbackDisplayDialogs ) {
            alert( strAlertDocumentMustBeOpened );
        }
                return 'cancel'; // quit, returning 'cancel' (dont localize) makes the actions palette not record our script
    }
 
 // check to make sure document is saved before running exportInfo
    try { app.activeDocument.path } catch (e) 
         {
            if ( DialogModes.NO != app.playbackDisplayDialogs ) 
            {
            alert(strAlertSaveDocument );
             this.destination = false; 
            }
        return 'cancel'; 
     }
 
    var exportInfo = new ExportInfo();
    

 
//~     // look for last used params via Photoshop registry, getCustomOptions will throw if none exist
//~     try 
//~     {
//~             var d = app.getCustomOptions("83ba0aab-dcf6-4f96-a7c6-e08f48e65c62");
//~             descriptorToObject(exportInfo, d, strMessage);
//~     }
//~     catch(e) {} // it's ok if we don't have any options, continue with defaults
   
    // see if I am getting descriptor parameters
    descriptorToObject(exportInfo, app.playbackParameters, strMessage);
   
        if ( DialogModes.ALL == app.playbackDisplayDialogs ) 
        {
            if (cancelButtonID == settingDialog(exportInfo)) 
            {
                return 'cancel'; // quit, returning 'cancel' (dont localize) makes the actions palette not record our script
            }
        }
 
    try {
        
        
        var docName = app.activeDocument.name;
        app.activeDocument = app.documents[docName];
        docRef = app.activeDocument;
        setTempFolder(exportInfo);
        var artbrdName = new String("none");
       
       // if export only is selected in UI, then get  data from AB's that are selected only. if not, then get list of all artboards in document. 
		if (exportInfo.selectionOnly)  
		{
			sel_indxs = getSelectedLayersAMIdx();		
			allToplays = [];
			abAr = [];
			// get artboards list from selected layers. 
			for (x=0;x<sel_indxs.length;x++) {	allToplays.push(getTopLayerby(sel_indxs[x]));  } 
             // remove duplicate entries into array list if more then one layer in a single artboard was selected. 
             // and reverse becaseu script counts from bottom up and we want AB's too appear from top down. 
			var topLays = removeDuplicates(allToplays);
			// use artboards list to get AB data. 
			for (i=0;i<topLays.length;i++)
			{
				var getABdata = getEachABdata(topLays[i], "AMid");
				if (getABdata.result) abAr.push(getABdata); 
			}
		} 
		else  
        { 
            // get the artboard data from the active document, 
            var abAr = getABLayerInfo().reverse();
         } 
        
       
      var artbrdCount = abAr.length;
        
        if ( artbrdCount == 0 ) 
        {
            if ( DialogModes.NO != app.playbackDisplayDialogs ) { alert ( strAlertNoArtboardsFound ); }
                return 'cancel'; // quit, returning 'cancel' (dont localize) makes the actions palette not record our script
        } else {
            // DO IT! 

            // create duplicate doc and flatten to save memory. and processing time.  I already have all the data i need so don't need the layers anymore. 
            var srcDoc = docRef.duplicate();
            srcDoc.flatten();	
			app.activeDocument = srcDoc;
			var curRulOrigin = getActiveDocRulerOrigin();	
			
            var tempFileList = new Array();   // array of Files that will be exported to build to PDF.
            var exportFileCount = 0;
			
            for ( artbrdIndex = 0; artbrdIndex < artbrdCount; artbrdIndex++ )
            {
               // duplicate the duped doc, because this is the one I'm going to crop, save, and close. 
				var abDoc = srcDoc.duplicate();
		   
               // get crop region
			   var lt = -curRulOrigin[0] + abAr[artbrdIndex].left;
                var tp = -curRulOrigin[1] + abAr[artbrdIndex].top ;
                var rt = (abAr[artbrdIndex].right - abAr[artbrdIndex].left) + lt ;
                var bt = (abAr[artbrdIndex].bottom - abAr[artbrdIndex].top) + tp ;
				
			   var cropRegion = [lt,tp,rt,bt];
                abDoc.crop(cropRegion);
                                                               
                var abDoc = app.activeDocument; 
                if (abDoc.bitsPerChannel == abDoc.THIRTYTWO) abDoc.bitsPerChannel = abDoc.SIXTEEN;
                var fileNameBody = zeroSuppress(artbrdIndex, 4);  // adds padding.
                                                                                                               
                fileNameBody += "_" + abAr[artbrdIndex].name;
                
                var tempFile = exportInfo.tempLocation + "/" + fileNameBody + ".psd";
                tempFile = new File( tempFile );
                // builds an array of temp files for exporting.
                tempFileList[exportFileCount] = tempFile;
                // saves temp file.
                abDoc.saveAs( tempFile );
			  abDoc.close(SaveOptions.DONOTSAVECHANGES);
			   
			  // reset active doc to FLATTENED doc. 
			  app.activeDocument = srcDoc;
                exportFileCount++;
            }
            logToHeadLights("Export Artboard to PDF count: " + exportFileCount);
           srcDoc.close(SaveOptions.DONOTSAVECHANGES);

		// Send error message if no artboard selected. 		
        if ( exportFileCount == 0 ) 
        {
            if ( exportInfo.selectionOnly)
            {  
                if ( DialogModes.NO != app.playbackDisplayDialogs ) { alert ( strAlertNoArtboardsSelected ); } 
            } 
            else 
           { 
               if ( DialogModes.NO != app.playbackDisplayDialogs ) { alert ( strAlertNoArtboardsFound ); } 
            }
           return 'cancel'; // quit, returning 'cancel' (dont localize) makes the actions palette not record our script
        } 
   
            // run PDF Presentation
            var presentationOptions = new PresentationOptions();
            presentationOptions.presentation = true;
            presentationOptions.view = true;
            presentationOptions.autoAdvance = exportInfo.ssoAdvance;
            presentationOptions.interval = exportInfo.ssoSeconds;
            presentationOptions.loop = exportInfo.ssoLoop;
            app.makePDFPresentation(tempFileList, File(exportInfo.destination), presentationOptions);
   
            // delete temporary files
            for ( artbrdIndex = 0; artbrdIndex < exportFileCount; artbrdIndex++ ) {
                tempFileList[artbrdIndex].remove();
            }
       
            // delete temprary folder
            var tempFolder = new Folder(exportInfo.tempLocation);
            tempFolder.remove();
			
        //TODO restore selection to what it was on start.  ? if needed. 
	
             var d = objectToDescriptor(exportInfo, strMessage);
            app.putCustomOptions("83ba0aab-dcf6-4f96-a7c6-e08f48e65c62", d);
 
            var dd = objectToDescriptor(exportInfo, strMessage);
            app.playbackParameters = dd;
 
            if ( DialogModes.ALL == app.playbackDisplayDialogs ) {
                alert(strTitle + strAlertWasSuccessful + "\n" + exportInfo.destination);
            }
 
            app.playbackDisplayDialogs = DialogModes.ALL;
        
        }
    } catch (e) {
        if ( DialogModes.NO != app.playbackDisplayDialogs ) {
            alert(e);
        }
                return 'cancel'; // quit, returning 'cancel' (dont localize) makes the actions palette not record our script
    }



}
///////////////////////////////////////////////////////////////////////////////
// Function: objectToDescriptor
// Usage: create an ActionDescriptor from a JavaScript Object
// Input: JavaScript Object (o)
//        object unique string (s)
//        Pre process converter (f)
// Return: ActionDescriptor
// NOTE: Only boolean, string, number and UnitValue are supported, use a pre processor
//       to convert (f) other types to one of these forms.
// REUSE: This routine is used in other scripts. Please update those if you
//        modify. I am not using include or eval statements as I want these
//        scripts self contained.
///////////////////////////////////////////////////////////////////////////////
function objectToDescriptor (o, s, f) {
                if (undefined != f) {
                                o = f(o);
                }
                var d = new ActionDescriptor;
                var l = o.reflect.properties.length;
                d.putString( app.charIDToTypeID( 'Msge' ), s );
                for (var i = 0; i < l; i++ ) {
                                var k = o.reflect.properties[i].toString();
                                if (k == "__proto__" || k == "__count__" || k == "__class__" || k == "reflect")
                                                continue;
                                var v = o[ k ];
                                k = app.stringIDToTypeID(k);
                                switch ( typeof(v) ) {
                                                case "boolean":
                                                                d.putBoolean(k, v);
                                                                break;
                                                case "string":
                                                                d.putString(k, v);
                                                                break;
                                                case "number":
                                                                d.putDouble(k, v);
                                                                break;
                                                default:
                                                {
                                                                if ( v instanceof UnitValue ) {
                                                                                var uc = new Object;
                                                                                uc["px"] = charIDToTypeID("#Rlt"); // unitDistance
                                                                                uc["%"] = charIDToTypeID("#Prc"); // unitPercent
                                                                                d.putUnitDouble(k, uc[v.type], v.value);
                                                                } else {
                                                                                throw( new Error("Unsupported type in objectToDescriptor " + typeof(v) ) );
                                                                }
                                                }
                                }
                }
    return d;
}
 
 
///////////////////////////////////////////////////////////////////////////////
// Function: descriptorToObject
// Usage: update a JavaScript Object from an ActionDescriptor
// Input: JavaScript Object (o), current object to update (output)
//        Photoshop ActionDescriptor (d), descriptor to pull new params for object from
//        object unique string (s)
//        JavaScript Function (f), post process converter utility to convert
// Return: Nothing, update is applied to passed in JavaScript Object (o)
// NOTE: Only boolean, string, number and UnitValue are supported, use a post processor
//       to convert (f) other types to one of these forms.
// REUSE: This routine is used in other scripts. Please update those if you
//        modify. I am not using include or eval statements as I want these
//        scripts self contained.
///////////////////////////////////////////////////////////////////////////////
function descriptorToObject (o, d, s, f) {
                var l = d.count;
                if (l) {
                    var keyMessage = app.charIDToTypeID( 'Msge' );
        if ( d.hasKey(keyMessage) && ( s != d.getString(keyMessage) )) return;
                }
                for (var i = 0; i < l; i++ ) {
                                var k = d.getKey(i); // i + 1 ?
                                var t = d.getType(k);
                                strk = app.typeIDToStringID(k);
                                switch (t) {
                                                case DescValueType.BOOLEANTYPE:
                                                                o[strk] = d.getBoolean(k);
                                                                break;
                                                case DescValueType.STRINGTYPE:
                                                                o[strk] = d.getString(k);
                                                                break;
                                                case DescValueType.DOUBLETYPE:
                                                                o[strk] = d.getDouble(k);
                                                                break;
                                                case DescValueType.UNITDOUBLE:
                                                                {
                                                                var uc = new Object;
                                                                uc[charIDToTypeID("#Rlt")] = "px"; // unitDistance
                                                                uc[charIDToTypeID("#Prc")] = "%"; // unitPercent
                                                                uc[charIDToTypeID("#Pxl")] = "px"; // unitPixels
                                                                var ut = d.getUnitDoubleType(k);
                                                                var uv = d.getUnitDoubleValue(k);
                                                                o[strk] = new UnitValue( uv, uc[ut] );
                                                                }
                                                                break;
                                                case DescValueType.INTEGERTYPE:
                                                case DescValueType.ALIASTYPE:
                                                case DescValueType.CLASSTYPE:
                                                case DescValueType.ENUMERATEDTYPE:
                                                case DescValueType.LISTTYPE:
                                                case DescValueType.OBJECTTYPE:
                                                case DescValueType.RAWTYPE:
                                                case DescValueType.REFERENCETYPE:
                                                default:
                                                                throw( new Error("Unsupported type in descriptorToObject " + t ) );
                                }
                }
                if (undefined != f) {
                                o = f(o);
                }
}
 
///////////////////////////////////////////////////////////////////////////
// Function: StrToIntWithDefault
// Usage: convert a string to a number, first stripping all characters
// Input: string and a default number
// Return: a number
///////////////////////////////////////////////////////////////////////////
function StrToIntWithDefault( s, n ) {
    var onlyNumbers = /[^0-9]/g;
    var t = s.replace( onlyNumbers, "" );
                t = parseInt( t );
                if ( ! isNaN( t ) ) {
        n = t;
    }
    return n;
}
 
///////////////////////////////////////////////////////////////////////////////
// Function: NumericEditKeyboardHandler
// Usage: Do not allow anything except for numbers 0-9
// Input: ScriptUI keydown event
// Return: <nothing> key is rejected and beep is sounded if invalid
///////////////////////////////////////////////////////////////////////////////
function NumericEditKeyboardHandler (event) {
    try {
        var keyIsOK = KeyIsNumeric (event) ||
                                              KeyIsDelete (event) ||
                                                                                  KeyIsLRArrow (event) ||
                                                                                  KeyIsTabEnterEscape (event);
                                                                                 
        if (! keyIsOK) {
            //    Bad input: tell ScriptUI not to accept the keydown event
            event.preventDefault();
            /*    Notify user of invalid input: make sure NOT
                to put up an alert dialog or do anything which
                requires user interaction, because that
                interferes with preventing the 'default'
                action for the keydown event */
            app.beep();
        }
    }
    catch (e) {
        ; // alert ("Ack! bug in NumericEditKeyboardHandler: " + e);
    }
}
 
 
//    key identifier functions
function KeyHasModifier (event) {
    return event.shiftKey || event.ctrlKey || event.altKey || event.metaKey;
}
 
function KeyIsNumeric (event) {
    return  (event.keyName >= '0') && (event.keyName <= '9') && ! KeyHasModifier (event);
}
 
function KeyIsDelete (event) {
    //    Shift-delete is ok
    return (event.keyName == 'Backspace') && ! (event.ctrlKey);
}
 
function KeyIsLRArrow (event) {
    return ((event.keyName == 'Left') || (event.keyName == 'Right')) && ! (event.altKey || event.metaKey);
}
 
function KeyIsTabEnterEscape (event) {
                return event.keyName == 'Tab' || event.keyName == 'Enter' || event.keyName == 'Escape';
}
 

 ///////////////////////////////////////////////////////////////////////////////
// Function: getABdataArray
// Usage:   loop through each top level layer and check if its an artboard. 
// Input:document
// Return: array of artboard object data extracted from getEachABdata
///////////////////////////////////////////////////////////////////////////////
//~ function getABdataArray(inDoc)
//~ {
//~     // get all layers at top level of document.  (ab's only live at top) 
//~     var allToplays = inDoc.layers;
//~     var abArr = [];

//~     for (i=0;i<allToplays.length;i++)
//~     {
//~         inDoc.activeLayer = inDoc.layers[i];
//~          // artboard will not be a background layer, so only look for artboards if NOT a background layer. 
//~         if (!inDoc.activeLayer.isBackgroundLayer ) 
//~         {
//~             var getABdata = getEachABdata(getLayerID(), "AMid");
//~             if (getABdata.result) abArr.push(getABdata); 
//~         }
//~     }
//~     return abArr
//~ }


///////////////////////////////////////////////////////////////////////////////
// Function: getABLayerInfo
// Usage:  use ActionManager to check each layer in document for artboard and extract location and AMID
// Input: nothing. 
// Return: array of artboard object data extracted.
///////////////////////////////////////////////////////////////////////////////
function getABLayerInfo()
{
    var abArr = [];
    
		var ref = new ActionReference(); 
		ref.putEnumerated( charIDToTypeID('Dcmn'), charIDToTypeID('Ordn'), charIDToTypeID('Trgt') ); 
		var count = executeActionGet(ref).getInteger(charIDToTypeID('NmbL')) +1;   //  number of total layers in the document including start AND stop of groups.  So layersets get counted twice. 
		var infoList=[];
		try{ activeDocument.backgroundLayer;  var i = 0; }catch(e){ var i = 1; }; 
	
	for(i;i<count;i++)
		{ 	 
        ref = new ActionReference(); 
        ref.putIndex( charIDToTypeID( 'Lyr ' ), i );
        var desc = executeActionGet(ref);
		// this gets the layer name 
        var layerName = desc.getString(charIDToTypeID( 'Nm  ' ));
		if(layerName.match(/^<\/Layer group/) ) continue;  // removes "/Layer Groups" from the listed output.   (like if ID = "/Layer Group" then skip) 		
		 var name = layerName
          var id = desc.getInteger(stringIDToTypeID( 'layerID' ));
		 var index = desc.getInteger(charIDToTypeID( 'ItmI' ));
		 var layerType = typeIDToStringID(desc.getEnumerationValue( stringIDToTypeID( 'layerSection' )));		
		 var isLayerSet =( layerType == 'layerSectionContent') ? false:true;
		 var visible = desc.getBoolean(charIDToTypeID('Vsbl'));
        if (isLayerSet)
         {
               var artBoardLay = {};
               artBoardLay.result = false; 
                var ab_actDesc = desc.getObjectValue(stringIDToTypeID('artboard'));
                var abrect_desc = ab_actDesc.getObjectValue(stringIDToTypeID('artboardRect'));
                //~ 	// get bounds of artboard. 
                atop = parseInt(abrect_desc.getUnitDoubleValue(charIDToTypeID('Top ')))
                aleft = parseInt(abrect_desc.getUnitDoubleValue(charIDToTypeID('Left')));
                abottom = parseInt(abrect_desc.getUnitDoubleValue(charIDToTypeID('Btom')));
                aright = parseInt(abrect_desc.getUnitDoubleValue(charIDToTypeID('Rght')));
                
                // add the 4 values together, and if they are 0  then I know its not an actual artboard. 
                var checVal = (atop+aleft+abottom+aright);
                if (checVal != 0) 
                {
                     artBoardLay.result = true;
                     artBoardLay.name = name;
                     artBoardLay.top = atop;
                     artBoardLay.left = aleft;
                     artBoardLay.bottom = abottom;
                     artBoardLay.right = aright;
                     artBoardLay.AMid= id;
                     artBoardLay.index = index;
                    //alert([name, isLayerSet, atop,aleft,abottom,aright, name, id, index, visible] )
                    abArr.push(artBoardLay); 
                }
           }
   };
    return abArr;
};

///////////////////////////////////////////////////////////////////////////////
// Function: getEachABdata
// Usage:   extract weather layer is an artboard layer or not.  but either AMid or AMindx 
// Input: num - either AMid or AMindx  Type "AMid" or "AMindx")  
// Return: artboard AMid
///////////////////////////////////////////////////////////////////////////////
function getEachABdata(num,intype)
{
    var abObj = {}; //top: 2040, left: 1174, bottom: 3191, right: 3219, name:"AB1test", ID:4 
     abObj.result = false;  
 
    ref = new ActionReference(); 
	if (intype == "AMid")  {  ref.putIdentifier( charIDToTypeID( 'Lyr ' ), parseInt(num) ); }
	if (intype == "AMindx")  { ref.putIndex( charIDToTypeID( 'Lyr ' ), parseInt(num) );  }
	
    var desc = executeActionGet(ref);
	
	var ab_actDesc = desc.getObjectValue(stringIDToTypeID('artboard'));
	var abrect_desc = ab_actDesc.getObjectValue(stringIDToTypeID('artboardRect'));
	// get bounds of artboard. 
		abObj.top = parseInt(abrect_desc.getUnitDoubleValue(charIDToTypeID('Top ')))
		abObj.left = parseInt(abrect_desc.getUnitDoubleValue(charIDToTypeID('Left')));
		abObj.bottom = parseInt(abrect_desc.getUnitDoubleValue(charIDToTypeID('Btom')));
		abObj.right = parseInt(abrect_desc.getUnitDoubleValue(charIDToTypeID('Rght')));
		
		// add the 4 values together, and if they are 0  then I know its not an actual artboard. 
		var checVal = (abObj.top+abObj.left+abObj.bottom+abObj.right);
		if (checVal === 0)  return abObj;
		
		abObj.rulerOrigin = getActiveDocRulerOrigin();
		abObj.name = desc.getString(charIDToTypeID( 'Nm  ' ));
		abObj.AMid = desc.getInteger(stringIDToTypeID( 'layerID' ));
		abObj.result = true; 
        
    return abObj 
 }

///////////////////////////////////////////////////////////////////////////////
// Function: getTopLayerby
// Usage:  used to extract top most parent layer of a layer idendified by its AM index. 
// Input: an AM extracted Layer Index 
// Return: the Am ID ofthe top level layer group
///////////////////////////////////////////////////////////////////////////////
function getTopLayerby(indx)
{
	   var ref = new ActionReference();
	   ref.putIndex(charIDToTypeID('Lyr '), indx);
	   var desc = new ActionDescriptor();
	   desc.putReference(charIDToTypeID('null'), ref);
	   desc.putBoolean(charIDToTypeID('MkVs'), false);
		executeAction(charIDToTypeID('slct'), desc, DialogModes.NO); 
		
		var topLay = returnLayParent(app.activeDocument, app.activeDocument.activeLayer);

		app.activeDocument.activeLayer = topLay;
		var layID = getLayerID();
		return layID
}

///////////////////////////////////////////////////////////////////////////////
// Function: returnLayParent
// Usage:  used to get the top most parent layerset of a layer. 
// Input: Document and layerObject that you want the top level parent of. 
// Return: Top level layerset of whic the supplied layer is inside.
///////////////////////////////////////////////////////////////////////////////
function returnLayParent(inDoc, acLay) 
{

	while (acLay.parent != inDoc)
	{
		acLay = acLay.parent;
		returnLayParent(inDoc, acLay); 
	}

	return acLay;
}  

///////////////////////////////////////////////////////////////////////////////
// Function: removeDuplicates
// Usage:  used to remove any duplicate string or number values in an array. 
// Input: an array of string or number elements (does not work on objects.) 
// Return: array with dupliates removed 
///////////////////////////////////////////////////////////////////////////////
function removeDuplicates(arr)
	{
	var temp=new Array();
	//arr.sort();  // no need to sort in this case, I know the same AB's will be next to each other.  And then the order is preserved.
	for(i=0;i<arr.length;i++)
	{
		if(arr[i]==arr[i+1]) {continue}
		temp[temp.length]=arr[i];
	}
  return temp;
} 

///////////////////////////////////////////////////////////////////////////////
// Function: getLayerID
// Usage:  Used to get ActionManager layer ID value
// Input: active layer in active document
// Return: the Am ID ofthe active layer in the active document.
///////////////////////////////////////////////////////////////////////////////
function getLayerID()
{
    var ref = new ActionReference();
    ref.putEnumerated( charIDToTypeID('Lyr '),charIDToTypeID('Ordn'),charIDToTypeID('Trgt') );
    amID = executeActionGet(ref).getInteger(stringIDToTypeID( "layerID" ));
    return amID;
}

///////////////////////////////////////////////////////////////////////////////
// Function: getActiveDocRulerOrigin
// Usage:  Used to get the document coordinates of the 0,0 ruler coordinate. 
// Input:  active document.
// Return: the document coordinates of the 0,0 ruler center location.
///////////////////////////////////////////////////////////////////////////////
function getActiveDocRulerOrigin()
{
	var ref = new ActionReference();
	ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
	var applicationDesc = executeActionGet(ref);
	var theH = applicationDesc.getInteger(stringIDToTypeID("rulerOriginH")) / 65536;
	var theV = applicationDesc.getInteger(stringIDToTypeID("rulerOriginV")) / 65536;
	return [theH,theV]
}


///////////////////////////////////////////////////////////////////////////////
// Function: getSelectedLayersAMIdx
// Usage:   extract a list of index values of all the selected layers. 
// Input::   (active document.) s
// Return: array of indexes ID's of selected layers. 
///////////////////////////////////////////////////////////////////////////////
function getSelectedLayersAMIdx()
{
	  var selectedLayers = new Array;
	  var ref = new ActionReference();
	  // get a number list of selected artLayers in the document
	  ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
	  // what do I want to do this this list?  Define an description of an action.
	  var desc = executeActionGet(ref);
	  // if the selected object has the "Target Layers" key  (only works cs4 + ) 
	  if( desc.hasKey( stringIDToTypeID( 'targetLayers' ) ) )
	  {
		 desc = desc.getList( stringIDToTypeID( 'targetLayers' ));
		  var c = desc.count 
		  var selectedLayers = [];  // for each 
		  for(var i=0;i<c;i++){
			try{ 
			   activeDocument.backgroundLayer;  // try to select a background layer, if I can then adjust the index counting.  (Background layers change index counitng of all layers by 1) 
			   selectedLayers.push(  desc.getReference( i ).getIndex() );
			}catch(e){
			   selectedLayers.push(  desc.getReference( i ).getIndex()+1 );
			}
		  }
	   }
	//  This is for layerSets 
	else{
		 var ref = new ActionReference(); 
		ref.putProperty( charIDToTypeID("Prpr") , charIDToTypeID( "ItmI" )); 
		ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
		// if there is a background layer inside a layer set, it will still effect thr  
		 try{ 
			activeDocument.backgroundLayer;
			selectedLayers.push( executeActionGet(ref).getInteger(charIDToTypeID( "ItmI" ))-1);
		 }catch(e){
			selectedLayers.push( executeActionGet(ref).getInteger(charIDToTypeID( "ItmI" )));
		 }
	  }
	  return selectedLayers;
}



///////////////////////////////////////////////////////////////////////////////
// Function: logToHeadLights
// Usage:   Logs to headlight usage data based on "export". 
// Input::   (active document.) s
// Return: array of indexes ID's of selected layers. 
///////////////////////////////////////////////////////////////////////////////
function logToHeadLights(eventRecord) 
{
    var headlightsActionID = stringIDToTypeID("headlightsLog");
    var desc = new ActionDescriptor();
    desc.putString(stringIDToTypeID("subcategory"), "Export");
    desc.putString(stringIDToTypeID("eventRecord"), eventRecord);
    executeAction(headlightsActionID, desc, DialogModes.NO);
}

// End Layer artbrd To PDF.jsx
 
 