# easyofd-java **Repository Path**: likewindy2000/easyofd-java ## Basic Information - **Project Name**: easyofd-java - **Description**: OFD (open fixed layout document) JAVA Version EASYOFD OFD板式文件JAVA版本EASYOFD - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2025-05-26 - **Last Updated**: 2026-09-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: OFD, Java ## README # easyofd-java OFD 板式文件(GB/T 33190-2016)JAVA 生成/读取/签章库。 `easyofd` 提供了一套面向对象的 API,屏蔽 OFD 内部 XML 结构细节,让你像操作画布一样生成 OFD 文档:画文字、画图形、贴图、套用模板页,并支持国密(SM2/SM3)电子签章、数字签名与验签。 - 项目主页: - 标准文档 Wiki: # 一、 入门   本章节介绍如何在项目中引用本easyofd的jar文件,完成项目初始化,建立测试文件文件产生项目OFD文件。   详细内容见 **入门 start** # 二、图形 Path   图形章节介绍OFD中基础的图形绘制相关操作,主要包括了线、矩形、圆和椭圆,也预留了接口支持特殊图形。也是对OFD标准的第九章节9.1 图形对象 PathObject的功能实现部分。   详细内容见 **图形 Path** # 三、图像 Image   图像章节介绍OFD中图像相关内容。也是对OFD标准的第十章节10.1 图像对象 Image的功能实现部分。   详细内容见 **图像 ** # 四、文字 Text   文字在OFD板式文件中站了很大比例,也是很重要的章节。   详细内容见 文字 Text # 五、 模板及页面设置 Template   在OFD板式文件中使用模板概念,可以将各页面通用的内容进行统一的维护利于页面内容的统一,这就可以很大程度上减少了文件的大小。   详细内容见 模板及页面设置 # 六、 数字签名及验签 Singnature   数字签名是OFD板式文件对文件安全考虑的一个重要特性。JAVA版本的easyofd实现了该签名功能,同时页可以对外部已经完成数字签名文件进行验签。 /JAVA/数字签名JAVA # 七、 电子签章及验签 Seal   电子签章是OFD板式文件对文件安全考虑的另一个重要特性。目前很多电子发票和银行具有签章的功能。JAVA版本的easyofd实现了该签章功能,同时页可以对外部已经完成签章文件进行验签。 电子签章及验签 Seal --- ## 目录 - [特性](#特性) - [环境要求](#环境要求) - [安装](#安装) - [快速开始](#快速开始) - [核心类与职责](#核心类与职责) - [使用指南](#使用指南) - [页面与坐标](#页面与坐标) - [文字](#文字) - [图形](#图形) - [图片](#图片) - [模板页](#模板页) - [字体与颜色](#字体与颜色) - [读取已有 OFD](#读取已有-ofd) - [电子签章(电子印章)](#电子签章电子印章) - [数字签名与验签](#数字签名与验签) - [工程结构](#工程结构) - [测试](#测试) - [构建](#构建) - [许可](#许可) --- ## 特性 - **生成 OFD**:按 OFD 标准打包为 `OFD.xml` + `Doc_0/*` 的 ZIP(`.ofd`)文件,自动维护 `MaxUnitID`、资源引用 ID。 - **页面元素**:文字(含竖排、圆形/椭圆环绕文字、字型 Glyph 图元)、图形(直线、矩形、圆、椭圆、五角星、自定义 Path)、图像(含旋转)、复合对象、页面块、层(Layer)。 - **模板页**:`OFDTemplate` 定义一次,多页复用(`Background` / `Foreground`)。 - **资源管理**:字体、颜色空间、绘制参数、多媒体由 `OFDCommonData` / `OFDRes` 统一管理,字体文件与图片自动打包进 OFD。 - **附件**:`OFDAttachment` 支持把任意文件(如 `image/123.png`)作为附件写入 OFD,并能从 OFD 中读取、导出附件。 - **读取 OFD**:`OFDReadFile` 反序列化为 `OFDocument` 对象树(JAXB)。 - **国密签章**:支持 SM2/SM3 电子印章(`SESeal`,遵循 GM/T 0031 电子印章数据结构)、OFD 签章与验签。 - **数字签名**:基于 X.509 国密证书的 OFD 签名与验签。 - **完整 XSD 模型**:`cn.easyofd.xsd` 下为 OFD 标准的 JAXB Bean,可做任意底层定制。 ## 环境要求 - JDK 1.8 及以上 - Maven 3.x - 依赖: - `org.apache.commons:commons-compress:1.26.1`(ZIP 打包/解包) - `org.bouncycastle:bcprov-jdk15on:1.70`、`bcpkix-jdk15on:1.70`(国密算法与证书) - `junit:junit:4.13.1`(仅测试) ## 安装 Maven 坐标: ```xml cn.easyofd easyofd 2.0.0 ``` 本地构建安装: ```bash mvn clean install ``` ## 快速开始 生成一个包含文字和直线的 A4 页面并保存为 `hello.ofd`: ```java import cn.easyofd.document.OFDocument; import cn.easyofd.document.page.OFDPage; import java.io.FileOutputStream; import java.io.OutputStream; public class Hello { public static void main(String[] args) throws Exception { // 1. 创建文档 OFDocument ofDocument = new OFDocument(); // 2. 创建一页并加入文档 OFDPage page = new OFDPage(ofDocument); ofDocument.addPage(page); // 3. 绘制内容(单位:毫米) page.addText("你好,OFD!", 20.0, 30.0); page.addLine(0, 40, 210, 40); // 4. 保存 try (OutputStream os = new FileOutputStream("hello.ofd")) { ofDocument.save(os); } } } ``` OFD 本质是一个 ZIP 包,`save()` 会写出: ``` OFD.xml 根文件 Doc_0/Document.xml 文档主入口与页树 Doc_0/PublicRes.xml 公共资源(字型、颜色空间) Doc_0/DocumentRes.xml 文档资源(绘制参数、多媒体) Doc_0/Page_0/Content.xml 页面内容 Doc_0/Res/… 图片、字体等资源文件 Doc_0/Tpls/Tpl_x/Content.xml 模板页(若使用) ``` ## 核心类与职责 | 类 | 说明 | | --- | --- | | `cn.easyofd.document.OFDocument` | 文档协调类。持有根 `OFD.xml`、`Document.xml`、页列表;`addPage()` 加页,`save(OutputStream)` 打包输出 | | `cn.easyofd.document.page.OFDPage` | 页面类,继承自 `OFDTemplate`,额外提供 `addTemplatePage()` 引用模板页 | | `cn.easyofd.document.Tpls.OFDTemplate` | 模板页/页面基类,提供文字、图形、图像、复合对象等所有绘图 API | | `cn.easyofd.document.OFDCommonData` | 公共数据:`addFont()`、`addColorSpace()`、`addDrawParam()`、`addMultiMedia()`、`addTemplate()`、页面区域设置 | | `cn.easyofd.document.res.OFDRes` | 资源描述文件(`PublicRes.xml` / `DocumentRes.xml`)的封装 | | `cn.easyofd.document.attachment.OFDAttachment` | 附件管理:`addAttachment()` 增加附件,`getAttachmentBytes()` / `saveAttachment()` 读取导出附件 | | `cn.easyofd.document.OFDReadFile` | 读取并解析已有 OFD 文件,返回 `OFDocument` | | `cn.easyofd.document.OFDSealFile` | OFD 电子签章(盖章) | | `cn.easyofd.document.OFDSingnatures` | OFD 数字签名 | | `cn.easyofd.document.OFDVerifySignature` | 签章/签名验证 | | `cn.easyofd.document.seal.SealBuilder` | 生成电子印章 `SESeal`(`.esl`) | | `cn.easyofd.document.signs.x509.GMX509Builder` | 国密 SM2 X.509 证书构建/加载 | | `cn.easyofd.document.util.Util` | 单位换算、文字度量、旋转矩阵、SM2/SM3、XML 序列化等工具 | | `cn.easyofd.xsd.*` | OFD 标准对应的 JAXB Bean(由 `src/main/java/xsd/*.xsd` 生成) | | `cn.easyofd.asn1.seal.*` | 电子印章 ASN.1 数据结构(`SESeal`、`SES_SealInfo` 等) | ## 使用指南 ### 页面与坐标 - 坐标系原点在页面左上角,单位为**毫米(mm)**。 - 默认页面为 A4:`0 0 210 297`,默认字体宋体,默认字号 `3.70mm`(五号)。 - 可通过 `OFDocument#getOfdCommonData()` 修改页面区域: ```java ofDocument.getOfdCommonData().setPagePhysicaArea(210, 297); // 物理区域 ofDocument.getOfdCommonData().setPageApplicationArea(210, 297); // 显示区域 ofDocument.getOfdCommonData().setPageContentArea(180, 260); // 版心区域 ofDocument.getOfdCommonData().setBleedArea(210, 297); // 出血区域 ``` 亦可在页面级设置:`page.setPhysicalBox(new CTBoundary(0, 0, 210, 297))` 等。 多页文档只需重复 `addPage()`: ```java OFDPage page1 = new OFDPage(ofDocument); OFDPage page2 = new OFDPage(ofDocument); ofDocument.addPage(page1); ofDocument.addPage(page2); ``` ### 文字 ```java CTColor red = new CTColor(); red.setValue("255 0 0"); CTColor green = new CTColor(); green.setValue("0 255 0"); page.addText("默认宋体", 20.0, 20.0); // 默认字号 3.7mm page.addText("5mm 字号", 20, 30, 5); page.addText("竖排阅读方向", 20, 40, 5, 90); // readDirection: 0 / 90 page.addText("字体颜色", 100, 40, 8, red, null); // fillColor, strokeColor page.addText("字体勾边", 80, 100, 18, green, red); ``` 环绕文字(常用于印章): ```java // 圆形环绕:圆心 (x,y)、半径 r、起止角度、方向 1-顺时针 2-逆时针 page.addTextCircleAndTrans("电子印章专用文字", 100, 100, 40, 0, 360, 1); // 椭圆环绕:长轴 a、短轴 b page.addTextEllipseAndTrans("椭圆环绕文字", 100, 100, 60, 40, 0, 360, 1); ``` 字型图元(`Glyphs`,把文字转为字形索引序列,便于脱离系统字体渲染): ```java Font font = new Font("宋体", Font.PLAIN, 14); page.addTextGlyph("文字图元", 20, 50, 5, ofDocument.getOfdCommonData().getDefaultFontid(), font); ``` > 说明:`addText` 的 `y` 坐标为文字 baseline,方法内部会依据 `FontMetrics` 自动计算 `Boundary`。 ### 图形 ```java page.addLine(0, 20, 210, 20); // 直线 page.addLine(0, 30, 210, 30, 0.5, red); // 直线 + 线宽 + 颜色 page.addSquare(20, 40, 100, 50); // 矩形(x, y, w, h) page.addSquare(20, 100, 100, 50, red, green); // 矩形 + 勾边 + 填充 page.addCircle(100, 100, 30); // 圆 page.addCircle(100, 100, 30, red, green, 0.5, null, null); page.addEllipse(100, 150, 40, 20, 0.5); // 椭圆(长轴 a,短轴 b) page.addPentagram(100, 200, 20, red, green, 0.5); // 正五角星 ``` 虚线与端点样式: ```java page.addLine(0, 60, 210, 60, 0.353, red, 0.0, "3 2", "Round"); page.addSquare(20, 70, 100, 30, red, null, 0.5, 0.0, "5 3", "Round", "Miter", 3.528); ``` 自定义 Path(`AbbreviatedData`): ```java CTBoundary boundary = new CTBoundary(10, 10, 100, 60); page.addPathObject("M 0 0 L 100 0 L 100 60 L 0 60 C", boundary); ``` 也可以直接构造底层对象并加入页面: ```java page.addPathObject(pathObject); page.addTextObject(textObject); page.addImageObject(imageObject); page.addCompositeObject(compositeObject); page.addPageBlock(pageBlock); ``` ### 图片 图片先注册进文档资源(返回资源 ID),再在页面上按 `Boundary` 绘制: ```java File file = new File("image/123.png"); byte[] bytes = Files.readAllBytes(file.toPath()); // 1. 注册多媒体资源(自动打包进 Doc_0/Res/) int picID = ofDocument.getOfdCommonData().addMultiMedia("Image", "PNG", file.getName(), bytes); // 2. 在页面绘制 CTBoundary boundary = new CTBoundary(40, 40, 40, 30); // x, y, w, h page.addImageObject(picID, file.getName(), "PNG", boundary); // 3. 带旋转角度的绘制(角度制) page.addImageObject(picID, file.getName(), "PNG", new CTBoundary(120, 40, 40, 30), 45); ``` 支持格式:BMP、JPEG、TIFF、PNG。 ### 模板页 模板页适合页眉页脚、水印、背景底纹等多页复用场景: ```java OFDTemplate template = new OFDTemplate(ofDocument); CTColor gray = new CTColor(); gray.setValue("135 206 250"); template.addSquare(0, 0, 210, 297, null, gray); template.addText("模板页内容", 50, 50); // 注册模板,返回模板 ID int templateId = ofDocument.getOfdCommonData().addTemplate(template); // 页面引用模板(ZOrder: Background / Foreground) OFDPage page = new OFDPage(ofDocument); page.addTemplatePage(templateId, "Background"); ofDocument.addPage(page); ``` ### 字体与颜色 ```java // 使用系统已集成字型(不嵌入字体文件) int kaiId = ofDocument.getOfdCommonData().addFont("楷体"); // 注册并嵌入外部字体文件(字体名需与文件名一致) int fontId = ofDocument.getOfdCommonData().registerFont("思源黑体", "font/思源黑体 Normal.ttc"); // 颜色空间与绘制参数 int csId = ofDocument.getOfdCommonData().addColorSpace("RGB"); int dpId = ofDocument.getOfdCommonData().addDrawParam(red, 0.5); ``` 颜色空间类型可取值:`Gray`、`RGB`、`CMYK`。 ### 附件 附件定义见 [OFD 标准 - 附件](http://wiki.easyofd.cn/OFD%E6%A0%87%E5%87%86/%E9%99%84%E4%BB%B6)。 附件列表文件为 `Doc_0/Attachments.xml`,附件内容文件默认存放于 `Doc_0/Attachs/` 下。 **写入附件:** ```java // 文件方式:把 font/楷体.ttf 作为附件存入 OFD CTAttachment attachment = ofDocument.addAttachment(new File("font/楷体.ttf")); // 路径方式 ofDocument.addAttachment("font/楷体.ttf"); // 字节方式:可指定是否可见(Visible)与用途(Usage) byte[] bytes = Files.readAllBytes(Paths.get("font/楷体.ttf")); ofDocument.addAttachment("楷体.ttf", "ttf", bytes, true, "none"); ``` `addAttachment()` 会自动补充 `ID`、`FileLoc`、`Format`、`Size`(KB)、`CreationDate`、`ModDate`, 并在 `Document.xml` 中设置附件列表入口 `Attachments.xml`。 同名附件会自动追加序号(`楷体_1.ttf`)。 **读取附件:** ```java OFDReadFile ofdReadFile = new OFDReadFile(new File("XML/attachment/attachment.ofd")); OFDocument ofDocument = ofdReadFile.read(); OFDAttachment ofdAttachment = ofDocument.getOfdAttachment(); // 没有附件时为 null // 附件描述列表 List attachments = ofdAttachment.getAttachment(); for (CTAttachment one : attachments) { System.out.println(one.getName() + " " + one.getFormat() + " " + one.getSize() + "KB"); } // 按名称 / 按 FileLoc 获取附件内容 byte[] data = ofdAttachment.getAttachmentBytesByName("楷体.ttf"); byte[] data2 = ofdAttachment.getAttachmentBytes("Attachs/楷体.ttf"); // 附件另存为文件 ofdAttachment.saveAttachmentByName("楷体.ttf", new File("XML/attachment/楷体-out.ttf")); ofdAttachment.saveAttachmentToDir("楷体.ttf", "XML/attachment"); ``` 生成的 `Doc_0/Attachments.xml`: ```xml Attachs/楷体.ttf ``` ### 读取已有 OFD ```java OFDReadFile ofdReadFile = new OFDReadFile(new File("XML/image/image-beRead.ofd")); OFDocument ofDocument = ofdReadFile.read(); // 读取后即可访问公共数据、页树、资源、附件等 OFDCommonData commonData = ofDocument.getOfdCommonData(); List pages = ofDocument.getOfdpages(); OFDAttachment attachments = ofDocument.getOfdAttachment(); ``` `OFDReadFile` 还提供 `getFilesMap()`(包内所有条目)、`getFilePath()`(文件路径列表)、`getZipFile()` 等底层访问能力。 ### 电子签章(电子印章) **1) 制作电子印章(`.esl`)** ```java byte[] picBytes = Files.readAllBytes(Paths.get("XML/seal/seal.png")); ASN1OctetString picData = new DEROctetString(picBytes); ASN1OctetString cert = new DEROctetString(certificate.getEncoded()); SESeal seSeal = SealBuilder.getInstance( "印章标识ID", // ID 1, // 类型:01法定名称章 02财务专用章 03发票专用章 04合同专用章 05名章 "某某公司公章", (BCECPrivateKey) privateKey, // 国密 SM2 私钥 "png", picData, 118, 118, // 图像显示宽高 null, // 自定义扩展数据 cert); // 证书 ``` **2) 在 OFD 上盖章** ```java SESeal seSeal = Util.getSealFromFile("XML/seal/seal-out.esl"); BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) getPrivateKey("XML/seal/seal-key.esl"); OFDSealFile ofdSealFile = new OFDSealFile(new File("XML/image/image-beSeal.ofd"), seSeal); ofdSealFile.seal(bcecPrivateKey, new CTBoundary(10, 10, 40, 30)); // 印章位置与大小 try (OutputStream os = new FileOutputStream("XML/seal/image-seal.ofd")) { ofdSealFile.saveOFD(os); } ``` `seal(...)` 另有重载可指定 `fileReference`(仅对指定文件计算摘要)。 **3) 验证签章** ```java Security.addProvider(new BouncyCastleProvider()); OFDVerifySignature verify = new OFDVerifySignature(new File("XML/seal/image-seal.ofd")); verify.setSealCheck(sesSignature -> { // 自定义印章校验逻辑 return true; }); boolean ok = verify.verifySignature(); ``` ### 数字签名与验签 **签名:** ```java X509Certificate certificate = GMX509Builder.getInstance("key/ofd.cert.pem"); BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) loadPrivateKey("key/ofd.key.pem"); OFDSingnatures signatures = new OFDSingnatures(new File("XML/image/image-beSigns-1.ofd"), certificate); signatures.siqn(bcecPrivateKey); // 也可传入 String[] fileReference 指定签名范围 try (OutputStream os = new FileOutputStream("XML/signs/image-signs-2.ofd")) { signatures.saveOFD(os); } ``` **验签:** ```java Security.addProvider(new BouncyCastleProvider()); OFDVerifySignature verify = new OFDVerifySignature(new File("XML/signs/image-signs.ofd")); verify.setSignsCheck(contentInfo -> { // 自定义签名值校验逻辑 return true; }); boolean ok = verify.verifySignature(); ``` **生成/加载国密证书:** ```java // 加载 PEM 证书 X509Certificate cert = GMX509Builder.getInstance("key/ofd.cert.pem"); // 生成自签名 SM2 证书 GMX509Builder builder = GMX509Builder.getInstance( BigInteger.ONE, new X500Name("CN=easyofd"), new X500Name("CN=test"), Calendar.getInstance(), expiry, extensionsGenerator); X509Certificate newCert = builder.getCertificate(); ``` ## 工程结构 ``` easyofd-java/ ├─ pom.xml 示例工程依赖(easyofd + junit + bouncycastle) ├─ LICENSE Apache-2.0 ├─ README.md ├─ font/ 开源字体(思源黑体,SIL OFL),见 font/README.md ├─ image/ 示例图片 ├─ out/ 示例输出目录(Git 忽略) └─ src/test/java/cn/easyofd/demo/ ├─ QuickStartTest.java 入门:创建文档、写文字、画线、画矩形 ├─ PathDemoTest.java 图形:线、矩形、圆、椭圆、五角星 ├─ ImageDemoTest.java 图像:图片与旋转 ├─ TextDemoTest.java 文字:注册字体、沿圆/椭圆排布 ├─ TemplateDemoTest.java 模板页 ├─ AttachmentDemoTest.java 附件写入 ├─ SignDemoTest.java 数字签名与验签 ├─ SealDemoTest.java 制作电子印章、盖章与验章 ├─ ReadDemoTest.java 读取已有 OFD ├─ DemoDocs.java 生成示例用的基础文档 ├─ DemoKeys.java 运行时生成证书与私钥 └─ DemoOutput.java 输出目录管理 ``` > 本仓库只含示例工程,核心库以 Maven 依赖 `cn.easyofd:easyofd` 引入。 ## 测试 测试用例即用即查的示例,运行全部测试: ```bash mvn test ``` 生成的 OFD 文件输出到 `out/` 目录: | 场景 | 示例类 | 输出 | | --- | --- | --- | | 入门 | `QuickStartTest` | `out/quickstart.ofd` | | 图形 | `PathDemoTest` | `out/path/path.ofd` | | 图片与旋转 | `ImageDemoTest` | `out/image/image.ofd` | | 文字 | `TextDemoTest` | `out/text/text.ofd` | | 模板页 | `TemplateDemoTest` | `out/template/template.ofd` | | 附件写入 | `AttachmentDemoTest` | `out/attachment/attachment.ofd` | | 数字签名与验签 | `SignDemoTest` | `out/sign/signed.ofd` | | 电子签章与验章 | `SealDemoTest` | `out/seal/sealed.ofd` | | 读取 OFD | `ReadDemoTest` | `out/read/read.ofd` | ## 构建 ```bash # 编译 mvn clean compile # 打包 mvn clean package # 安装到本地仓库 mvn clean install # 生成 Javadoc mvn javadoc:javadoc ``` > 说明:发布到 Maven Central 使用了 `central-publishing-maven-plugin` 与 `maven-gpg-plugin`,普通构建不受影响。 ## 许可 Apache License, Version 2.0。详见每个源文件头部的版权声明: ``` Copyright 2025 ZhangXinPan (11627685@qq.com) Licensed under the Apache License, Version 2.0 ``` 联系与反馈:<11627685@qq.com>