';

这篇是说的是如何使用 CEP 提供的各种接口和功能,比如 CEP 事件、文件操作、界面颜色等等。算是篇幅最大的了,而且并未写完,未完待续…

事件机制

CEP 扩展通过事件(Event)来捕获宿主应用(如 PhotoShop)的各种操作和状态的变化,当宿主应用(如 PhotoShop) 进行各种操作和状态的变化时就会发送事件,在 CEP 的 JavaScript 可以中通过监听器把方法和事件类型绑定,每当事件发生,就会执行绑定的方法。同时也可以发送自定义的事件,而且在 ExtendScript 中也可以发送事件,这就可以在 ExtendScript 与 CEP 的 JavaScript 间传递信息和数据了。

事件属性

事件(Event)作为一个对象,有以下属性:

属性 名称 作用
type 类型 用来识别事件的名称
scope 作用范围 可设置为"APPLICATION" (当前宿主应用)或者 "GLOBAL" (全局)
appId 应用 ID 生成事件的宿主应用的识别名称。可选
extensionId 扩展 ID 生成事件的扩展的识别名称。可选
data 数据 事件携带的数据,用来传递信息

事件分为 2 种:

  • CEP 事件
    • 宿主程序事件
      PhotoShop 启动、打开文档等会产生的这类事件,
      这种事件有自己的类型(type),例如:
      applicationActivatedocumentAfterActivate
    • 自定义事件
      用户自己创建并发送的 CEP 事件。
  • ExtendScript 事件
    也是宿主活动产生的事件,但与前 1 种并不一样,你不能从某个类型(type)来识别和捕获这种事件,而必须注册要监听的事件,并通过 com.adobe.PhotoshopJSONCallback 事件集中监听 ExtendScript 事件,并自己通过 eventID 判断接受到的是何种事件。可以说 ExtendScript 事件是另一套事件系统,而它实际上通过 CEP 事件来传递。

监听事件

通过 csInterface.js 提供的 addEventListener(type,function) 可以把一个方法绑定给一种事件,每当这种事件发生,就调用这个方法,并传递此次事件的 Event 对象。

Event 对象的属性在前面已经说过了 event.data 属性中会有事件的信息,如果是宿主程序事件一般是 XML 字符串,而 ExtendScript 事件是 JSON 字符串。

下面这个示例把方法 PSCallback()绑定给了 documentAfterActivate(文档被激活)事件,当 PhotoShop 中切换到某个文档时就会收到调用PSCallback(),并且传递事件对象,通过访问事件对象的 .data 属性会得到类似这样的数据:”<documentAfterActivate><name>2个VM差别</name><url>file:///E:/工作/Adobe 扩展开发/2 个 VM 差别.psd</url></documentAfterActivate>”

var cs = new CSInterface();
cs.addEventListener("documentAfterActivate" , PSCallback); //applicationActivate : 文档被激活

function PSCallback(event) //处理事件的函数,会接受到一个 Event 对象
{
    alert(event.data);/***  <documentAfterActivate><name>2个VM差别</name><url>file:///E:/工作/Adobe 扩展开发/2 个 VM 差别.psd</url></documentAfterActivate>  ***/
}

另外 addEventListener()也支持匿名函数,上面的例子可以简化为

 new CSInterface().addEventListener(
        "documentAfterActivate", function (event){ alert(event.data) ;}
    )

取消监听

通过 removeEventListener( ) 可以取消监听绑定

var cs = new CSInterface();
cs.removeEventListener("documentAfterActivate" , PSCallback);

 

发送事件

除了监听宿主应用的事件,我们自己也可以发送事件

CEP JavaScript 端

通过创建一个 Event 对象再使用 CSInterface 的 dispatchEvent() 方法,就可以发送一个事件,这样发送的信息根据事件 type 就能简单的捕获。

 var cs = new CSInterface();
    var event = new CSEvent(); // 创建一个事件对象
    event.type = "com.nullice.event.test"; //设定一个类型名称
    event.scope = "APPLICATION";// 限定在宿主应用范围内
    event.data = " good bye! @whitesincerely "; // 事件要传递的信息
    cs.dispatchEvent(event); // GO ! 发送事件

ExtendScript 端

在 ExtendScript 端,由于 ExtendScript 默认没有支持事件处理,需要手动载入一个库:new ExternalObject("lib:\PlugPlugExternalObject") ,载入后就可以通过创建 CSXSEvent() 对象来创建并发送事件了:

try {
    var loadSuccess = new ExternalObject("lib:\PlugPlugExternalObject"); //载入所需对象,loadSuccess 记录是否成功载入
} catch (e) {
    alert(e);// 如果载入失败,输出错误信息
}

if (loadSuccess) { 
var eventJAX = new CSXSEvent(); //创建事件对象
        eventJAX.type = "com.nullice.event.test2"; //设定一个类型名称
        eventJAX.data = " (⋟﹏⋞) !!!"; // 事件要传递的信息
        eventJAX.dispatch(); // GO ! 发送事件
}

