﻿// Copyright 2012 Adobe Systems Incorporated.  All Rights reserved.// IMPORTANT: This file MUST be written out from ESTK with the option to write the UTF-8// signature turned ON (Edit > Preferences > Documents > UTF-8 Signature).  Otherwise,// the script fails when run from Photoshop with "JavaScript code was missing" on// non-English Windows systems.//// Extract CSS from the current layer selection and copy it to the clipboard.///*@@@BUILDINFO@@@ CopyCSSToClipboard.jsx 1.0.0.0*/$.localize = true;// Constants for accessing PS event functionality.  In the interests of speed// we're defining just the ones used here, rather than sucking in a general defs file.const classApplication				= app.charIDToTypeID('capp');const classDocument				= charIDToTypeID('Dcmn');const classLayer					= app.charIDToTypeID('Lyr ');const classLayerEffects            = app.charIDToTypeID('Lefx');const classProperty					= app.charIDToTypeID('Prpr');const enumTarget					= app.charIDToTypeID('Trgt');const eventGet						= app.charIDToTypeID('getd');const eventHide                    = app.charIDToTypeID('Hd  ');const eventSelect			= app.charIDToTypeID('slct');const eventShow                    = app.charIDToTypeID('Shw ');const keyItemIndex				= app.charIDToTypeID ('ItmI');const keyLayerID						= app.charIDToTypeID('LyrI');const keyTarget						= app.charIDToTypeID('null');const keyTextData					= app.charIDToTypeID('TxtD');const typeNULL                     = app.charIDToTypeID('null');const typeOrdinal					= app.charIDToTypeID('Ordn');const ktextToClipboardStr			= app.stringIDToTypeID( "textToClipboard" );const unitAngle		= app.charIDToTypeID('#Ang');const unitDensity	= app.charIDToTypeID('#Rsl');const unitDistance	= app.charIDToTypeID('#Rlt');const unitNone		= app.charIDToTypeID('#Nne');const unitPercent	= app.charIDToTypeID('#Prc');const unitPixels	= app.charIDToTypeID('#Pxl');const unitMillimeters= app.charIDToTypeID('#Mlm');const unitPoints	= app.charIDToTypeID('#Pnt');const enumRulerCm		= app.charIDToTypeID('RrCm');const enumRulerInches	= app.charIDToTypeID('RrIn');const enumRulerPercent	= app.charIDToTypeID('RrPr');const enumRulerPicas	= app.charIDToTypeID('RrPi');const enumRulerPixels	= app.charIDToTypeID('RrPx');const enumRulerPoints	= app.charIDToTypeID('RrPt');// SheetKind definitions from USheet.hconst kAnySheet				= 0;const kPixelSheet			= 1;const kAdjustmentSheet		= 2;const kTextSheet			= 3;const kVectorSheet			= 4;const kSmartObjectSheet		= 5;const kVideoSheet			= 6;const kLayerGroupSheet		= 7;const k3DSheet				= 8;const kGradientSheet		= 9;const kPatternSheet			= 10;const kSolidColorSheet		= 11;const kBackgroundSheet		= 12;const kHiddenSectionBounder	= 13;// Tables to convert Photoshop UnitTypes into CSS typesvar unitIDToCSS = {};unitIDToCSS[unitAngle]			= "deg";unitIDToCSS[unitDensity]		= "DEN	";	// Not supported in CSSunitIDToCSS[unitDistance]		= "DIST";	// Not supported in CSSunitIDToCSS[unitNone]			= "";		// Not supported in CSSunitIDToCSS[unitPercent]		= "%";unitIDToCSS[unitPixels]			= "px";unitIDToCSS[unitMillimeters]	= "mm";unitIDToCSS[unitPoints]			= "pt";unitIDToCSS[enumRulerCm]		= "cm";unitIDToCSS[enumRulerInches]	= "in";unitIDToCSS[enumRulerPercent]	= "%";unitIDToCSS[enumRulerPicas]		= "pc";unitIDToCSS[enumRulerPixels]	= "px";unitIDToCSS[enumRulerPoints]	= "pt";// Pixel units in Photoshop are hardwired to 72 DPI (points),// regardless of the doc resolution.var unitIDToPt = {};unitIDToPt[unitPixels]			= 1;unitIDToPt[enumRulerPixels]		= 1;unitIDToPt[Units.PIXELS]		= 1;unitIDToPt[unitPoints]			= 1;unitIDToPt[enumRulerPoints]		= 1;unitIDToPt[Units.POINTS]		= 1;unitIDToPt[unitMillimeters]		= UnitValue(1, "mm").as('pt');unitIDToPt[Units.MM]			= UnitValue(1, "mm").as('pt');unitIDToPt[enumRulerCm]			= UnitValue(1, "cm").as('pt');unitIDToPt[Units.CM]			= UnitValue(1, "cm").as('pt');unitIDToPt[enumRulerInches]		= UnitValue(1, "in").as('pt');unitIDToPt[Units.INCHES]		= UnitValue(1, "in").as('pt');unitIDToPt[stringIDToTypeID("inchesUnit")] = UnitValue(1, "in").as('pt');unitIDToPt[enumRulerPicas]		= UnitValue(1, "pc").as('pt');unitIDToPt[Units.PICAS]			= UnitValue(1, "pc").as('pt');unitIDToPt[unitDistance]		= 1;unitIDToPt[unitDensity]		 = 1;// Fortunately, both CSS and the DOM unit values use the same// unit abbreviations.var DOMunitToCSS = {};DOMunitToCSS[Units.CM]			= "cm";DOMunitToCSS[Units.INCHES]		= "in";DOMunitToCSS[Units.MM]			= "mm";DOMunitToCSS[Units.PERCENT]		= "%";DOMunitToCSS[Units.PICAS]		= "pc";DOMunitToCSS[Units.PIXELS]		= "px";DOMunitToCSS[Units.POINTS]		= "pt";DOMunitToCSS[TypeUnits.MM]		= "mm";DOMunitToCSS[TypeUnits.PIXELS]	= "px";DOMunitToCSS[TypeUnits.POINTS]	= "pt";// A sample object descriptor path looks like:// AGMStrokeStyleInfo.strokeStyleContent.'Clr '.'Rd  '// This converts either OSType or string IDs.makeID = function( keyStr ){	if (keyStr[0] == "'")	// Keys with single quotes 'ABCD' are charIDs.		return app.charIDToTypeID( eval(keyStr) );	else		return app.stringIDToTypeID( keyStr );}// Clean up some pretty noisy FP numbers...function round1k( x ) { return Math.round( x * 1000 ) / 1000; }// Strip off the unit string and return UnitValue as an actual numberfunction stripUnits( x ) { return Number( x.replace(/[^0-9.-]+/g, "") ); }// Convert a "3.0pt" style string or number to a DOM UnitValuefunction makeUnitVal( v ){	if (typeof v == "string")		return UnitValue( stripUnits( v ), v.replace(/[0-9.-]+/g, "" ) );	if (typeof v == "number")		return UnitValue( v, DOMunitToCSS[app.preferences.rulerUnits] );}// Convert a pixel measurement into a UnitValue in rulerUnitsfunction pixelsToAppUnits( v ){	if (app.preferences.rulerUnits == Units.PIXELS)		return UnitValue( v, "px" );	else	{		// Divide by doc's DPI, convert to inch, then convert to ruler units.		var appUnits = DOMunitToCSS[app.preferences.rulerUnits];		return UnitValue( (UnitValue( v / app.activeDocument.resolution, "in" )).as(appUnits), appUnits );	}}// Format a DOM UnitValue as a CSS string, using the rulerUnits units.UnitValue.prototype.asCSS = function(){	var cssUnits = DOMunitToCSS[app.preferences.rulerUnits];	return round1k( this.as(cssUnits) ) + cssUnits;}// Return the absolute value of a UnitValue as a UnitValueUnitValue.prototype.abs = function(){	return UnitValue( Math.abs( this.value ), this.type );}// It turns out no matter what your PS units pref is set to, the DOM/PSEvent// system happily hands you values in whatever whacky units it feels like.// This normalizes the unit output to the ruler setting, for consistency in CSS.// Note: This isn't a method because "desc" can either be an ActionDescriptor// or an ActionList (in which case the "ID" is the index).function getPSUnitValue( desc, ID ){	var srcUnitsID = desc.getUnitDoubleType( ID );		if (srcUnitsID == unitNone)	// Um, unitless unitvalues are just...numbers.		return round1k( desc.getUnitDoubleValue( ID ));		// Angles and percentages are typically things like gradient parameters,	// and should be left as-is.	if ((srcUnitsID == unitAngle) || (srcUnitsID == unitPercent))		return round1k(desc.getUnitDoubleValue( ID )) + unitIDToCSS[srcUnitsID];			// Skip conversion if coming and going in pixels	if (((srcUnitsID == unitPixels) || (srcUnitsID == enumRulerPixels))		&& (app.preferences.rulerUnits == Units.PIXELS))			return round1k(desc.getUnitDoubleValue( ID )) + "px";	// Other units to pixels must first convert to points, 	// then expanded by the actual doc resolution (measured in DPI)	if (app.preferences.rulerUnits == Units.PIXELS)		return round1k( desc.getUnitDoubleValue( ID ) * unitIDToPt[srcUnitsID] 								* app.activeDocument.resolution / 72 ) + "px";									var DOMunitStr = DOMunitToCSS[app.preferences.rulerUnits];	// Pixels must be explictly converted to other units	if ((srcUnitsID == unitPixels) || (srcUnitsID == enumRulerPixels))		return pixelsToAppUnits( desc.getUnitDoubleValue( ID ) ).as(DOMunitStr) + DOMunitStr;		// Otherwise, let Photoshop do generic conversion.	return round1k( UnitValue( desc.getUnitDoubleValue( ID ), 	                          unitIDToCSS[srcUnitsID] 					      ).as( DOMunitStr ) ) + DOMunitStr;}	// Attempt decoding of reference types.  This generates an object with two keys, // "refclass" and "value".  So a channel reference looks like://    { refclass:'channel', value: 1 }// Note the dump method compresses this to the text "{ channel: 1 }", but internally// the form above is used.  This is because ExtendScript doesn't have a good method// for enumerating keys.function getReference( ref ){	var v;	switch (ref.getForm())	{	case ReferenceFormType.CLASSTYPE:	v = typeIDToStringID( ref.getDesiredClass() ); break;	case ReferenceFormType.ENUMERATED:	v = ref.getEnumeratedValue(); break;	case ReferenceFormType.IDENTIFIER:		v = ref.getIdentifier(); break;	case ReferenceFormType.INDEX:			v = ref.getIndex(); break;	case ReferenceFormType.NAME:			v =ref.getName(); break;	case ReferenceFormType.OFFSET:		v = ref.getOffset(); break;	case ReferenceFormType.PROPERTY:	v = ref.getProperty(); break;	default: v = null;	}		return { refclass: typeIDToStringID( ref.getDesiredClass() ), value: v };}// For non-recursive types, return the value.  Note unit types are// returned as strings with the unit suffix, if you want just the number // you'll need to strip off the type and convert it to Number()// Note: This isn't a method because "desc" can either be an ActionDescriptor// or an ActionList (in which case the "ID" is the index).function getFlatType( desc, ID ){	switch (desc.getType( ID ))	{	case DescValueType.BOOLEANTYPE:	return desc.getBoolean( ID );	case DescValueType.STRINGTYPE:		return desc.getString( ID );	case DescValueType.INTEGERTYPE:	return desc.getInteger( ID );	case DescValueType.DOUBLETYPE:	return desc.getDouble( ID );	case DescValueType.UNITDOUBLE:	return getPSUnitValue( desc, ID );	case DescValueType.ENUMERATEDTYPE: return typeIDToStringID( desc.getEnumerationValue(ID) );	case DescValueType.REFERENCETYPE: return getReference( desc.getReference( ID ) );	case DescValueType.RAWTYPE: 	return desc.getData( ID );	case DescValueType.ALIASTYPE:	return desc.getPath( ID );	case DescValueType.CLASSTYPE:	return typeIDToStringID( desc.getClass( ID ) );	default: return desc.getType(ID).toString();	}}//////////////////////////////////// ActionDescriptor //////////////////////////////////////ActionDescriptor.prototype.getFlatType = function( ID ){	return getFlatType( this, ID );}ActionList.prototype.getFlatType = function( index ){	// Share the ActionDesciptor code via duck typing	return getFlatType( this, index );}// Traverse the object described the string in the current layer.// Objects take the form of the nested descriptor IDs (the code above figures out the types on the fly).// So //     AGMStrokeStyleInfo.strokeStyleContent.'Clr '.'Rd  '// translates to doing a eventGet of stringIDToTypeID("AGMStrokeStyleInfo") on the current layer,// then doing://   desc.getObject(s2ID("AGMStrokeStyleInfo"))//		.getObject(s2ID("strokeStyleContent)).getObject(c2ID('Clr ')).getDouble('Rd  ');// ActionDescriptor.prototype.getVal = function( keyList, firstListItemOnly  ){	if (typeof(keyList) == 'string')	// Make keyList an array if not already		keyList = keyList.split('.');			if (typeof( firstListItemOnly ) == "undefined")		firstListItemOnly = true;	// If there are no more keys to traverse, just return this object.	if (keyList.length == 0)		return this;		keyStr = keyList.shift();	keyID = makeID(keyStr);		if (this.hasKey( keyID))		switch (this.getType( keyID ))		{		case DescValueType.OBJECTTYPE:			return this.getObjectValue( keyID ).getVal( keyList, firstListItemOnly );		case DescValueType.LISTTYPE:			var xx = this.getList( keyID );  // THIS IS CREEPY - original code below fails in random places on the same document.			return /*this.getList( keyID )*/xx.getVal( keyList, firstListItemOnly );		default: return this.getFlatType( keyID );		}	else		return null;}// Traverse the actionList using the keyList (see below)ActionList.prototype.getVal = function( keyList, firstListItemOnly ){	if (typeof(keyList) == 'string')	// Make keyList an array if not already		keyList = keyList.split('.');			if (typeof( firstListItemOnly ) == "undefined")		firstListItemOnly = true;	// Instead of ID, pass list item #.  Duck typing.	if (firstListItemOnly)		switch (this.getType( 0 ))		{		case DescValueType.OBJECTTYPE:			return this.getObjectValue( 0 ).getVal( keyList, firstListItemOnly );		case DescValueType.LISTTYPE:			return this.getList( 0 ).getVal( keyList, firstListItemOnly );		default: return this.getFlatType( 0 );			}	else	{		var i, result = [];		for (i = 0; i < this.count; ++i)			switch (this.getType(i))			{			case DescValueType.OBJECTTYPE:				result.push( this.getObjectValue( i ).getVal( keyList, firstListItemOnly  ));				break;			case DescValueType.LISTTYPE:				result.push( this.getList( i ).getVal( keyList, firstListItemOnly ));				break;			default:				result.push( this.getFlatType( i ) );			}		return result;	}}ActionDescriptor.prototype.extractBounds = function(){	function getbnd(desc, key) { return makeUnitVal( desc.getVal( key ) ); }	return [getbnd(this,"left"), getbnd(this,"top"), getbnd(this,"right"), getbnd(this,"bottom")];}ActionDescriptor.dumpValue = function( flatValue ){	if ((typeof flatValue == "object") && (typeof flatValue.refclass == "string"))		return "{ " + flatValue.refclass + ": " + flatValue.value + " }";	else		return flatValue;}// Debugging - recursively walk a descriptor and dump out all of the keys// Note we only dump stringIDs.  If you look in UActions.cpp:CInitialStringToIDEntry,// there is a table converting most (all?) charIDs into stringIDs.ActionDescriptor.prototype.dumpDesc = function( keyName ){	var i;	if (typeof( keyName ) == "undefined")		keyName = "";	for (i = 0; i < this.count; ++i)	{		try {			var key = this.getKey(i);			var ref;			var thisKey = keyName + "." + app.typeIDToStringID( key ) ;			switch (this.getType( key ))			{			case DescValueType.OBJECTTYPE:				this.getObjectValue( key ).dumpDesc( thisKey );				break;			case DescValueType.LISTTYPE:				this.getList( key ).dumpDesc( thisKey );				break;							case DescValueType.REFERENCETYPE:				ref = this.getFlatType( key );				$.writeln( thisKey + ":ref:" + ref.refclass + ":" + ref.value );				break;							default:				$.writeln( thisKey 							 + ": " + ActionDescriptor.dumpValue( this.getFlatType( key ) ) );			}		} catch (err) {			$.writeln("Error "+keyName+"["+i+"]: " + err.message);		}	}}ActionList.prototype.dumpDesc = function( keyName ){	var i;	if (typeof( keyName ) == "undefined")		keyName = "";	if (this.count == 0)		$.writeln( keyName + " <empty list>" );	else	for (i = 0; i < this.count; ++i)	{		try {			if (this.getType(i) == DescValueType.OBJECTTYPE)				this.getObjectValue(i).dumpDesc( keyName + "[" + i + "]" );			else			if (this.getType(i) == DescValueType.LISTTYPE)				this.getList(i).dumpDesc( keyName + "[" + i + "]" );			else				$.writeln( keyName + "[" + i + "]:"							+ ActionDescriptor.dumpValue( this.getFlatType( i ) ) );		}		catch (err)		{			$.writeln("Error "+keyName+"["+i+"]: " + err.message);		}	}}//////////////////////////////////// ProgressBar //////////////////////////////////////// "ProgressBar" provides an abstracted interface to the progress bar DOM. It keeps// track of the total steps and number of steps completed so task steps can simply call// nextProgress().function ProgressBar(){	this.totalProgressSteps = 0;	this.currentProgress = 0;}// You must set cssToClip.totalProgressSteps to the total number of// steps to complete before calling this or nextProgress().// Returns true if aborted.ProgressBar.prototype.updateProgress = function( done ){	if (this.totalProgressSteps == 0)		return false;        return !app.updateProgress( done, this.totalProgressSteps );}// Returns true if aborted.ProgressBar.prototype.nextProgress = function(){	this.currentProgress++;	return this.updateProgress( this.currentProgress );}//////////////////////////////////// PSLayer //////////////////////////////////////// The overhead for using Photoshop DOM layers is high, and can be// really high if you need to switch the active layer.  This class provides// a cache and accessor functions for layers bypassing the DOM.function PSLayerInfo( layerIndex, isBG ){	this.index = layerIndex;	this.boundsCache = null;	this.descCache = {};		if (isBG)	{		this.layerID = "BG";		this.layerKind = kBackgroundSheet;	}	else	{		// See TLayerElement::Make() to learn how layers are located by PS events.		var ref = new ActionReference();		ref.putProperty( classProperty, keyLayerID );		ref.putIndex( classLayer, layerIndex );		this.layerID = executeActionGet( ref ).getVal("layerID");		this.layerKind = this.getLayerAttr("layerKind");		this.visible = this.getLayerAttr("visible");	}}PSLayerInfo.layerIDToIndex = function( layerID ){	var ref = new ActionReference();	ref.putProperty( classProperty, keyItemIndex );	ref.putIdentifier( classLayer, layerID );	return executeActionGet( ref ).getVal("itemIndex");}PSLayerInfo.prototype.makeLayerActive = function(){	var desc = new ActionDescriptor();	var ref = new ActionReference();	ref.putIdentifier( classLayer, this.layerID );	desc.putReference( typeNULL, ref );	executeAction( eventSelect, desc, DialogModes.NO );}PSLayerInfo.prototype.getLayerAttr = function( keyString, layerDesc ){	var layerDesc;	var keyList = keyString.split('.');		if ((typeof(layerDesc) == "undefined") || (layerDesc == null))	{		// Cache the IDs, because some (e.g., Text) take a while to get.		if (typeof this.descCache[keyList[0]] == "undefined")		{			var ref = new ActionReference();			ref.putProperty( classProperty, makeID(keyList[0]));			ref.putIndex( classLayer, this.index );			layerDesc = executeActionGet( ref );			this.descCache[keyList[0]] = layerDesc;		}		else			layerDesc = this.descCache[keyList[0]];	}	return layerDesc.getVal( keyList );}PSLayerInfo.prototype.getBounds = function( ignoreEffects ){	var boundsDesc;	if (typeof ignoreEffects == "undefined")		ignoreEffects = false;	if (ignoreEffects)		boundsDesc = this.getLayerAttr("boundsNoEffects");	else	{		if (this.boundsCache)			return this.boundsCache;		boundsDesc = this.getLayerAttr("bounds");	}    if (this.getLayerAttr("artboardEnabled"))        boundsDesc = this.getLayerAttr("artboard.artboardRect");	var bounds = boundsDesc.extractBounds();	if (! ignoreEffects)		this.boundsCache = bounds;	return bounds;}// Get a list of descriptors.  Returns NULL if one of them is unavailable.PSLayerInfo.prototype.getLayerAttrList = function( keyString ){	var i, keyList = keyString.split('.');	var descList = [];	// First item from the layer	var desc = this.getLayerAttr( keyList[0] );	if (! desc)		return null;	descList.push( desc );	if (keyList.length == 1)		return descList;		for (i = 1; i < keyList.length; ++i)	{		desc =  descList[i-1].getVal( keyList[i] );		if (desc == null) return null;		descList.push( desc );	}	return descList;}PSLayerInfo.prototype.descToColorList = function( colorDesc, colorPath ){	function roundColor( x ) { x = Math.round(x); return (x > 255) ? 255 : x; }	var i, rgb = ["'Rd  '", "'Grn '","'Bl  '"];	// Note double quotes around single quotes	var rgbTxt = [];	// See if the color is really there	colorDesc = this.getLayerAttr( colorPath, colorDesc );	if (! colorDesc)		return null;	for (i in rgb)		rgbTxt.push( roundColor(colorDesc.getVal( rgb[i] )) );	return rgbTxt;}// If the desc has a 'Clr ' object, create CSS "rgb( rrr, ggg, bbb )" output from it.PSLayerInfo.prototype.descToCSSColor = function( colorDesc, colorPath ){	var rgbTxt = this.descToColorList( colorDesc, colorPath );	if (! rgbTxt)		return null;	return "rgb(" + rgbTxt.join(", ") + ")";}PSLayerInfo.prototype.descToRGBAColor = function( colorPath, opacity, colorDesc ){	var rgbTxt = this.descToColorList( colorDesc, colorPath );	rgbTxt = rgbTxt ? rgbTxt : ["0","0","0"];		if (! ((opacity > 0.0) && (opacity < 1.0)))		opacity = opacity / 255.0;			if (opacity == 1.0)		return "rgb(" + rgbTxt.join( ", ") + ")";	else		return "rgba(" + rgbTxt.join( ", ") + ", " + round1k( opacity ) + ")";}function DropShadowInfo( xoff, yoff, dsDesc ){	this.xoff = xoff;	this.yoff = yoff;	this.dsDesc = dsDesc;}PSLayerInfo.getEffectOffset = function( fxDesc ){	var xoff, yoff, angle;	// Assumes degrees, PS users aren't into radians.	if (fxDesc.getVal( "useGlobalAngle" ))		angle = stripUnits( cssToClip.getAppAttr( "globalAngle.globalLightingAngle" ) ) * (Math.PI/180.0);	else		angle = stripUnits( fxDesc.getVal( "localLightingAngle" ) ) * (Math.PI/180.0);	// Photoshop describes the drop shadow in polar coordinates, while CSS uses cartesian coords.	var distance = fxDesc.getVal( "distance" );	var distUnits = distance.replace( /[\d.]+/g, "" );	distance = stripUnits( distance );	return [round1k(-Math.cos(angle) * distance) + distUnits,				round1k(  Math.sin(angle) * distance) + distUnits];}// New lfx: dropShadowMulti, frameFXMulti, gradientFillMulti, innerShadowMulti, solidFillMulti, PSLayerInfo.prototype.getDropShadowInfo = function( shadowType, boundsInfo, psEffect ){    psEffect = (typeof psEffect == "undefined") ? "dropShadow" : psEffect;	var lfxDesc = this.getLayerAttr( "layerEffects");	var dsDesc = lfxDesc ? lfxDesc.getVal( psEffect ) : null;	var lfxOn = this.getLayerAttr( "layerFXVisible" );        // Gather the effect and effectMulti descriptors into a single list.     // It will be one or the other    var dsDescList = null;    if (lfxDesc)        dsDescList = dsDesc ? [dsDesc] : lfxDesc.getVal(psEffect + "Multi", false);        	// If any of the other (non-drop-shadow) layer effects are on, then	// flag this so we use the proper bounds calculation.	if ((typeof shadowType != "undefined") && (typeof boundsInfo != "undefined")		&& (shadowType == "box-shadow") && lfxDesc && lfxOn && !dsDescList)	{		var i, fxList = ["dropShadow", "innerShadow", "outerGlow", "innerGlow",						 "bevelEmboss", "chromeFX", "solidFill", "gradientFill"];		for (i in fxList)			if (lfxDesc.getVal( fxList[i] + ".enabled"))			{				boundsInfo.hasLayerEffect = true;				break;			}        // Search multis as well        if (! boundsInfo.hasLayerEffect)        {            var fxMultiList = ["dropShadowMulti", "frameFXMulti", "gradientFillMulti",                                    "innerShadowMulti", "solidFillMulti"];            for (i in fxMultiList)            {                var j, fxs = lfxDesc.getVal( fxMultiList[i] );                for (j = 0; j < fxs.length; ++j)                    if (fxs[j].getVal("enabled"))                    {                        boundsInfo.hasLayerEffect = true;                        break;                    }                if (boundsInfo.hasLayerEffect) break;            }          }	}	// Bail out if effect turned off (no eyeball)	if (! dsDescList || !lfxOn)		return null;        var i, dropShadows = [];    for (i = 0; i < dsDescList.length; ++i)        if (dsDescList[i].getVal("enabled"))        {            var offset = PSLayerInfo.getEffectOffset( dsDescList[i] );            dropShadows.push( new DropShadowInfo( offset[0], offset[1], dsDescList[i] ) );        }    return (dropShadows.length > 0) ? dropShadows : null;}//// Return text with substituted descriptors.  Note items delimited// in $'s are substituted with values looked up from the layer data// e.g.: //     border-width: $AGMStrokeStyleInfo.strokeStyleLineWidth$;"// puts the stroke width into the output.  If the descriptor isn't// found, no output is generated.//PSLayerInfo.prototype.replaceDescKey = function( cssText, baseDesc ){	// Locate any $parameters$ to be substituted.	var i, subs = cssText.match(/[$]([^$]+)[$]/g);	var replacementFailed = false;		function testAndReplace( item )	{		if (item != null)			cssText = cssText.replace(/[$]([^$]+)[$]/, item );		else			replacementFailed = true;	}			if (subs)	{		// Stupid JS regex leaves whole match in capture group!		for (i = 0; i < subs.length; ++i)			subs[i] = subs[i].split("$")[1];		if (typeof(baseDesc) == "undefined")			baseDesc = null;		if (! subs)			alert('Missing substitution text in CSS/SVG spec');					for (i = 0; i < subs.length; ++i)		{			// Handle color as a special case			if (subs[i].match(/'Clr '/))				testAndReplace( this.descToCSSColor( baseDesc, subs[i] ) );			else if (subs[i].match(/(^|[.])color$/))				testAndReplace( this.descToCSSColor( baseDesc, subs[i] ) );			else				testAndReplace( this.getLayerAttr( subs[i], baseDesc ) );		}	}	return [replacementFailed, cssText];}// If useLayerFX is false, then don't check it.  By default it's checked.PSLayerInfo.prototype.gradientDesc = function( useLayerFX ){	if (typeof useLayerFX == "undefined")		useLayerFX = true;	var descList = this.getLayerAttr( "adjustment" );	if (descList && descList.getVal("gradient"))	{		return descList;	}	else		// If there's no adjustment layer, see if we have one from layerFX...	{		if (useLayerFX)			descList = this.getLayerAttr( "layerEffects.gradientFill" );	}	return descList;}function GradientInfo( gradDesc ){	this.angle = gradDesc.getVal("angle");	this.opacity = gradDesc.getVal("opacity");	this.opacity = this.opacity ? stripUnits(this.opacity)/100.0 : 1;	if (this.angle == null)		this.angle = "0deg";	this.type = gradDesc.getVal("type");	// Get rid of the new "gradientType:" prefix	this.type = this.type.replace(/^gradientType:/,"");	if ((this.type != "linear") && (this.type != "radial"))		this.type = "linear";		// punt	this.reverse = gradDesc.getVal("reverse") ? true : false;}// Extendscript operator overloadingGradientInfo.prototype["=="] = function( src ){	return (this.angle === src.angle)			&& (this.type === src.type)			&& (this.reverse === src.reverse);}PSLayerInfo.prototype.gradientInfo = function( useLayerFX ){	var gradDesc = this.gradientDesc( useLayerFX );	// Make sure null is returned if we aren't using layerFX and there's no adj layer	if (! useLayerFX && gradDesc && !gradDesc.getVal("gradient"))		return null;	return (gradDesc && (!useLayerFX || gradDesc.getVal("enabled"))) ? new GradientInfo( gradDesc ) : null;}// Gradient stop object, made from PS gradient.colors/gradient.transparency descriptorfunction GradientStop( desc, maxVal ){	this.r = 0; this.g = 0; this.b = 0; this.m = 100;	this.location  = 0; this.midPoint = 50;	if (typeof desc != "undefined")	{		var colorDesc = desc.getVal("color");		if (colorDesc)		{			this.r = Math.round(colorDesc.getVal("red"));			this.g = Math.round(colorDesc.getVal("green"));			this.b = Math.round(colorDesc.getVal("blue"));		}		var opacity = desc.getVal("opacity");		this.m = opacity ? stripUnits(opacity) : 100;		this.location = (desc.getVal("location") / maxVal) * 100;		this.midPoint = desc.getVal("midpoint");	}}GradientStop.prototype.copy = function( matte, location ){	var result = new GradientStop();	result.r = this.r;	result.g = this.g;	result.b = this.b;	result.m = (typeof matte == "undefined") ? this.m : matte;	result.location = (typeof location == "undefined")  ? this.location : location;	result.midPoint = this.midPoint;	return result;}GradientStop.prototype["=="] = function( src ){	return (this.r === src.r) && (this.g === src.g)			&& (this.b === src.b) && (this.m === src.m)			&& (this.location === src.location) 			&& (this.midPoint === src.midPoint);}// Lerp ("linear interpolate")GradientStop.lerp = function(t, a, b) { return Math.round(t * (b - a) + a); }  // Same as (1-t)*a + t*bGradientStop.prototype.interpolate = function( dest, t1 ){	var result = new GradientStop();	result.r = GradientStop.lerp( t1, this.r, dest.r );	result.g = GradientStop.lerp( t1, this.g, dest.g );	result.b = GradientStop.lerp( t1, this.b, dest.b );	result.m = GradientStop.lerp(t1, this.m, dest.m );	return result;}GradientStop.prototype.colorString = function( noTransparency ){	if (typeof noTransparency == "undefined")		noTransparency = false;	var compList = (noTransparency || (this.m == 100))							? [this.r, this.g, this.b] 							: [this.r, this.g, this.b, this.m/100];	var tag = (compList.length == 3) ? "rgb(" : "rgba(";	return tag + compList.join(",") + ")";}GradientStop.prototype.toString = function(){	 return this.colorString() + " " + Math.round(this.location) + "%";}GradientStop.reverseStoplist = function( stopList ){	stopList.reverse();	// Fix locations to ascending order	for (var s in stopList)		stopList[s].location = 100 - stopList[s].location;	return stopList;}GradientStop.dumpStops = function( stopList ){	for (var i in stopList)		$.writeln( stopList[i] );}// Gradient format: linear-gradient( <angle>, rgb( rr, gg, bb ) xx%, rgb( rr, gg, bb ), yy%, ... );PSLayerInfo.prototype.gradientColorStops = function(){	// Create local representation of PS stops	function makeStopList( descList, maxVal )	{		var s, stopList = [];		for (s in descList)			stopList.push( new GradientStop( descList[s], maxVal ) );				// Replace Photoshop "midpoints" with complete new stops		for (s = 1; s < stopList.length; ++s)		{			if (stopList[s].midPoint != 50)			{				var newStop = stopList[s-1].interpolate( stopList[s], 0.5 );				newStop.location = GradientStop.lerp( stopList[s].midPoint/100.0, 																  stopList[s-1].location,																  stopList[s].location );				stopList.splice( s, 0, newStop );				s += 1;	// Skip new stop			}		}		return stopList;	}	var gdesc = this.gradientDesc();	var psGrad = gdesc ? gdesc.getVal("gradient") : null;	if (psGrad)	{//		var maxVal = psGrad.getVal( "interpolation" );	// I swear it used to find this.		var maxVal = 4096;		var c, colorStops = makeStopList( psGrad.getVal( "colors", false ), maxVal );					var m, matteStops = makeStopList( psGrad.getVal( "transparency", false ), maxVal );				// Check to see if any matte stops are active		var matteActive = false;		for (m in matteStops)			if (! matteActive)				matteActive = (matteStops[m].m != 100);						if (matteActive)		{			// First, copy matte values from matching matte stops to the color stops			c = 0;			for (m in matteStops)			{				while ((c < colorStops.length) && (colorStops[c].location < matteStops[m].location))					c++;				if ((c < colorStops.length) && (colorStops[c].location == matteStops[m].location))					colorStops[c].m = matteStops[m].m;			}              // Make sure the end locations match up             if (colorStops[colorStops.length-1].location < matteStops[matteStops.length-1].location)                 colorStops.push( colorStops[colorStops.length-1].copy( colorStops[colorStops.length-1].m, matteStops[matteStops.length-1].location ));                 			// Now weave the lists together			m = 0; c = 0;			while (c < colorStops.length)			{				// Must adjust color stop's matte to interpolate matteStops				if (colorStops[c].location < matteStops[m].location)				{					var t = (colorStops[c].location - matteStops[m-1].location) 							/ (matteStops[m].location - matteStops[m-1].location);					colorStops[c].m = GradientStop.lerp( t, matteStops[m-1].m, matteStops[m].m );					c++;				}				// Must add matte stop to color stop list				if (matteStops[m].location < colorStops[c].location)				{					var t, newStop;					// If matte stops exist in front of the 1st color stop					if (c < 1)					{						newStop = colorStops[0].copy( matteStops[m].m, matteStops[m].location );					}					else					{						t = (matteStops[m].location - colorStops[c-1].location) 								/ (colorStops[c].location - colorStops[c-1].location);						newStop = colorStops[c-1].interpolate( colorStops[c], t );						newStop.m = matteStops[m].m;						newStop.location = matteStops[m].location;					}					colorStops.splice( c, 0, newStop );					m++;					c++;	// Step past newly added color stop				}				// Same, was fixed above				if (matteStops[m].location == colorStops[c].location)				{					m++; c++;				}			}			// If any matte stops remain, add those too.			while (m < matteStops.length)			{				var newStop = colorStops[c-1].copy( matteStops[m].m, matteStops[m].location );				colorStops.push( newStop );				m++;			}		}		return colorStops;	}	else		return null;}//////////////////////////////////// CSSToClipboard //////////////////////////////////////// Base object to scope the rest of the functions in.function CSSToClipboard(){	// Constructor moved to reset(), so it can be called via a script.}cssToClip = new CSSToClipboard();cssToClip.reset = function(){	this.pluginName = "CSSToClipboard";	this.cssText = "";	this.indentSpaces = "";	this.browserTags = ["-moz-", "-webkit-", "-ms-"];	this.currentLayer = null;	this.currentPSLayerInfo = null;	this.groupLevel = 0;	this.currentLeft = 0;	this.currentTop = 0;		this.groupProgress = new ProgressBar();		this.aborted = false;		// Work-around for screwy layer indexing.	this.documentIndexOffset = 0;	try {		// This throws an error if there's no background		if (app.activeDocument.backgroundLayer)			this.documentIndexOffset = 1;	}	catch (err)	{}}cssToClip.reset();// Call Photoshop to copy text to the system clipboardcssToClip.copyTextToClipboard = function( txt ){	var testStrDesc = new ActionDescriptor();	testStrDesc.putString( keyTextData, txt );	executeAction( ktextToClipboardStr, testStrDesc, DialogModes.NO );}cssToClip.copyCSSToClipboard = function(){	this.logToHeadlights("Copy to CSS invoked");	this.copyTextToClipboard( this.cssText );}cssToClip.isCSSLayerKind = function( layerKind ){	if (typeof layerKind == "undefined")		layerKind = this.currentPSLayerInfo.layerKind;	switch (layerKind)	{	case kVectorSheet:	return true;	case kTextSheet:		return true;	case kPixelSheet:		return true;	case kLayerGroupSheet:  return true;	}	return false}// Listen carefully:  When the Photoshop DOM *reports an index to you*, it uses one based// indexing.  When *you request* layer info with ref.putIndex( classLayer, index ),// it uses *zero* based indexing.  The DOM should probably stick to the zero-based// index, so the adjustment is made here.// Oh god, it gets worse...the indexing is zero based if there's no background layer.cssToClip.setCurrentLayer = function( layer ){	this.currentLayer = layer;	this.currentPSLayerInfo = new PSLayerInfo(layer.itemIndex - this.documentIndexOffset, layer.isBackgroundLayer);}cssToClip.getCurrentLayer = function(){	if (! this.currentLayer)		this.setCurrentLayer( app.activeDocument.activeLayer );	return this.currentLayer;}// These shims connect the original cssToClip with the new PSLayerInfo object.cssToClip.getLayerAttr = function( keyString, layerDesc ){ return this.currentPSLayerInfo.getLayerAttr( keyString, layerDesc ); }cssToClip.getLayerBounds = function( ignoreEffects ){ return this.currentPSLayerInfo.getBounds( ignoreEffects ); }cssToClip.descToCSSColor = function( colorDesc, colorPath ){ return this.currentPSLayerInfo.descToCSSColor( colorDesc, colorPath ); }// Like getLayerAttr, but returns an app attribute.  No caching.cssToClip.getPSAttr = function( keyStr, objectClass ){	var keyList = keyStr.split('.');	var ref = new ActionReference();	ref.putProperty( classProperty, makeID( keyList[0] ) );	ref.putEnumerated( objectClass, typeOrdinal, enumTarget );		var resultDesc = executeActionGet( ref );		return resultDesc.getVal( keyList );}cssToClip.getAppAttr = function( keyStr ){ return this.getPSAttr( keyStr, classApplication ); }cssToClip.getDocAttr = function( keyStr ){ return this.getPSAttr( keyStr, classDocument ); }cssToClip.pushIndent = function(){	this.indentSpaces += "  ";}cssToClip.popIndent = function(){	if (this.indentSpaces.length < 2)		alert("Error - indent underflow");	this.indentSpaces = this.indentSpaces.slice(0,-2);}cssToClip.addText = function( text, browserTagList  ){	var i;	if (typeof browserTagList == "undefined")		browserTagList = null;		if (browserTagList)		for (i in browserTagList)			this.cssText += (this.indentSpaces + browserTagList[i] + text + "\n");	else		this.cssText += (this.indentSpaces + text + "\n");//	$.writeln(text);	// debug}cssToClip.addStyleLine = function( cssText, baseDesc, browserTagList ){	var result = this.currentPSLayerInfo.replaceDescKey( cssText, baseDesc );	var replacementFailed = result[0];	cssText = result[1];		if (! replacementFailed)		this.addText( cssText, browserTagList );	return !replacementFailed;}// Text items need to try both the base and the default descriptorscssToClip.addStyleLine2 = function( cssText, baseDesc, backupDesc ){	if (! this.addStyleLine( cssText, baseDesc ) && backupDesc)		this.addStyleLine( cssText, backupDesc );}// Text is handled as a special case, to take care of rounding issues.// In particular, we're avoiding 30.011 and 29.942, which round1k would miss// Seriously fractional text sizes (as specified by "roundMargin") are left as-iscssToClip.addTextSize = function( baseDesc, backupDesc ){    var roundMargin = 0.2;  // Values outside of this are left un-rounded    var sizeText = this.getLayerAttr("size", baseDesc );    if (! sizeText)        sizeText = this.getLayerAttr("size", backupDesc );    if (! sizeText)        return;    var unitRxp = /[\d.-]+\s*(\w+)/g;    var units = unitRxp.exec(sizeText);    if (! units) return;    units = units[1];    var textNum = stripUnits(sizeText);    var roundOff = textNum - (textNum|0);    if ((roundOff < roundMargin) || (roundOff > (1.0-roundMargin)))        this.addText( "font-size: " + Math.round(textNum) + units +";");    else        this.addStyleLine2( "font-size: $size$;", baseDesc, backupDesc );}// Checks the geometry, and returns "ellipse", "roundrect" // or "null" (if the points don't match round rect/ellipse pattern).// NOTE: All of this should go away when the DAG metadata is available// to just tell you what the radius is.// NOTE2: The path for a shape is ONLY visible when that shape is the active// layer.  So you must set the shape in question to be the active layer before// calling this function.  This really slows down the script, unfortunately.cssToClip.extractShapeGeometry = function(){	// We accept a shape as conforming if the coords are within "magnitude"	// of the overall size.	function near(a,b, magnitude)	{		a = Math.abs(a);  b = Math.abs(b);		return Math.abs(a-b) < (Math.max(a,b)/magnitude);	}	function sameCoord( pathPt, xy )	{		return (pathPt.rightDirection[xy] == pathPt.anchor[xy])				&& (pathPt.leftDirection[xy] == pathPt.anchor[xy]);	}	function dumpPts( pts )	// For debug viewing in Matlab	{		function pt2str( pt ) { return "[" + Math.floor(pt[0]) + ", " + Math.floor(pt[1]) + "]"; }		var i;		for (i = 0; i < pts.length; ++i)			$.writeln( "[" + [pt2str(pts[i].rightDirection), pt2str(pts[i].anchor), pt2str(pts[i].leftDirection)].join( "; " ) + "];" );	}	// Control point location for Bezier arcs.	// See problem 1, http://www.graphics.stanford.edu/courses/cs248-98-fall/Final/q1.html	const kEllipseDist = 4*(Math.sqrt(2) - 1)/3;	if (app.activeDocument.pathItems.length == 0)		return null;	// No path		// Grab the path name from the layer name (it's auto-generated)	var i, pathName = localize("$$$/ShapeLayerPathName=^0 Shape Path");	var path = app.activeDocument.pathItems[pathName.replace(/[^]0/,app.activeDocument.activeLayer.name)];		// If we have a plausible path, walk the geometry and see if it matches a shape we know about.	if ((path.kind == PathKind.VECTORMASK) && (path.subPathItems.length == 1))	{		var subPath = path.subPathItems[0];		if (subPath.closed && (subPath.pathPoints.length == 4))	// Ellipse?		{			function next(index) { return (index + 1) % 4; }			function prev(index) { return (index > 0) ? (index-1) : 3; }			var pts = subPath.pathPoints;						// dumpPts( pts );			for (i = 0; i < 4; ++i)			{				var xy = i % 2;	// 0 = x, 1 = y, alternates as we traverse the oval sides				if (! sameCoord( pts[i], 1-xy )) return null;				if (! near( pts[i].leftDirection[xy] - pts[i].anchor[xy], 							 (pts[next(i)].anchor[xy] - pts[i].anchor[xy]) * kEllipseDist, 100)) return null;				if (! near( pts[i].anchor[xy] - pts[i].rightDirection[xy],							   (pts[prev(i)].anchor[xy] - pts[i].anchor[xy]) * kEllipseDist, 100)) return null;			}			// Return the X,Y radius			return [pts[1].anchor[0] - pts[0].anchor[0], pts[1].anchor[1] - pts[0].anchor[1], "ellipse"];		}		else if (subPath.closed && (subPath.pathPoints.length == 8))	// RoundRect?		{			var pts = subPath.pathPoints;			//dumpPts( pts );			function sameCoord2( pt, xy, io )			{				return (sameCoord( pt, xy ) 							&& ( ((io == 0) && (pt.rightDirection[1-xy] == pt.anchor[1-xy]))									|| ((io == 1) && (pt.leftDirection[1-xy] == pt.anchor[1-xy])) ) );			}			function next(index) { return (index + 1) % 8; }			function prev(index) { return (index > 0) ? (index-1) : 7; }			function arm( pt, xy, io ) { return (io == 0) ? pt.rightDirection[xy] : pt.leftDirection[xy]; }						for (i = 0; i < 8; ++i)			{				var io = i % 2;			// Incoming / Outgoing vector on the anchor point				var hv = (i >> 1) % 2;	// Horizontal / Vertical side of the round rect				if (! sameCoord2( pts[i], 1-hv, 1-io )) return null;				if (io == 0) 				{					if( ! near( arm( pts[i], hv, io ) - pts[i].anchor[hv], 								   (pts[prev(i)].anchor[hv] - pts[i].anchor[hv])*kEllipseDist, 10 ) )					return null;				}				else				{					if( ! near( arm( pts[i], hv, io ) - pts[i].anchor[hv], 								   (pts[next(i)].anchor[hv] - pts[i].anchor[hv])*kEllipseDist, 10 ) )					return null;				}			}			return [pts[2].anchor[0] - pts[1].anchor[0], pts[2].anchor[1] - pts[1].anchor[1], "round rect"];		}	}}// Gradient format: linear-gradient( <angle>, rgb( rr, gg, bb ) xx%, rgb( rr, gg, bb ), yy%, ... );cssToClip.gradientToCSS = function(){	var colorStops = this.currentPSLayerInfo.gradientColorStops();	var gradInfo = this.currentPSLayerInfo.gradientInfo();	if (colorStops && gradInfo)	{		if (gradInfo.reverse)			colorStops = GradientStop.reverseStoplist( colorStops );		if (gradInfo.type == "linear")			return gradInfo.type + "-gradient( " + gradInfo.angle + ", " + colorStops.join(", ") + ");";		// Radial - right now gradient is always centered (50% 50%)		if (gradInfo.type == "radial")			return gradInfo.type + "-gradient( 50% 50%, circle closest-side, " + colorStops.join(", ") + ");";	}	else		return null;}// Translate Photoshop drop shadow.  May need work with layerEffects.scale,// and need to figure out what's up with the global angle.cssToClip.addDropShadow = function( shadowType, boundsInfo ){	var dsInfo = this.currentPSLayerInfo.getDropShadowInfo( shadowType, boundsInfo, "dropShadow" );    var isInfo = this.currentPSLayerInfo.getDropShadowInfo( shadowType, boundsInfo, "innerShadow" );	if (! (dsInfo || isInfo))		return;            function map( lst, fn )    {        var i, result = [];        for (i = 0; i < lst.length; ++i)            result.push( fn(lst[i] ) );        return result;    }    function getShadowCSS( info, skipSpread )    {        // Translate PS parameters to CSS style        var opacity = info.dsDesc.getVal("opacity");        // LFX reports "opacity" as a percentage, so convert it to decimal        opacity = opacity ? stripUnits(opacity)/100.0 : 1;        var colorSpec =  cssToClip.currentPSLayerInfo.descToRGBAColor( "color", opacity, info.dsDesc );        var size = stripUnits(info.dsDesc.getVal("blur"));        var chokeMatte =  stripUnits(info.dsDesc.getVal("chokeMatte"));        var spread = size * chokeMatte / 100;        var blurRad = size - spread;        // Hack - spread is not used for text shadows.        var spreadStr = skipSpread ? "" : spread+ "px "                return info.xoff+" " + info.yoff + " "                    + blurRad + "px " + spreadStr + colorSpec;    }    function insetShadowCSS( info ) {  return "inset " + getShadowCSS( info ); }    function textShadowCSS( info ) { return getShadowCSS( info, true ); }    // You say CSS was designed by committee?  Really?	if (shadowType == "box-shadow")	{         var i, shadows = [];         if (dsInfo)            shadows = map( dsInfo, getShadowCSS );         if (isInfo) // push.apply == extend            shadows.push.apply( shadows, map( isInfo, insetShadowCSS ) );                  this.addText( shadowType + ": " + shadows.join(",") + ";" );         		boundsInfo.hasLayerEffect = true;	}    // CSS doesn't support inner shadow, just drop shadow	if (dsInfo && (shadowType == "text-shadow")) {        var shadows = map( dsInfo, textShadowCSS );        this.addText(shadowType + ": " + shadows.join(",") + ";" );    }}cssToClip.addOpacity = function( opacity ){	opacity = (typeof opacity == "number") ? opacity : this.getLayerAttr("opacity");	if ((typeof opacity == "number") && (opacity < 255))		this.addText( "opacity: " + round1k(opacity / 255) + ";" );}cssToClip.addRGBAColor = function( param, opacity, colorDesc ){	this.addText( param + ': ' + this.currentPSLayerInfo.descToRGBAColor( "color", opacity, colorDesc ) +';' );}function BoundsParameters(){	this.borderWidth = 0;	this.textOffset = null;	this.hasLayerEffect = false;	this.textLine = false;	this.rawTextBounds = null;	this.textHasDecenders = false;	this.textFontSize = 0;	this.textLineHeight = 1.2;}cssToClip.addObjectBounds = function( boundsInfo ){	var curLayer = this.getCurrentLayer();			var bounds = this.getLayerBounds( boundsInfo.hasLayerEffect );		if (boundsInfo.rawTextBounds)	{		// If the text has been transformed, rawTextBounds is set.  We need		// to set the CSS bounds to reflect the *un*transformed text, placed about		// the center of the transformed text's bounding box.		var cenx = bounds[0] + (bounds[2] - bounds[0])/2;		var ceny = bounds[1] + (bounds[3] - bounds[1])/2;		var txtWidth = boundsInfo.rawTextBounds[2] - boundsInfo.rawTextBounds[0];		var txtHeight= boundsInfo.rawTextBounds[3] - boundsInfo.rawTextBounds[1];		bounds[0] = cenx - (txtWidth/2);		bounds[1] = ceny - (txtHeight/2);		bounds[2] = bounds[0] + txtWidth;		bounds[3] = bounds[1] + txtHeight;	}	if (boundsInfo.textLine 		&& !boundsInfo.hasLayerEffect		&& (boundsInfo.textFontSize !== 0))	{		var actualTextPixelHeight = (bounds[3] -bounds[1]).as('px');		var textBoxHeight = boundsInfo.textFontSize * boundsInfo.textLineHeight;		var correction = (actualTextPixelHeight - textBoxHeight)/2;		// If the text doesn't have decenders, then the correction by the PS baseline will		// be off (the text is instead centered vertically in the CSS text box).  This applies		// a different correciton for this case.		if (boundsInfo.textOffset) 		{			if (boundsInfo.textHasDecenders)			{				var lineHeightCorrection = (boundsInfo.textFontSize - (boundsInfo.textFontSize * boundsInfo.textLineHeight))/2;				boundsInfo.textOffset[1] += lineHeightCorrection;			}			else				boundsInfo.textOffset[1] = UnitValue( correction, 'px' );		}	}	if ((this.groupLevel == 0) && boundsInfo.textOffset)	{		this.addText("position: absolute;" );		this.addText("left: " + (bounds[0] + boundsInfo.textOffset[0]).asCSS() +";");		this.addText("top: " + (bounds[1] + boundsInfo.textOffset[1]).asCSS() + ";");	}	else	{		// Go through the DOM to ensure we're working in Pixels		var left = bounds[0];		var top = bounds[1];				if (boundsInfo.textOffset == null)			boundsInfo.textOffset = [0, 0];		// Intuitively you'd think this would be "relative", but you'd be wrong.		// "Absolute" coordinates are relative to the container.		this.addText("position: absolute;");		this.addText("left: " + (left 										- this.currentLeft										+ boundsInfo.textOffset[0]).asCSS() +";");		this.addText("top: " + (top										- this.currentTop										+ boundsInfo.textOffset[1]).asCSS() + ";");	}	// Go through the DOM to ensure we're working in Pixels	var width = bounds[2] - bounds[0];	var height = bounds[3] - bounds[1];	// In CSS, the border width is added to the -outside- of the bounds.  In order to match	// the default behavior in PS, we adjust it here.	if (boundsInfo.borderWidth > 0)	{		width -=  2*boundsInfo.borderWidth;		height -= 2*boundsInfo.borderWidth;	}	// Don't generate a width for "line" (paint) style text.	if (! boundsInfo.textLine)	{		this.addText( "width: " + ((width < 0) ? 0 : width.asCSS()) + ";");		this.addText( "height: " + ((height < 0) ? 0 : height.asCSS()) + ";");	}}// Only called for shape (vector) layers.cssToClip.getShapeLayerCSS = function( boundsInfo ){	// If we have AGM stroke style info, generate that.	var agmDesc = this.getLayerAttr( "AGMStrokeStyleInfo" );	boundsInfo.borderWidth = 0;	var opacity = this.getLayerAttr("opacity" );		if (agmDesc && agmDesc.getVal( "strokeEnabled"))	{		// Assumes pixels!		boundsInfo.borderWidth = makeUnitVal(agmDesc.getVal( "strokeStyleLineWidth" ));		this.addStyleLine( "border-width: $strokeStyleLineWidth$;", agmDesc );		this.addStyleLine( "border-color: $strokeStyleContent.color$;", agmDesc );		var cap = agmDesc.getVal( "strokeStyleLineCapType" );		var dashes = agmDesc.getVal( "strokeStyleLineDashSet", false );		if (dashes && dashes.length > 0)		{			if ((cap == "strokeStyleRoundCap") && (dashes[0] == 0))				this.addStyleLine("border-style: dotted;" );			if ((cap == "strokeStyleButtCap") && (dashes[0] > 0))				this.addStyleLine("border-style: dashed;");		}		else			this.addStyleLine("border-style: solid;");	}	// Check for layerFX style borders	var fxDesc = this.getLayerAttr( "layerEffects.frameFX" );	if (fxDesc && fxDesc.getVal( "enabled" ) 		&& (fxDesc.getVal( "paintType" ) == "solidColor"))	{		opacity = (stripUnits( fxDesc.getVal("opacity") ) / 100) * opacity;		boundsInfo.borderWidth = makeUnitVal(fxDesc.getVal( "size" )); // Assumes pixels!		this.addStyleLine("border-style: solid;");		this.addStyleLine("border-width: $size$;", fxDesc );		this.addStyleLine("border-color: $color$;", fxDesc );	}	// The Path for a shape *only* becomes visible when that shape is the active layer,	// so we need to make the current layer active before we extract geometry information.	// Yes, I know this is painfully slow, modifying the DOM or PS to behave otherwise is hard.	var saveLayer = app.activeDocument.activeLayer;	app.activeDocument.activeLayer = this.getCurrentLayer();	var shapeGeom = this.extractShapeGeometry();	app.activeDocument.activeLayer = saveLayer;		// We assume path coordinates are in pixels, they're not stored as UnitValues in the DOM.	if (shapeGeom)	{		// In CSS, the borderRadius needs to be added to the borderWidth, otherwise ovals		// turn into rounded rects.		if (shapeGeom[2] == "ellipse")			this.addText("border-radius: 50%;");		else		{			var radius =  Math.round((shapeGeom[0]+shapeGeom[1])/2);			// Note: path geometry is -always- in points ... unless the ruler type is Pixels.			radius = (app.preferences.rulerUnits == Units.PIXELS)					? radius = pixelsToAppUnits( radius )					: radius = UnitValue( radius, "pt" );			cssToClip.addText( "border-radius: " + radius.asCSS() +";");		}	}	var i, gradientCSS = this.gradientToCSS();	if (!agmDesc 	// If AGM object, only fill if explictly turned on		|| (agmDesc && agmDesc.getVal("fillEnabled")))	{		if (gradientCSS)		{			for (i in this.browserTags)				this.addText( "background-image: " + this.browserTags[i] + gradientCSS);		}		else		{			var fillOpacity = this.getLayerAttr("fillOpacity") / 255.0;			if (fillOpacity < 1.0)				this.addRGBAColor( "background-color", fillOpacity, this.getLayerAttr( "adjustment" ));			else				this.addStyleLine( "background-color: $adjustment.color$;" );		}	}	this.addOpacity( opacity );				this.addDropShadow( "box-shadow", boundsInfo );}// Only called for text layers.cssToClip.getTextLayerCSS = function( boundsInfo ){	function isStyleOn( textDesc, defTextDesc, styleKey, onText )	{		var styleText = textDesc.getVal( styleKey );		if (! styleText && defTextDesc)			styleText = defTextDesc.getVal( styleKey );		return (styleText && (styleText.search( onText ) >= 0));	}	// If the text string is empty, then trying to access the attributes fails, so exit now.	var textString = this.getLayerAttr("textKey.textKey");	if (textString.length === 0)		return;	var cssUnits = DOMunitToCSS[app.preferences.rulerUnits];	boundsInfo.textOffset = [UnitValue( 0, cssUnits ), UnitValue( 0, cssUnits )];	var leadingOffset = 0;		var opacity = (this.getLayerAttr("opacity")/255.0) * (this.getLayerAttr("fillOpacity")/255.0);	var textDesc = this.getLayerAttr( "textKey.textStyleRange.textStyle" );	var defaultDesc = this.getLayerAttr( "textKey.paragraphStyleRange.paragraphStyle.defaultStyle" );	if (! defaultDesc)		defaultDesc = this.getLayerAttr("textKey.textStyleRange.textStyle.baseParentStyle");	if (textDesc)	{//		this.addStyleLine2( "font-size: $size$;", textDesc, defaultDesc );		this.addTextSize( textDesc, defaultDesc );		this.addStyleLine2( 'font-family: "$fontName$";', textDesc, defaultDesc );		if (opacity == 1.0)			this.addStyleLine2( "color: $color$;", textDesc, defaultDesc );	// Color can just default to black		else		{			if (textDesc.getVal("color"))				this.addRGBAColor( "color" , opacity, textDesc );			else				this.addRGBAColor( "color", opacity, defaultDesc );		}				// This table is: [PS Style event key ; PS event value keyword to search for ; corresponding CSS]		var styleTable = [["fontStyleName",		"Bold",				"font-weight: bold;"],								["fontStyleName",		"Italic",				"font-style: italic;"],								["strikethrough",		"StrikethroughOn",	"text-decoration: line-through;"],								["underline",				"underlineOn",	 "text-decoration: underline;"],								 // Need RE, otherwise conflicts w/"smallCaps"								["fontCaps",				/^allCaps/,		 	"text-transform: uppercase;"], 								["fontCaps",				"smallCaps",		 "font-variant: small-caps;"],								// These should probably also modify the font size?								["baseline",				"superScript",	 	"vertical-align: super;"],								["baseline",				"subScript",			"vertical-align: sub;"]];		var i;		for (i in styleTable)			if (isStyleOn( textDesc, defaultDesc, styleTable[i][0], styleTable[i][1] ))				this.addText( styleTable[i][2] );		// Synthesize the line-height from the "leading" (line spacing) / font-size		var fontSize = textDesc.getVal( "size" );		if (! fontSize && defaultDesc) fontSize = defaultDesc.getVal( "size" );		var fontLeading = textDesc.getVal( "leading" );		if (fontSize)			fontSize = stripUnits(fontSize);		if (fontSize && fontLeading)		{			leadingOffset = fontLeading;			boundsInfo.textLineHeight = round1k(stripUnits(fontLeading) / fontSize);		}		this.addText("line-height: " + boundsInfo.textLineHeight + ";");			     if (fontSize)			boundsInfo.textFontSize = fontSize;						var pgraphStyle = this.getLayerAttr( "textKey.paragraphStyleRange.paragraphStyle" );		if (pgraphStyle)		{			this.addStyleLine( "text-align: $align$;", pgraphStyle );			var lineIndent = pgraphStyle.getVal( "firstLineIndent" );			if (lineIndent && (stripUnits(lineIndent) != 0))				this.addStyleLine( "text-indent: $firstLineIndent$;", pgraphStyle );			// PS startIndent for whole 'graph, CSS is?		}			// Update boundsInfo		this.addDropShadow( "text-shadow", boundsInfo );		// text-indent text-align letter-spacing line-height				var baseDesc = this.getLayerAttr( "textKey" );		function txtBnd( id ) { return makeUnitVal(baseDesc.getVal(id)); }		boundsInfo.textOffset = [txtBnd("bounds.left") - txtBnd("boundingBox.left"),										txtBnd("bounds.top") - txtBnd("boundingBox.top") + makeUnitVal(leadingOffset)];		if (this.getLayerAttr( "textKey.textShape.char" ) == "paint")			boundsInfo.textLine = true;					// This seems to be the one reliable indicator that the text has decenders		// below the baseline, indicating the positioning in CSS must be handled		// differently.		if (txtBnd("boundingBox.bottom").as('px') / fontSize > 0.03)			boundsInfo.textHasDecenders = true;			// Matrix: [xx xy 0; yx yy 0; tx ty 1], if not identiy, then add it.		var textXform = this.getLayerAttr( "textKey.transform" );		var vScale = textDesc.getVal("verticalScale");		var hScale = textDesc.getVal("horizontalScale");		vScale = (typeof vScale == "number") ? round1k(vScale/100.0) : 1;		hScale = (typeof hScale == "number") ? round1k(hScale/100.0) : 1;		if (textXform)		{			function xfm(key) { return textXform.getVal( key ); }			var xformData = this.currentPSLayerInfo.replaceDescKey("[$xx$, $xy$, $yx$, $yy$, $tx$, $ty$]", textXform);			var m = eval(xformData[1]);			m[0] *= hScale;			m[3] *= vScale;			if (! ((m[0] == 1) && (m[1] == 0)			   && (m[2] == 0) && (m[3] == 1)			   && (m[4] == 0) && (m[5] == 0)))			{				boundsInfo.rawTextBounds = baseDesc.getVal("boundingBox").extractBounds();				this.addText("transform: matrix( " + m.join(",") + ");", this.browserTags );			}		}		else 		{			// Case for text not otherwise transformed.			if ((vScale != 1.0) || (hScale != 1.0))			{				boundsInfo.rawTextBounds = baseDesc.getVal("boundingBox").extractBounds();				this.addText( "transform: scale(" + hScale + ", " + vScale + ");", this.browserTags );			}		}	}}cssToClip.getPixelLayerCSS = function(){	var name = this.getLayerAttr( "name" );	// If suffix isn't present, add one.  Assume file is in same folder as parent.	if (name.search( /[.]((\w){3,4})$/ ) < 0) {				this.addStyleLine( 'background-image: url("$name$.png");');	}	else	{		// If the layer has a suffix, assume Generator-style naming conventions		var docSuffix = app.activeDocument.name.search(/([.]psd)$/i);		var docFolder = (docSuffix < 0) ? app.activeDocument.name												  : app.activeDocument.name.slice(0, docSuffix);		docFolder += "-assets/";	// The "-assets" is not localized.				// Weed out any Generator parameters, if present.		var m = name.match(/(?:[\dx%? ])*([^.+,\n\r]+)([.]\w+)+$/);		if (m) {			name = m[1]+m[2];		}		this.addText( 'background-image: url("' + docFolder + name + '");');	}	var fillOpacity = this.getLayerAttr("fillOpacity")/255.0;	this.addOpacity( this.getLayerAttr("opacity") * fillOpacity );}// This walks the group and outputs all visible items in that group.  If the current// layer is not a group, then it walks to the end of the document (i.e., for dumping// the whole document).cssToClip.getGroupLayers = function ( currentLayer, memberTest, processAllLayers){    processAllLayers = (typeof processAllLayers === "undefined") ? false : processAllLayers;    // If processing all of the layers, don't stop at the end of the first group    var layerLevel = processAllLayers ? 2 : 1;    var visibleLevel = layerLevel;    var curIndex = currentLayer.index;    var saveGroup = [];    if (currentLayer.layerKind === kLayerGroupSheet)    {        if (! currentLayer.visible) {            return;        }        curIndex--; // Step to next layer in group so layerLevel is correct    }    var groupLayers = [];    while ((curIndex > 0) && (layerLevel > 0))    {        var nextLayer = new PSLayerInfo(curIndex, false);        if (memberTest(nextLayer.layerKind))        {            if (nextLayer.layerKind === kLayerGroupSheet)            {                if (nextLayer.visible && (visibleLevel === layerLevel)) {                    visibleLevel++;                    // The layers and section bounds must be swapped                    // in order to process the group's layerFX                     saveGroup.push(nextLayer);                    groupLayers.push(kHiddenSectionBounder);                }                layerLevel++;            }            else            {                if (nextLayer.visible && (visibleLevel === layerLevel)) {                    groupLayers.push(nextLayer);                }            }        }        else        if (nextLayer.layerKind === kHiddenSectionBounder)        {            layerLevel--;            if (layerLevel < visibleLevel) {                visibleLevel = layerLevel;                if (saveGroup.length > 0) {                    groupLayers.push(saveGroup.pop());                }            }        }        curIndex--;    }    return groupLayers;};// Recursively count the number of layers in the group, for progress barcssToClip.countGroupLayers = function( layerGroup, memberTest ){    if (! memberTest)        memberTest = cssToClip.isCSSLayerKind;    var currentLayer = new PSLayerInfo( layerGroup.itemIndex - cssToClip.documentIndexOffset);    var groupLayers = this.getGroupLayers( currentLayer, memberTest );    var i, visLayers = 0;    for (i = 0; i < groupLayers.length; ++i)        if (typeof groupLayers[i] === "object")            visLayers++;    return visLayers;}// The CSS for nested DIVs (essentially; what's going on with groups) // are NOT specified hierarchically.  So we need to finish this group's// output, then create the CSS for everything in it.cssToClip.pushGroupLevel = function(){	if (this.groupLevel == 0)	{        var numSteps = this.countGroupLayers( this.getCurrentLayer() )+1;		this.groupProgress.totalProgressSteps = numSteps;	}	this.groupLevel++;}cssToClip.popGroupLevel = function(){	var i, saveGroupLayer = this.getCurrentLayer();	var saveLeft = this.currentLeft, saveTop = this.currentTop;	var bounds = this.getLayerBounds();		this.currentLeft = bounds[0];	this.currentTop = bounds[1];	var notAborted = true;	for (i = 0; ((i < saveGroupLayer.layers.length) && notAborted); ++i)	{		this.setCurrentLayer( saveGroupLayer.layers[i] );		if (this.isCSSLayerKind())			notAborted = this.gatherLayerCSS();			}	this.setCurrentLayer( saveGroupLayer );	this.groupLevel--;	this.currentLeft = saveLeft;	this.currentTop = saveTop;	return notAborted;}cssToClip.layerNameToCSS = function( layerName ){	const kMaxLayerNameLength = 50;	// Remove any user-supplied class/ID delimiter	if ((layerName[0] == ".") || (layerName[0] == "#"))		layerName = layerName.slice(1);		// Remove any other creepy punctuation.	var badStuff = /[“”";!.?,'`@’#'$%^&*)(+=|}{><\x2F\s-]/g	var layerName = layerName.replace(badStuff, "_");	// Text layer names may be arbitrarily long; keep it real	if (layerName.length > kMaxLayerNameLength)		layerName = layerName.slice(0, kMaxLayerNameLength-3) ;	// Layers can't start with digits, force an _ in front in that case.	if (layerName.match(/^[\d].*/))		layerName = "_" + layerName;	return layerName;}// Gather the CSS info for the current layer, and add it to this.cssText// Returns FALSE if the process was aborted.cssToClip.gatherLayerCSS = function(){	// Script can't be called from PS context menu unless there is an active layer	var curLayer = this.getCurrentLayer();	// Skip invisible or non-css-able layers.	var layerKind = this.currentPSLayerInfo.layerKind;    if (layerKind === kBackgroundSheet)     // Background == pixels. Never in groups.        layerKind = kPixelSheet;	if ((! this.isCSSLayerKind( layerKind )) || (! curLayer.visible))		return true;	var isCSSid = (curLayer.name[0] == '#'); // Flag if generating ID not class	var layerName = this.layerNameToCSS( curLayer.name );		this.addText( (isCSSid ? "#" : ".") + layerName + " {" );	this.pushIndent();	var boundsInfo = new BoundsParameters();	switch (layerKind)	{	case kLayerGroupSheet:	this.pushGroupLevel();		break;	case kVectorSheet:		this.getShapeLayerCSS( boundsInfo );	break;	case kTextSheet:		this.getTextLayerCSS( boundsInfo );		break;	case kPixelSheet:		this.getPixelLayerCSS();		break;	}		var aborted = false;	if (this.groupLevel > 0)		aborted = this.groupProgress.nextProgress();	if (aborted)		return false;	// Use the Opacity tag for groups, so it applies to all descendants.	if (layerKind == kLayerGroupSheet)		this.addOpacity();	this.addObjectBounds( boundsInfo );	this.addStyleLine( "z-index: $itemIndex$;" );	this.popIndent();	this.addText("}");		var notAborted = true;		// If we're processing a group, now is the time to process the member layers.	if ((curLayer.typename == "LayerSet")	    && (this.groupLevel > 0))		notAborted = this.popGroupLevel();	return notAborted;}// Main entry pointcssToClip.copyLayerCSSToClipboard = function(){    var resultObj = new Object();        app.doProgress( localize("$$$/Photoshop/Progress/CopyCSSProgress=Copying CSS..."), "this.copyLayerCSSToClipboardWithProgress(resultObj)");        return resultObj.msg;}cssToClip.copyLayerCSSToClipboardWithProgress = function(outResult){	this.reset();	var saveUnits = app.preferences.rulerUnits;	app.preferences.rulerUnits = Units.PIXELS;	// Web dudes want pixels.		try {		var elapsedTime, then = new Date();		if (! this.gatherLayerCSS())			return;						// aborted		elapsedTime = new Date() - then;	}	catch (err)	{		// Copy CSS fails if a new doc pops open before it's finished, possible if Cmd-N is selected		// before the progress bar is up.  This message isn't optimal, but it was too late to get a		// proper error message translated, so this was close enough.		// MUST USE THIS FOR RELEASE PRIOR TO CS7/PS14//		alert( localize( "$$$/MaskPanel/MaskSelection/NoLayerSelected=No layer selected" ) );		alert( localize( "$$$/Scripts/CopyCSSToClipboard/Error=Internal error creating CSS: " ) + err.message + 				localize( "$$$/Scripts/CopyCSSToClipboard/ErrorLine= at script line ") + err.line );	}		cssToClip.copyCSSToClipboard();	if (saveUnits)		app.preferences.rulerUnits = saveUnits;			// We can watch this in ESTK without screwing up the app	outResult.msg = ("time: " + (elapsedTime / 1000.0) + " sec");}// ----- End of CopyCSSToClipboard script proper.  What follows is test & debugging code -----// Dump out a layer attribute as text.  This is how you learn what attributes are available.// Note this only works for ActionDescriptor or ActionList layer attributes; for simple// types just call cssToClip.getLayerAttr().cssToClip.dumpLayerAttr = function( keyName ){	this.setCurrentLayer( app.activeDocument.activeLayer );	var ref = new ActionReference();	ref.putIdentifier( classLayer, app.activeDocument.activeLayer.id );	layerDesc = executeActionGet( ref );	var desc = layerDesc.getVal( keyName, false );	if (! desc)		return;	if ((desc.typename == "ActionDescriptor") || (desc.typename == "ActionList"))		desc.dumpDesc( keyName );	else	if ((typeof desc != "string") && (desc.length >= 1))	{		s = []		for (var i in desc) 		{			if ((typeof desc[i] == "object") 				&& (desc[i].typename in {"ActionDescriptor":1, "ActionList":1 }))				desc[i].dumpDesc( keyName + "[" +i + "]" );			else				s.push( desc[i].dumpDesc( keyName ) )		}		if (s.length > 0)			$.writeln( keyName +": [" + s.join(", ") + "]" );	}	else		$.writeln(keyName + ": " + ActionDescriptor.dumpValue(desc) );}// Taken from inspection of ULayerElement.cppcssToClip.allLayerAttrs = ['AGMStrokeStyleInfo','adjustment','background','bounds',	'boundsNoEffects','channelRestrictions','color','count','fillOpacity','filterMaskDensity',	'filterMaskFeather','generatorSettings','globalAngle','group','hasFilterMask',	'hasUserMask','hasVectorMask','itemIndex','layer3D','layerEffects','layerFXVisible',	'layerSection','layerID','layerKind','layerLocking','layerSVGdata','layerSection',	'linkedLayerIDs','metadata','mode','name','opacity','preserveTransparency',	'smartObject','targetChannels','textKey','useAlignedRendering','useAlignedRendering',	'userMaskDensity','userMaskEnabled','userMaskFeather','userMaskLinked',	'vectorMaskDensity','vectorMaskFeather','videoLayer','visible','visibleChannels',	'XMPMetadataAsUTF8'];// Dump all the available attributes on the layer.  cssToClip.dumpAllLayerAttrs = function(){	this.setCurrentLayer( app.activeDocument.activeLayer );		var ref = new ActionReference();	ref.putIndex( classLayer, app.activeDocument.activeLayer.itemIndex );	var desc = executeActionGet( ref );	var i;	for (i = 0; i < this.allLayerAttrs.length; ++i)	{		var attr = this.allLayerAttrs[i];		var attrDesc = null;		try {			attrDesc = this.getLayerAttr( attr );			if (attrDesc)				this.dumpLayerAttr( attr );			else				$.writeln( attr + ": null" );		}		catch (err)		{			$.writeln( attr + ': ' + err.message );		}	}}// Walk the document's layers and describe them.cssToClip.dumpLayers = function( layerSet ){	var i, layerID;	if (typeof layerSet == "undefined")		layerSet = app.activeDocument;	for (i= 0; i < layerSet.layers.length; ++i)	{		if (layerSet.layers[i].typename == "LayerSet")			this.dumpLayers( layerSet.layers[i] );		this.setCurrentLayer( layerSet.layers[i] );		layerID = (layerSet.layers[i].isBackground) ? "BG" : cssToClip.getLayerAttr( "layerID" );		$.writeln("Layer[" + cssToClip.getLayerAttr( "itemIndex" ) + "] ID=" + layerID + " name: " + cssToClip.getLayerAttr( "name" ) );	}}cssToClip.logToHeadlights = function(eventRecord) {	var headlightsActionID = stringIDToTypeID("headlightsLog");    var desc = new ActionDescriptor();    desc.putString(stringIDToTypeID("subcategory"), "Export");    desc.putString(stringIDToTypeID("eventRecord"), eventRecord);    executeAction(headlightsActionID, desc, DialogModes.NO);}function testProgress(){    app.doProgress( localize("$$$/Photoshop/Progress/CopyCSSProgress=Copying CSS..."),"testProgressTask()" );}function testProgressTask(){	var i, total = 10;	var progBar = new ProgressBar();	progBar.totalProgressSteps = total;	for (i = 0; i <= total; ++i)	{//		if (progBar.updateProgress( i ))		if (progBar.nextProgress())		{			$.writeln('cancelled');			break;		}		$.sleep(800);	}}// Debug.  Uncomment one of these lines, and watch the output// in the ESTK "JavaScript Console" panel.// Walk the layers//runCopyCSSFromScript = true; cssToClip.dumpLayers();// Print out some interesting objects//runCopyCSSFromScript = true; cssToClip.dumpLayerAttr( "AGMStrokeStyleInfo" );//runCopyCSSFromScript = true; cssToClip.dumpLayerAttr( "adjustment" );  // Gradient, etc.//runCopyCSSFromScript = true; cssToClip.dumpLayerAttr( "layerEffects" );  // Layer FX, drop shadow, etc.//runCopyCSSFromScript = true; cssToClip.dumpLayerAttr( "textKey" );//runCopyCSSFromScript = true; cssToClip.dumpLayerAttr( "bounds" );// Some useful individual parameters//runCopyCSSFromScript = true; $.writeln( cssToClip.dumpLayerAttr( "opacity" ) );//runCopyCSSFromScript = true; $.writeln( cssToClip.dumpLayerAttr( "fillOpacity" ) );//runCopyCSSFromScript = true; $.writeln( cssToClip.dumpLayerAttr( "name" ));//runCopyCSSFromScript = true; $.writeln( cssToClip.dumpLayerAttr( "itemIndex" ));//runCopyCSSFromScript = true; $.writeln( cssToClip.dumpLayerAttr( "layerFXVisible" ));//runCopyCSSFromScript = true; $.writeln( cssToClip.dumpLayerAttr("layerSVGdata" ));//runCopyCSSFromScript = true; $.writeln( cssToClip.dumpLayerAttr("layerVectorPointData" ));// Debugging tests//runCopyCSSFromScript = true; testProgress();//runCopyCSSFromScript = true; cssToClip.countGroupLayers( cssToClip.getCurrentLayer() );// Backdoor to allow using this script as a library; if ((typeof( runCopyCSSFromScript ) == 'undefined')	|| (runCopyCSSFromScript == false))	cssToClip.copyLayerCSSToClipboard();