目录
- 一、下载Jacob
- 二、创建JacobDemo maven工程,引入jacob依赖
- 1. 创建工程
- 2. 引入jacob依赖
- 3. 在pom.xml中引入jacob.jar
- 三、在代码中操作Adobe Illustrator
- 四、Jacob中的Dispatch简单介绍
在Windows下,使用Java操作Adobe Illustrator(下称“AI”),需要通过JNI技术调用C++来实现。在本地安装好AI后,AI会在注册表注册自己的COM信息。C++之所以能够操作COM组件,是因为Windows提供了一整套技术来支持。
当然,在2021年,我们已经不需要自己去手动写C++代码来对对AI进行操作,而是使用Jacob开源中间件(发音类似“雅各布”,协议LGPLv2)。Jacob全称是Java-COM Bridge,也就是它是一个方便Java调用COM组件的一个库。Jacob是一个从1999年就开始的开源项目。Jacob最初将源代码托管在sourceforge:https://sourceforge.net/projects/jacob-project/ 上,在去年9月份(2020-9-25)迁移到了github上。最新的v1.20支持在Windows 10+Java8 32/64位下运行。
不过有个奇怪的事情,如果你在github搜索,会发现一份最后更新时间为2011年的Jacob项目:https://github.com/joval/jacob。具体原因我就没有深究了。同时,在开源中国上也有一个相关的项目Jacob:。
当然,在开始下面的步骤之前,你的电脑上应该安装了Adobe Illustrator并能够正常使用。我这里安装的版本是Adobe Illustrator CC 2017(21.0.0),需要注意的是,如果安装了Adobe Illustrator CC 2019(23.0.0)会出现无法通过Java打开Illustrator的情况,貌似需要升级到23.0.2。
环境说明:
- 操作系统:Windows 10 64位
- JDK:JDK1.8 102 32位
- Jacob:1.20
- IDEA:2020.3.3
- Adobe Illustrator: CC 2017(21.0.0)
一、下载Jacob
Jacob迁移到github之后,sourceforge上就无法下载了。可以在此处:https://github.com/freemansoft/jacob-project/releases 下载最新版1.20(2020-9-25发布)。或者,直接点击jacob-1.20.zip:https://github.com/freemansoft/jacob-project/releases/download/Root_B-1_20/jacob-1.20.zip 下载不带源码版的压缩包。解压后文件夹内容如下:
其中,jacob.jar需要在Java工程中引入,而jacob-1.20-x86.dll或jacob-1.20-x64.dll需要加载到当前Java程序的库目录下(比如:当前目录、java.library.path等文件夹下都可以)。
二、创建JacobDemo maven工程,引入jacob依赖
1. 创建工程
这里我使用IntelliJ IDEA 2020.3.3创建maven工程对jacob进行简单的介绍。在菜单上,点击File -> New -> Project…打开如下对话框,左侧选择maven。
新项目的名称定为JacobDemo,点击Finish完成项目创建。
2. 引入jacob依赖
项目创建完成后,我们将解压出来的jacob.jar、jacob-1.20-x86.dll、jacob-1.20-x64.dll复制到项目根目录下,在我这即是~/IdeaProjects/JacobDemo/
。
如果没有正确复制dll文件,那么会在执行时抛出异常:no jacob-1.20-x86 in java.library.path
。
3. 在pom.xml中引入jacob.jar
在pom.xml中增加如下配置
<dependencies>
<dependency>
<groupId>com.jacob</groupId>
<artifactId>Jacob</artifactId>
<version>1.20</version>
<scope>system</scope>
<systemPath>${basedir}/jacob.jar</systemPath>
</dependency>
</dependencies>
三、在代码中操作Adobe Illustrator
下一步,我们创建一个JacobDemo类,并填充样例代码。
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
public class JacobDemo {
public static void main(String[] args) {
ActiveXComponent app = new ActiveXComponent("Illustrator.Application"); // 创建一个Application对象
Dispatch docs = Dispatch.call(app, "Documents").toDispatch(); // 获取app中所有的文稿对象Documents
Dispatch doc = Dispatch.call(docs, "Add").toDispatch(); // 调用Documents的Add方法,得到一个新文稿
Dispatch textFrames = Dispatch.call(doc, "TextFrames").toDispatch(); // 获取doc下的所有TextFrames
Dispatch textFrame = Dispatch.call(textFrames, "Add").toDispatch();
Dispatch.put(textFrame, "Top", 600);
Dispatch.put(textFrame, "Left", 100);
Dispatch.put(textFrame, "Contents", "hello, illustrator");
}
}
执行上述main方法,可以看到,在执行过程中,Adobe Illustrator会自动打开,并新建一个窗口,在窗口左上角添加了一个文本框,框中内容为“hello, illustrator”。如下图所示。
好了,这就是一个基本的使用Java调用Adobe Illustrator的Demo。
四、Jacob中的Dispatch简单介绍
通过阅读demo代码我们可以发现,基本上都是调用Dispatch类的静态方法来达到操作目的。假如Java中有相应的类,那么demo的代码将等同于如下代码。
public class JacobDemo {
public static void main(String[] args) {
Illustrator.Application app = new Illustrator.Application();
Document doc = app.getDocuments().Add();
TextFrame textFrame = doc.getTextFrames().Add();
textFrame.setTop(600);
textFrame.setLeft(100);
textFrame.setContents("hello, illustrator");
}
}
实际上,通过Java调用Illustrator.Application中暴露出来的方法,基本上分为这三类:
- 获取基本属性的值,如获取文本框的宽度,高度等
- 设置基本属性的值,如设置文本框的偏移量、文本内容等
- 调用某个对象的某个方法。
所以,针对以上三类,对应着Dispatch中的3个方法:Dispatch.get(Dispatch, String)、Dispatch.put(Dispatch,String,Object)、Dispatch.call(Dispatch,String,Object…)。我们增加JacobDemo2,代码如下。
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
public class JacobDemo2 {
public static void main(String[] args) {
ActiveXComponent app = new ActiveXComponent("Illustrator.Application");
Dispatch docs = Dispatch.get(app, "Documents").toDispatch(); // 此处修改为get,与call等效,但语义更明确
Dispatch doc = Dispatch.call(docs, "Add").toDispatch();
Dispatch textFrames = Dispatch.get(doc, "TextFrames").toDispatch();// 此处修改为get,与call等效,但语义更明确
Variant textFrameVar = Dispatch.call(textFrames, "Add"); // 看一看call方法的返回类型
Dispatch textFrame = textFrameVar.toDispatch();
Dispatch.put(textFrame, "Top", 600);
Dispatch.put(textFrame, "Left", 100);
Dispatch.put(textFrame, "Contents", "hello, illustrator");
Variant widthVar = Dispatch.get(textFrame, "Width");
double width = widthVar.getDouble();
System.out.println("width = " + width); // 控制台输出:width = 72.94677734375
// 以下是另存为一个新文件,并关闭的代码示例
ActiveXComponent saveOptions = new ActiveXComponent("Illustrator.IllustratorSaveOptions");
Dispatch.put(saveOptions, "Compatibility", 17);
Dispatch.put(saveOptions, "FlattenOutput", 1);
Dispatch.call(doc, "SaveAs", "~\\IdeaProjects\\JacobDemo\\test.ai", saveOptions);
Dispatch.call(doc, "Close");
}
}
通过Demo代码,我们还可以发现,Dispatch贯穿代码始终,那么这个Dispatch是什么呢?在这里,你可以简单理解为,如果通过Dispatch.get/put/call方法得到的返回值为基本数据类型(如,获取文本框的宽高),那么不能转为Dispatch(毕竟,基本数据类型已经不需要继续调用Dipatch的方法了)。如果需要进一步调用Dispatch.get/put/call方法时,需要需要将前一次调用的返回值toDispatch()之后传递给这次的第一个参数。
Dispatch.get/put/call返回值是一个Variant对象,系统并不知道这个返回值是否是一个可继续调用的对象,所以,我们需要根据API来决定是否把这个Variant转为Dispatch(toDispatch
方法)还是某个具体的基本数据类型(getInt
、getDouble
等方法)。