上面这个示例中的事件都可以在 CEP JavaScript 中通过下面这个示例代码监听:

cs.addEventListener("com.nullice.event.test2",
        function (Event)
        {
            alert(Event.type +" : " + Event.data);
        }
    );

在 Adobe ExtendScript Toolkit 中执行可以立即看到结果

 

ExtendScript 事件监听

在 PhotoShop 中创建、移动一个图层、复制、粘贴内容等操作产生的事件就属于 ExtendScript 事件,ExtendScript 事件与不同其他事件,不能简单通过事件的 type 来捕捉,而需要注册想要捕捉的事件后统一捕捉并处理。由于ExtendScript 事件的特殊性,在这里单独来讲。

ExtendScript eventID

每种 ExtendScript 事件有不同 eventID ,比如创建图层是 1298866208, 复制是 1668247673 ,在 ExtendScript 中有个方法 charIDToTypeID( ) 可以把事件名转换成 eventID :

 charIDToTypeID( "copy" )  // 返回 1668247673

事件名可以在 Adobe 官方文档:《ADOBE PHOTOSHOP CC 2014 JAVASCRIPT SCRIPTING REFERENCE》的附录中找到。

不过我推荐这些事件名和 eventID 的对照表:
Photoshop Events (CC2015 ver.16)
Photoshop Constants Rosetta Stone

注册想要监听的 ExtendScript 事件

监听 ExtendScript 事件得先发送一个com.adobe.PhotoshopRegisterEvent 类型的 CEP 事件,来告诉宿主你要监听的 ExtendScript 事件。这个 “注册事件” 的 .data 就填写你要监听的 ExtendScript 事件的 eventID :

var cs= new CSInterface();
    var event = new CSEvent("com.adobe.PhotoshopRegisterEvent", "APPLICATION");  //创建一个“注册事件”
    event.extensionId = cs.getExtensionID(); //设置“注册事件” extensionId 为你扩展的扩展ID
    event.data = "1298866208"; // 想要监听的 ExtendScript 事件 eventID , 这里是创建图层对象事件: "Mk  "=1298866208
    cs.dispatchEvent(event); //发送“注册事件”,完成注册

开始监听事件绑定

注册完事件,就可以绑定方法了,通过 cs.addEventListener() ,监听一个 type 为 "com.adobe.PhotoshopJSONCallback + 你的扩展ID"的事件,就能接收到 ExtendScript 事件了:

 var cs= new CSInterface();
     cs.addEventListener("com.adobe.PhotoshopJSONCallback" + cs.getExtensionID(), PSCallback);

可以看到,所有 ExtendScript 事件都通过这个 "com.adobe.PhotoshopJSONCallback + 你的扩展ID"的 CEP 事件来接收,ExtendScript 事件对象 JSON 化的字符串被放在 "com.adobe.PhotoshopJSONCallback + 你的扩展ID" 的 .data

下面是上面示例代码中接收的事件的 .data(为了展示已经格式化,原本没有换行):

在 CEP 6.1 (CC2015.1) 之后,.data 会自动从 JSON 转到对象,也就是可以把直接吧 .data 当对象处理
ver1, {
    "eventID": 1298866208, "eventData": {
        "layerID": 16, "null": {"_ref": "contentLayer"}, "using": {
            "_obj": "contentLayer", "shape": {
                "_obj": "customShape", "bottom": {"_unit": "distanceUnit", "_value": 73.9907},
                "left": {"_unit": "distanceUnit", "_value": 54.9931}, "name": "箭头 5",
                "right": {"_unit": "distanceUnit", "_value": 107.986},
                "top": {"_unit": "distanceUnit", "_value": 34.9956}
            }, "strokeStyle": {
                "_obj": "strokeStyle", "fillEnabled": true, "strokeEnabled": false,
                "strokeStyleBlendMode": {"_enum": "blendMode", "_value": "normal"},
                "strokeStyleContent": {"_obj": "solidColorLayer", "color": {"_obj": "grayscale", "gray": 0}},
                "strokeStyleLineAlignment": {"_enum": "strokeStyleLineAlignment", "_value": "strokeStyleAlignInside"},
                "strokeStyleLineCapType": {"_enum": "strokeStyleLineCapType", "_value": "strokeStyleButtCap"},
                "strokeStyleLineDashOffset": {"_unit": "pointsUnit", "_value": 0}, "strokeStyleLineDashSet": [],
                "strokeStyleLineJoinType": {"_enum": "strokeStyleLineJoinType", "_value": "strokeStyleMiterJoin"},
                "strokeStyleLineWidth": {"_unit": "pointsUnit", "_value": 4}, "strokeStyleMiterLimit": 100,
                "strokeStyleOpacity": {"_unit": "percentUnit", "_value": 100}, "strokeStyleResolution": 72.009,
                "strokeStyleScaleLock": false, "strokeStyleStrokeAdjust": false, "strokeStyleVersion": 2
            }, "type": {"_obj": "solidColorLayer", "color": {"_obj": "grayscale", "gray": 33.8823}}
        }
    }
}

取消监听

要取消一个 ExtendScript 事件的监听,就是要取消其注册。与注册基本一样,只是这次发送的是 com.adobe.PhotoshopUnRegisterEvent 类型的消息:

 var cs= new CSInterface();
    var event = new CSEvent("com.adobe.PhotoshopUnRegisterEvent", "APPLICATION");  //创建一个“取消注册事件”
    event.extensionId = cs.getExtensionID(); //设置“取消注册事件” extensionId 为你扩展的扩展ID
    event.data = "1298866208"; // 要取消注册事件的 id
    cs.dispatchEvent(event); //发送“取消注册事件”,取消注册

执行 ExtendScript 代码

在 CEP 的 JavaScript 中用 CSInterface 的 evalScript()方法就可以执行 ExtendScript 代码:

var cs = new CSInterface();
cs.evalScript("app.documents.add();")

异步

evalScript(script, callback)方法接受 2 个参数,第一个是要执行的 ExtendScript 代码,第二个是接受执行返回值的回调函数。
值得注意的是执行 ExtendScript 后的返回值只能通过回调函数获取,而这是一个异步过程,所以像下面这样的代码得到的结果可能和你想象的不同:

var cs = new CSInterface();
var layerName = "缺省"
cs.evalScript("app.documents[0].layers[0].name;", function(result)
{
  layerName = result; // 设置 layerName 为第一个图层的图层名;
})
alert(layerName ); // 返回: "缺省"

由于回调函数是异步的,运行到 cs.evalScript()这行时并不会等待执行完 ExtendScript 代码,而是会继续运行下面的代码(也就是 alert(layerName );),所以这时 layerName依旧是 "缺省"而不是一般预想的执行ExtendScript 得到的图层名。真正执行回调函数得等到所有代码运行完毕之后了。要简单的使用异步机制,这里推荐使用 Promise 的方法,具体可以参考 《JavaScript Promise 迷你书》。不过如果代码简单的话,直接把所有的代码都写到回调中也是可以的。

执行 JSX 文件中的代码

ExtendScript 脚本保存的文件即是 JSX 文件。当 ExtendScript  代码很多很复杂的时候,还用 evalScript() 执行就很不明智了,这时候就应该把 ExtendScript  代码单独保存为 JSX 文件。

在  manifest.xml 文件中的 <ScriptPath> 中可以定义一个 JSX 文件,这个文件会在扩展启动时被载入:

<ScriptPath>./jsx/Source1.jsx</ScriptPath>

我们通常把 ExtendScript   代码都写到这个文件中,再在 CEP JavaScript 中使用 evalScript() 调用:

Source1.jsx

var dodo = function (info)
{
    alert("dodo:" + info);
}

test.js

    var cs = new CSInterface();
    var message= "来自 CEP 插件 :" + cs.getExtensionID();
    cs.evalScript("dodo('"+message +"');")// 调用 Source1.jsx  中的 dodo() 函数

结果

动态载入 JSX 文件

除了在 manifest.xml 指定的 JSX 文件,我们还可以动态的载入 JSX 文件,这样我们就可以根据需求载入多个 JSX 文件:
注意这是在 CEP JavaScript 中而非 JSX 中

 function loadJSX(fileName)
    {
        var extensionRoot = cs.getSystemPath(SystemPath.EXTENSION) + "/jsx/";// 这里是指插件目录下的 jsx 文件夹,可自行设为任意目录
        cs.evalScript('$.evalFile("' + extensionRoot + fileName + '")');
    }
    loadJSX("oo.jsx");// 载入插件目录下 jsx\oo.jsx 文件

 

通过 JSON 传递对象

从 ExtendScript  向 CEP JavaScript 传递对象除了使用事件,也可以使用 evalScript()的回调还实现,同样也是先在 ExtendScript  中把对象转换成 JSON 字符串,在返回字符串,最后在 CEP JavaScript 中把字符串还原成对象。
不过要注意的是 ExtendScript  并不预置 JSON 对象,需要自己载入。你需要下载 json2.js 并放在你的你的插件目录中,并用上面动态载入 JSX 文件的方法载入它。

下面是一个读取 Photoshop 中所有字体信息并发送到  CEP JavaScript 中的例子:

Source1.jsx

function getFontsJson()
{
    var fontlist = new Object(); // 创建一个要传递的对象

    //----这个例子是获得 PhotoShop 的可用字体列表
    fontlist.length = app.fonts.length; 
    fontlist.list = [{}];
    for (var i=0; i < app.fonts.length; i++)
    {
        fontlist.list[fontlist.list.length]=
        {
            name:app.fonts[i].name,
            style:app.fonts[i].style,
            typename:fonts[i].typename,
            postScriptName:fonts[i].postScriptName,
            family:fonts[i].family
        }
    }
   //---------
    return JSON.stringify(fontlist); // 把对象转化成 JSON 字符串并返回
}

test.js

 var cs = new CSInterface();

    function loadJSX(fileName)
    {
        var extensionRoot = cs.getSystemPath(SystemPath.EXTENSION) + "/jsx/";
        cs.evalScript('$.evalFile("' + extensionRoot + fileName + '")');
    }

    loadJSX("json2.js"); //为 ExtendScript 载入 JSON 库

    cs.evalScript('getFontsJson()',
        function (result)
        {
            var o = JSON.parse(result); //把 JSON 字符串转换为对象
            alert(o.list[0].name);  // 可以操作得到的对象了
        })

文件读写与二进制数据

由于 CEP 的 JavaScript 运行在 Node.js 引擎上,所以直接使用 Node.js 的 fs 模块(有关 fs 可以参考 fs 模块 – 《JavaScript 标准参考教程(alpha)》)就能进行文件的读写了,如下面的例子:

var fs = require('fs'); //引入 fs 模块
fs.readFile("D:/autorun.inf",function(err,data){ console.log(data)}) //读取文件并交给回调函数

Node.js 的文件读写操作完全能替代 CEP 的提供的 API,但还是推荐使用 CEP 的 API ,毕竟是官方标准,各宿主之间的兼容性更有保障。

Adobe 为 CEP 提供了自己的 API 来方便进行读写操作,实际上和使用 Node.js 的 fs 没有什么区别,不过 CEP 的接口的方法都是同步的,更容易理解。CEP 文件操作的方法放在 window.cep.fs 对象中。

文件路径

  • / 代表根目录, CEP 中为 PhotoShop 安装目录的根目录,比如 D:/
  • ./. 代表当前目录,同时如果没有前缀也表示当前目录,比如 cc 等同于 ./cc, CEP 中是 PS 安装目录 ,如 D:/PS/Adobe Photoshop CC 2015
  • ../.. 代表上级目录,可以连续使用: ../../

常用路径

*这里的 cs 指 CSInterface 对象,即:var cs = new CSInterface();

路径 例子
process.execPath Node.js 引擎可执行文件。
D:\PS\Adobe Photoshop CC 2015\Required\CEP\CEPHtmlEngine\CEPHtmlEngine.exe
__dirname 扩展所在目录
C:/Program Files (x86)/Common Files/Adobe/CEP/extensions/fonTags
__filename 当前文件目录
C:/Program Files (x86)/Common Files/Adobe/CEP/extensions/fonTags/index.html
cs.getSystemPath(SystemPath.USER_DATA) 系统用户数据文件夹
C:/Users/语冰/AppData/Roaming
cs.getSystemPath(SystemPath.COMMON_FILES) 系统公共库文件夹
C:/Program Files/Common Files
cs.getSystemPath(SystemPath.MY_DOCUMENTS) “我的文档”
C:/Users/不知语冰/Documents
cs.getSystemPath(SystemPath.HOST_APPLICATION) 宿主应用程序可执行文件
D:/PS/Adobe Photoshop CC 2015/Photoshop.exe

 

CEP 中的路径是 Unix 风格的 / 分隔,而不是 Windows 风格的 \,但是由于 CEP 扩展可以同时在 Windows 和 OS X 中运行,所以就有了跨平台问题。

跨平台路径处理

由于 OSX 和 Windows 上路径格式的不同,所以我们常常得对其处理,虽然 Node.js 和 CEP 提供的文件相关接口都是统一使用 Unix 风格的路径格式,但难免要处理来自本地的路径(比如向用户展示路径),要想简单的处理路径格式问题,可以使用 Node.js 自带的 Path 模块提供的功能。

var path = require('path'); //使用前先引入 path 模块
path.join("foo", "bar");
  •  拼接路径
    使用 path.join(); 来替代手动用 + 拼接路径,在不同系统上自动使用相应分隔符(要注意的是 CEP 提供的接口无论在 Windows 还是 OS X 上都是用 /):
path.join("AAA", "fff"); // 取代 "AAA"+"/" +"fff"
//在  OS X  上:AAA/fff
//在 Windows 上: AAA\\fff
  • 路径标准化
    如果你觉得使用 + 拼接路径根据方便也无妨,path 还有一个路径标准化功能,在最终使用路径前用 path.normalize() 处理路径就好了,路径标准化还有去除路径中无效字符的功能(比如 sd///ds 变成 sd/ds)。
path.normalize("Files/Adobe/CEP/");
//在  OS X  上:Files/Adobe/CEP/
//在 Windows 上: Files\Adobe\CEP\"
  • 获得相对路径的绝对路径
    使用 path.resolve(); 可以得到如 ./ 这样相对路径的绝对路径
  path.resolve("./");
 // "D:\PS\Adobe Photoshop CC 2015"

读取文件

window.cep.fs.readFile(路径, 方式编码) ,提供文件路径,和文本编码(默认按 UTF-8 处理),返回一个对象,其中

  • .err 存放返回错误信息
  • .data 以字符串形式存放读取返回的数据,
    var path = "D:/1.txt";
    var result = window.cep.fs.readFile(path);
    if (0 == result.err)// err 为 0 读取成功
    {
        console.log(result.data);
    }
    else
    {
        console.log("读取错误:" + result.err);// 失败
    }

Base64 模式读取

Base64 是一种把二进制数据以文本(64个可打印字符)形式表示的编码方法。由于 CEP 的 readFile() 只能读取文件后返回的 .data 只能是字符串,所以如果你要读取一个非文本的二进制文件,比如一张图片,就需要使用 Base64 模式读取,这样返回的.data中会是你读取文件数据的 Base64 编码后的字符串,否则读取的数据会被强制装换为字符串会丢失内容。

readFile() 第二个参数传入 "Base64" 即可以 Base64 模式读取。

    var path = "D:/1.txt"; 
    var result  = window.cep.fs.readFile(path ,"Base64");
    result.data //Base64 字符串: "77+977+977+977+977+977+977+977+977+977+977+977+977…v73v↵v73vv73vv73vv73vv73vv73vv73vv73vv73vv73vv70="

对于读取到的 Base64 字符串,可以用 CEP 提供的 cep.encoding.convertion 里的方法来进行转换,

  • .b64_to_ascii(base64str) Base64 字符串 以 ascii 编码转换为字符串
  • .b64_to_utf8(base64str) Base64 字符串 以 UTF-8 编码转换为字符串
  • .b64_to_binary(base64str) Base64 字符串以二进制编码转换为字符串

另外还有反过来的方法:.ascii_to_b64(ascii).utf8_to_b64(str).binary_to_b64(binary)
要注意的是 .b64_to_binary().binary_to_b64() 实际上是就是 window.atob()window.btoa(),而这 2 个方法返回的是字符串,而且是不支持 Unicode 字符的(比如中文),所以不要因为名字是 binary 就以为它能处理二进制数据,它们是用来处理含不可传输的 ASCII 控制字符的。

要处理其非文本的二进制数据的 Base64 ,可以使用 Node.js 的 Buffer(关于 Buffer 后面有说):

    var result  = window.cep.fs.readFile( "D:/1.txt","Base64");
    var buf = new Buffer (result.data, 'base64');

写入文件

与 CEP 的 readFile() 一样,写入文件接受的数据也是字符串:

    var data = "文本内容";
    var result = window.cep.fs.writeFile( "D:/1.TXT", data);
    if (0 == result.err) {
        // 成功·
    }
    else {
         // 失败
    }

要想对文件写入二进制数据,必须得使用 Base64 模式,并给 writeFile() Base64 字符串 下面这个例子展示的是读取一个二进制文件,并原样写出的过程:

    var inf = window.cep.fs.readFile ("D:/A.ico", "Base64"); //以 Base64 模式读入文件
    window.cep.fs.writeFile ("D:/B.ico", inf.data, "Base64");//以 Base64 模式写出文件

新建目录

window.cep.fs.makedir (path)
在指定位置新建文件夹

    var result = window.cep.fs.makedir(__dirname+"/"+"EEE");
    if (0 == result.err) {
        console.log("成功")
    }
    else {
        console.log("错误:" + result.err)
    }

删除文件

window.cep.fs.deleteFile(path);
用法同上,注意只能删除文件,不能删除文件夹

重命名文件

rename(oldPath, newPath)
可以重命名文件和文件夹。

    var result = window.cep.fs.rename(__dirname+"/EEE" ,__dirname+"/EEE2" );
    if (0 == result.err)
    {
       console.log("重命名成功");  
    }
    else
    {
        console.log("错误:" + result.err);
    }

读取目录中文件列表

window.cep.fs.readdir(path); 可以读取一个文件夹中存放的文件和文件夹(只是一级)列表。
读取的列表以文件名数组的形式存放在返回值的.data

    var result = window.cep.fs.readdir(__dirname );
    if (0 == result.err)
    {
       console.log( result.data);  
           // [".idea", "css", "CSXS", "EEE", "font", "img", "js", "jsx", "tem", ".debug", "1.TXT", "index.html"]
    }
    else
    {
        console.log("错误:" + result.err);
    }

判断路径是文件还是路径

window.cep.fs.stat(path).data.isDirectory()window.cep.fs.stat(path).data.isFile() 可以检测一个路径是文件还是文件夹。

    var result = window.cep.fs.stat(__dirname + "/" + "EEE");
    if (0 == result.err)
    {
            if (result.data.isDirectory() == true)
            {
              console.log("这是个文件夹");
            }
            else if (result.data.isFile() == true)
            {
               console.log("这是个文件");
            }
    }
    else
    {
        console.log("错误:" + result.err)
    }

获取文件最后修改时间

读取 window.cep.fs.stat(path).data.mtime 可以获取文件文件最后一次被修改的时间

设置文件权限

window.cep.fs.chmod (path, mode)
设置指定文件的权限,权限是用 Unix 风格的 chmod 数字表示权限。
在 Windows 上,只能设 0 为文件加上只读属性,777 取消只读属性。

打开、保存文件对话框

window.cep.fs.showOpenDialog (allowMultipleSelection, chooseDirectory, title, initialPath, fileTypes)
window.cep.fs.ShowOpenDialogEx (allowMultipleSelection, chooseDirectory, title, initialPath, fileTypes, friendlyFilePrefix, prompt)
window.cep.fs.showSaveDialogEx (title, initialPath, fileTypes, defaultName, friendlyFilePrefix, prompt, nameFieldLabel)

这个方法能弹出一个系统的文件选择对话框,用来选择或者保存文件,得到的文件以数组的形式存放在返回值的 .data 里。

参数的作用为:
showOpenDialog (允许多选, 选择目录模式, 标题, 初始路径, 指定文件类型数组)
ShowOpenDialogEx (允许多选, 选择目录模式, 标题, 初始路径, 指定文件类型数组, 文件类型说明, prompt消息)
showSaveDialogEx (标题, 初始, 文件类型, 默认名称, 文件类型说明, prompt信息, 名称字段标签)

其中 prompt 信息, 名称字段标签是 OSX 中才起作用的。

选择目录

弹出选择目录对话框,这时只有标题初始路径参数起作用。

   var result = window.cep.fs.showOpenDialog (true, true, "标题", "D:/", "")
   result.data
  //["E:/Qt_Project"]

选择目录模式

 

打开文件

var result = window.cep.fs.showOpenDialogEx (false, false, "标题", "D:/", ["txt","ico"] , "文件类型说明");
result;

打开文件对话框

 

保存文件

 result = window.cep.fs.showSaveDialogEx ("标题", "D:/", ["txt"], "默认名称.TXT", "文件类型说明");
    if (0 == result.err)
    {
        if(result.data.length==0)
        {
           console.log("用户放弃了保存");
        }
        else
        {
           console.log(result.data);
        }
     }
    else
    {
        console.log("错误:" + result.err)
    }

保存文件对话框

 

 

JavaScript 的二进制处理

这虽然只是 JavaScript 范畴的内容,不过由于一般网页设计师对 JavaScript 的二进制处理不会很熟悉,所以稍微说一下用 JavaScript 操作二进制文件的基本方法。

Blob 和 File 对象

Blob 对象是 ECMAScript 5 标准才引入的新内容,Blod 是一个存放二进制数据的容器,然而 Blob 对象成员属性只有 2 个 :size 表示数据长度,type 表示数据的类型(mime type),并不能读写已经放入 Blod 中的数据,他大多数情况下的作用只是用来生成一个可以下载的文件(还有剪贴板、拖拽操作可能会用到):

var myBlob = new Blob(["这是文本"], { "type" : "text\/xml" }); //把数据装入 Blob
var href = window.URL.createObjectURL(blob); //生成下载链接

File 对像继承了 Blob 对象,并增加了 name,lastModifiedDate 等文件相关属性,但依然和 Blob 一样并不能读写已经放入的数据。

ArrayBuffer、TypedArray、DataView

ArrayBuffer

要对二进制数据读写,需要使用缓冲数组:ArrayBuffer ,这个对象实际上对应的就是传统语言中的数组,即在创建申请所需长度的内存,数组内容存放在连续的内存中,并且长度不能动态增减。
在 JavaScript 中 ArrayBuffer 本身只有创建指定长度 ArrayBuffer 的功能,要对 ArrayBuffer 进行修改需要使用数据视图对象 TypedArray 或者 DataView。

var buf = new ArrayBuffer(32); // 创建 32 字节长度的 ArrayBuffer

DataView

数据视图要解决的问题是按何种数据类型去处理数据,举例来说就是一个字节(8位)是把他当成取值范围 0~255 的无符号整数(Uint8) 还是取值范围 -128~127 的带符号整数(Int8)处理。 JavaScript 支持以下数据类型:

数据类型 字节长度 含义 对应的传统语言类型
Int8 1 8 位带符号整数 signed char
Uint8 1 8 位无符号整数 unsigned char
Uint8C 1 8 位无符号整数(自动过滤溢出) unsigned char
Int16 2 16 位带符号整数 short
Uint16 2 16 位无符号整数 unsigned short
Int32 4 32 位带符号整数 int
Uint32 4 32 位无符号的整数 unsigned int
Float32 4 32 位浮点数 float
Float64 8 64 位浮点数 double

一个 8 字节数据不同视图下的处理单位

一个 DataView 只是一个视图,并不存储数据,他指向一个 ArrayBuffer ,我们使用 DataView 的各种方法按想要的数据类型去读写他指向的 ArrayBuffer :

var buf = new ArrayBuffer(32); // 创建 32 字节长度的 ArrayBuffer
var dataView = new DataView(buf);// 以 buf 为数据创建视图 dataView 
dataView.setUint8(3,0xff); //把 dataView 对应数据按 Uint8(无符号 8 位整数 0~255)处理,在第 4 位 Uint8 中写入 0xff
dataView.getUint8(3); // 按 Uint8 读取第 4 位。 返回 255(即 0xff)

DataView 有 3 个属性:

  • buffer :指向的 ArrayBuffer;
  • byteOffset :数据偏移值
  • byteLength :数据长度

表示的是 DataView 可以操作 bufferbyteOffset 开始 byteLength 长度的数据,其可以在创建对象时指定(也可修改):

var buf = new ArrayBuffer(8);// 创建 8字节长度的 ArrayBuffer
var dataView = new DataView(buf, 3, 2);// 以 buf 的第 4 位开始取 2 位长度的数据创建视图 dataView

默认 DataView 会以 ArrayBuffer 的所有数据创建视图。

DataView 读写数据的 .getXXX ,和 setXXX 方法都是从指定字节位来读取数据的,这意味着如果你要依次读取 Uint16 类型(一个 Uint16 占 2 个字节)的数据,你要使用的是 .getUint16(0), .getUint16(2), .getUint16(4), .getUint16(6),需要你手动处理数据类型长度,这有灵活操作的好处,但通常这样只会增加让程序员手动处理数据类型长度的麻烦。所以如果我们不是为了更灵活自由的处理数据类型,或者指定字节序的话更常用 TypedArray (类型化数组)。

TypedArray

TypedArray 类型化数组,和 DataView 实际上的功能是一样,都是为数据操作指定数据类型,不同的是 DataView 是拿到数据后进行可以按各种数据类型进行操作,而 TypedArray 是先按指定的数据类型拿到数据,以后都按这种数据类型进行操作。这样操作带来的好处就是无需程序员手动处理数据类型长度,并且减少每次操作都指定数据类型的麻烦,并且 TypedArray 虽然也是对 ArrayBuffer 的封装,但 TypedArray 可以和数组一样用下标操的方式操作,编写代码和调试都更加方便。

TypedArray 实际上有一组对象,名字为 XXXArray 的形式,XXX 在前面 DataView 那张数据类型表上有。分别是:Int8ArrayUint8ArrayUint8ClampedArrayInt16ArrayUint16Array、

Int32ArraUint32ArrayFloat32ArrayFloat64Array

var buf = new ArrayBuffer(8); // 创建 8 字节长度的 ArrayBuffer
var u8 = new Uint8Array(buf);// 以创建 buf 创建 TypedArray

//同 DataView 一样 TypedArray 也可以只截取 ArrayBuffer 的一部分:
var u8_2 = new Uint8Array(buf,4,2);// 以创建 buf 的第 5 位开始取 2 字节长度创建 TypedArray

//TypedArray 可以直接指定要创建长度,省去手动创建 ArrayBuffer 的过程
var f64a = new Float64Array(8);

//TypedArray 还可以直接输入数组
var x = new Uint8Array([1, 1,4,222]);
      x // [1, 1,4,222]

//TypedArray 还可以根据另一个 TypedArray 克隆
var x = new Uint8Array([1, 1,4,222]);
var y = new Uint8Array(x);
      x // [1, 1,4,222]
      y // [1, 1,4,222]

//TypedArray 还可以与另一个 TypedArray  共用一个 ArrayBuffer
var x = new Uint8Array([1, 1,4,222]);
var y = new Uint8Array(x.buffer); // x.buffer 获取 x 指向的 ArrayBuffer
      x // [1, 1,4,222]
      y // [1, 1,4,222]
y[0] // 1;
x[0] = 2;
y[0] // 2;

TypedArray 使用数组的操作方式读写指向的 ArrayBuffer ,普通数组的操作方法和属性,对TypedArray数组完全适用。
TypedArray 的 .buffer 属性表示指向的 ArrayBuffer 。
TypedArray 有 length 属性,和 byteLength 2个长度属性,其中 length 表示 TypedArray 数组成员个数,也就是有多少个,而 byteLength 是这些成员共占多少字节长度。

Buffer

与上面说的 JavaScript 原生的二进制数据操纵方法不同,Buffer 是 Node.js 提供的对象,Buffer 与 TypedArray 类似,都是以数组的操作方式来处理二进制数据,只是它不依赖于 ArrayBuffer ,但是 Buffer 也并不是直接存储数据,它也是指向一块数据,所以多个 Buffer 对象的实例也可以共用一块数据(使用 .slice())。
相较于 TypedArray ,Buffer 在创建时会有更好的性能表现,因为 ArrayBuffer 创建时会把申请的内存全白写 0 ,而 Buffer 创建时没有这步操作(所以新创建的 Buffer 里会有随机数据)。

var buf = new Buffer(16);  //创建 64 个字节长度的 Buffer

// Buffer 也能从普通数组创建
var x = new Buffer([1,2,3,4]);

// 由于 TypedArray 也有数组的特征,所以可以直接把 TypedArray 转化成 Buffer ,
var v = new Buffer(new Uint8Array([1, 1,4,222]));
        v // {0: 1, 1: 1, 2: 4, 3: 222}

// 反过来 Buffer  也能这样转成 TypedArray 
var g = new Uint8Array(v);
        g //[1, 1,4,222]


// Buffer 能根据另一个 Buffer 克隆 
var y = new Buffer(x);
        y // [1,2,3]
// 所谓克隆自然是深拷贝,与原 Buffer 已经毫无关系了
y[0] = 222;
        x[0] // 1
//而使用 .slice() 得到的是指向原 Buffer 指针
z = x.slice(0) //截取 x 从 0 位置到结尾,并赋值给 z ,这截取的只是指针,也就是 x 和 z 共用一段数据
          z //{0: 1, 1: 2, 2: 3,} 
z[0] = 222; // z 改变 x 也会同时改变
        x[0] // 222

Buffer 也有 readInt8readUInt16LEwriteFloatBE 这样指定数据类型和字节序的方法,使用起来和 DataView 差不多,不在繁述。

 

持久化运行

CEP 扩展面板在 PhotoShop 中默认是不会持久化运行的(而在 illustrator 中是默认持久化运行的),也就是说只要面板被隐藏扩展就会被关闭,面板再打开就会重新载入扩展,相当于浏览网页时,把网页最小化就会重载网页一样,这往往是我们不想要的,所以我们这时就需要把我们的扩展持久化,让它被隐藏时只是不显示而已,不会重新载入。

重新展开面板会导致重新载入面板失去所有状态

 

让我们的扩展保持持久运行,需要发送一个 com.adobe.PhotoshopPersistent 事件,告诉宿主我们的扩展需要持久化运行:

      var cs= new CSInterface();  

      var event = new CSEvent();//创建一个事件
      event.type = "com.adobe.PhotoshopPersistent"; //注册持久化运行事件
      event.scope = "APPLICATION";
      event.extensionId = cs.getExtensionID(); // 我们的扩展 ID
      cs.dispatchEvent(event); //发送事件让宿主持久化运行我们的扩展

 界面颜色

PhotoShop 等宿主应用可以设置界面外观的颜色,而我们的扩展是网页,它不能自动随着界面颜色改变而改变自己的颜色,这需要我们通过 JavaScript 取得宿主应用颜色设置,另外还有捕捉界面颜色改变的事件,让宿主界面颜色改变时我们的扩展面板也能跟着改变颜色。

PhotoShop 的界面颜色

获得界面颜色信息

通过访问 getHostEnvironment()方法获取宿主的信息对象,其中有一个子对象 .appSkinInfo 包含了宿主的界面外观信息:

  var cs = new CSInterface(); 

  var skIinInfo = cs.getHostEnvironment().appSkinInfo; 

  skIinInfo.panelBackgroundColor.color.red // 面板背景色 RGBA 值: R
  skIinInfo.panelBackgroundColor.color.green // 面板背景色 RGBA 值: G
  skIinInfo.panelBackgroundColor.color.blue // 面板背景色 RGBA 值: B
  skIinInfo.panelBackgroundColor.color.alpha // 面板背景色 RGBA 值: A (透明度)
对象 内容
panelBackgroundColor 面板背景颜色
appBarBackgroundColor 应用工具条背景色
panelBackgroundColorSRGB 面板背景颜色 在 sRGB 色彩空间的值
appBarBackgroundColorSRGB 应用工具条背景色 在 sRGB 色彩空间的值
baseFontFamily 界面字体类型
baseFontSize 界面字体尺寸

界面颜色改变事件

通过捕捉 Type 为 "com.adobe.csxs.events.ThemeColorChanged" 的事件就可以捕获界面颜色改变了,同时 CSInterface 有一个常量:CSInterface.THEME_COLOR_CHANGED_EVENT ,它们的作用是一样的,要注意的是这个事件不会向回调函数传递任何信息。

    var cs = new CSInterface(); 
    cs.addEventListener(CSInterface.THEME_COLOR_CHANGED_EVENT, tChanged);

    function tChanged(event)
    {
       var cs = new CSInterface() ;
       var skIinInfo = cs.getHostEnvironment().appSkinInfo;
    }

未完待续

CEP 还有很多接口,先写到这里,有空更新……