OFD 图元与图层
本节介绍页面内容流中的图元对象(foxit::ofd::graphics::OFDGraphicsObject 及其子类型)与图层(OFDLayer)的遍历与编辑,用于版式分析、内容抽取或二次编辑。取得 OFDPage 的方式请参阅 OFD 文档与页面。
任务场景
- 遍历页面图层与图元树,提取文本、路径矢量或图像资源及坐标信息。
- 在指定图层上动态添加路径、文本、图像等图元(如绘制边框、插入图片)。
- 嵌入视频图元,或处理含缩略图/替代内容的复合对象(
OFDCompositeObject)。 - 通过
TextLayout、CharInfo进行多行排版、对齐与字符级位置控制。 - 为图元绑定跳转、URI 等交互动作,请参阅 OFD 注释与动作 中的动作章节。
API 概览
| 功能 | C++ API(foxit::ofd / graphics) | 核心参数 / 描述 |
|---|---|---|
| 页面图层 | OFDPage::GetLayerCount、GetLayer | 按索引获取页面图层;图层类型含背景 / 正文 / 前景(OFDLayer::e_LayerTypeBackground 等) |
| 图层与图元容器 | OFDLayer | 继承自 OFDGraphicsObject;AddPathObject、AddTextObject、AddImageObject 等;GetGraphicsObjectCount、GetGraphicsObject、RemoveGraphicsObject |
| 图元基类 | graphics::OFDGraphicsObject | GetType、GetID、GetBoundary、GetMatrix、GetAlpha;颜色、线宽、虚线;CreateActions / GetActions |
| 路径图元 | graphics::OFDPathObject | SetFill / SetStroke、SetFillMode;OFDPathData(MoveTo、LineTo、CubicBezierTo 等)或 SetAbbreviatedData |
| 文本图元 | graphics::OFDTextObject | 字体、字号、阅读/字符方向;GetTextLayout、SetCharInfos;CharInfo |
| 图像图元 | graphics::OFDImageObject | SetImage(resource_id) 或从文件/缓冲区加载;GetImageBuffer、GetImageFormat |
| 视频图元 | graphics::OFDVideoObject | SetVideoFromFile、SetType、SetFormat、SetTitle;视频边框(圆角、虚线等) |
| 复合图元 | graphics::OFDCompositeObject | AddUnit 添加 OFDCompositeUnit;SetThumbnail、SetSubstitution |
| 块对象 | graphics::OFDBlockObject | 继承自 OFDLayer,作为嵌套图元容器(e_ObjectTypePageBlock) |
遍历与访问页面图元
典型流程:从 OFDPage 获取图层 → 遍历图层内图元 → 根据 GetType 转为具体子类型。GetBoundary 与 GetMatrix 均基于 OFD 页面坐标系,实际渲染位置由二者共同决定。
c++
#include "ofd/fs_ofdpackage.h"
#include "ofd/fs_ofddoc.h"
#include "ofd/fs_ofdpage.h"
#include "ofd/fs_ofdlayer.h"
#include "ofd/graphics/fs_ofdgraphicsobject.h"
#include "ofd/graphics/fs_ofdpathobject.h"
#include "ofd/graphics/fs_ofdtextobject.h"
#include "ofd/graphics/fs_ofdimageobject.h"
using namespace foxit;
using namespace foxit::ofd;
using namespace foxit::ofd::graphics;
void TraversePageGraphics(const wchar_t* input_file) {
OFDPackage package(input_file);
if (package.IsEmpty() || package.GetDocumentCount() <= 0) {
return;
}
OFDDoc doc = package.LoadDocument(0, L"");
if (doc.IsEmpty() || doc.GetPageCount() <= 0) {
return;
}
OFDPage page = doc.GetPage(0);
if (page.IsEmpty()) {
return;
}
// 遍历页面上的所有图层
int layer_count = page.GetLayerCount();
for (int i = 0; i < layer_count; ++i) {
OFDLayer layer = page.GetLayer(i);
if (layer.IsEmpty()) {
continue;
}
// 遍历图层内的图元
int object_count = layer.GetGraphicsObjectCount();
for (int j = 0; j < object_count; ++j) {
OFDGraphicsObject obj = layer.GetGraphicsObject(j);
if (obj.IsEmpty()) {
continue;
}
ObjectType type = obj.GetType();
RectF boundary = obj.GetBoundary();
Matrix matrix = obj.GetMatrix();
switch (type) {
case e_ObjectTypeImage: {
OFDImageObject image_obj(obj);
// 读取图像格式与缓冲区
break;
}
case e_ObjectTypeText: {
OFDTextObject text_obj(obj);
// 读取字体、字号、字符信息等
break;
}
case e_ObjectTypePath: {
OFDPathObject path_obj(obj);
// 读取路径数据或缩写路径
break;
}
case e_ObjectTypePageBlock: {
OFDBlockObject block_obj(obj);
// 块对象本身也是图层容器,可继续递归遍历
break;
}
default:
break;
}
}
}
}
java
import com.foxit.sdk.common.fxcrt.Matrix2D;
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.ofd.OFDDoc;
import com.foxit.sdk.ofd.OFDLayer;
import com.foxit.sdk.ofd.OFDPackage;
import com.foxit.sdk.ofd.OFDPage;
import com.foxit.sdk.ofd.graphics.OFDBlockObject;
import com.foxit.sdk.ofd.graphics.OFDGraphicsObject;
import com.foxit.sdk.ofd.graphics.OFDImageObject;
import com.foxit.sdk.ofd.graphics.OFDPathObject;
import com.foxit.sdk.ofd.graphics.OFDTextObject;
public class OFDGraphicsTraverseExample {
public static void traversePageGraphics(String inputFile) throws Exception {
OFDPackage pkg = new OFDPackage(inputFile);
if (pkg.isEmpty() || pkg.getDocumentCount() <= 0) {
return;
}
OFDDoc doc = pkg.loadDocument(0, null);
if (doc.isEmpty() || doc.getPageCount() <= 0) {
return;
}
OFDPage page = doc.getPage(0);
if (page.isEmpty()) {
return;
}
int layerCount = page.getLayerCount();
for (int i = 0; i < layerCount; i++) {
OFDLayer layer = page.getLayer(i);
if (layer.isEmpty()) {
continue;
}
int objectCount = layer.getGraphicsObjectCount();
for (int j = 0; j < objectCount; j++) {
OFDGraphicsObject obj = layer.getGraphicsObject(j);
if (obj.isEmpty()) {
continue;
}
int type = obj.getType();
RectF boundary = obj.getBoundary();
Matrix2D matrix = obj.getMatrix();
if (type == OFDGraphicsObject.e_ObjectTypeImage) {
OFDImageObject imageObj = new OFDImageObject(obj);
} else if (type == OFDGraphicsObject.e_ObjectTypeText) {
OFDTextObject textObj = new OFDTextObject(obj);
} else if (type == OFDGraphicsObject.e_ObjectTypePath) {
OFDPathObject pathObj = new OFDPathObject(obj);
} else if (type == OFDGraphicsObject.e_ObjectTypePageBlock) {
OFDBlockObject blockObj = new OFDBlockObject(obj);
}
}
}
}
}
创建与编辑图元
通过 OFDLayer::AddPathObject、AddTextObject、AddImageObject 等工厂方法创建图元,设置边界框、颜色、线宽等通用属性;不需要的图元可调用 RemoveGraphicsObject 或 RemoveAllGraphicsObjects 删除。修改后须 OFDPackage::Save / SaveAs 写回文件。
c++
#include "ofd/fs_ofdlayer.h"
#include "ofd/graphics/fs_ofdpathobject.h"
#include "ofd/graphics/fs_ofdtextobject.h"
#include "ofd/graphics/fs_ofdimageobject.h"
using namespace foxit;
using namespace foxit::ofd;
using namespace foxit::ofd::graphics;
void AddGraphicsObjects(OFDLayer& layer, uint32 image_res_id) {
if (layer.IsEmpty()) {
return;
}
// 添加路径图元并设置描边样式
OFDPathObject path_obj = layer.AddPathObject();
if (!path_obj.IsEmpty()) {
path_obj.SetStroke(true);
path_obj.SetLineWidth(0.5f);
path_obj.SetStrokeColor(RGB(255, 0, 0));
}
// 添加文本图元,通过 TextLayout 设置内容与排版
OFDTextObject text_obj = layer.AddTextObject();
if (!text_obj.IsEmpty()) {
text_obj.SetFontName(L"SimSun");
text_obj.SetFontSize(12);
text_obj.SetBoundary(RectF(10, 10, 200, 200));
text_obj.SetFillColor(RGB(0, 255, 0));
TextLayout text_layout = text_obj.GetTextLayout();
if (!text_layout.IsEmpty()) {
text_layout.SetText(L"Hello OFD");
text_layout.SetFontName(L"SimSun");
text_layout.SetFontSize(12);
text_layout.Update();
}
}
// 添加图像图元,image_res_id 由 OFDDoc::AddImageResourceFromFile 等接口获得
OFDImageObject img_obj = layer.AddImageObject();
if (!img_obj.IsEmpty()) {
img_obj.SetImage(image_res_id);
img_obj.SetBoundary(RectF(100, 100, 200, 200));
img_obj.SetVisible(false);
}
}
java
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.ofd.OFDLayer;
import com.foxit.sdk.ofd.graphics.OFDImageObject;
import com.foxit.sdk.ofd.graphics.OFDPathObject;
import com.foxit.sdk.ofd.graphics.OFDTextObject;
import com.foxit.sdk.ofd.graphics.TextLayout;
public class OFDGraphicsEditExample {
public static void addGraphicsObjects(OFDLayer layer, int imageResId) throws Exception {
if (layer.isEmpty()) {
return;
}
OFDPathObject pathObj = layer.addPathObject();
if (!pathObj.isEmpty()) {
pathObj.setStroke(true);
pathObj.setLineWidth(0.5f);
pathObj.setStrokeColor(0xFF0000);
}
OFDTextObject textObj = layer.addTextObject();
if (!textObj.isEmpty()) {
textObj.setFontName("SimSun");
textObj.setFontSize(12);
textObj.setBoundary(new RectF(10, 10, 200, 200));
textObj.setFillColor(0x00FF00);
TextLayout textLayout = textObj.getTextLayout();
if (!textLayout.isEmpty()) {
textLayout.setText("Hello OFD");
textLayout.setFontName("SimSun");
textLayout.setFontSize(12);
textLayout.update();
}
}
OFDImageObject imgObj = layer.addImageObject();
if (!imgObj.isEmpty()) {
imgObj.setImage(imageResId);
imgObj.setBoundary(new RectF(100, 100, 200, 200));
imgObj.setVisible(false);
}
}
}
矢量路径绘制
路径可使用 OFDPathData 逐点构建(StartFigure、MoveTo、LineTo、CubicBezierTo、ArcTo、CloseFigure),也可通过 SetAbbreviatedData 设置 OFD 缩写路径字符串以减小文件体积。填充规则支持 e_PathFillNonZero、e_PathFillEvenOdd 等。
c++
#include "ofd/graphics/fs_ofdpathobject.h"
using namespace foxit;
using namespace foxit::ofd::graphics;
void DrawRectanglePath(OFDLayer& layer) {
OFDPathObject path_obj = layer.AddPathObject();
if (path_obj.IsEmpty()) {
return;
}
path_obj.SetBoundary(RectF(20.0f, 95.0f, 45.0f, 25.0f));
path_obj.SetFill(true);
path_obj.SetStroke(true);
path_obj.SetFillMode(OFDPathObject::e_PathFillNonZero);
path_obj.SetFillColor(RGB(255, 255, 200));
path_obj.SetStrokeColor(RGB(40, 50, 60));
path_obj.SetLineWidth(0.5f);
// 方式一:缩写路径(适合简单矩形等)
path_obj.SetAbbreviatedData("M 0 0 L 20 0 L 20 20 L 0 20 B");
// 方式二:OFDPathData 逐点构建(适合复杂曲线)
// OFDPathData path_data;
// path_data.StartFigure(0, 0);
// path_data.LineTo(20, 0);
// path_data.LineTo(20, 20);
// path_data.CloseFigure();
// path_obj.SetPathData(path_data);
}
java
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.ofd.OFDLayer;
import com.foxit.sdk.ofd.graphics.OFDPathObject;
public class OFDPathDrawExample {
public static void drawRectanglePath(OFDLayer layer) throws Exception {
OFDPathObject pathObj = layer.addPathObject();
if (pathObj.isEmpty()) {
return;
}
pathObj.setBoundary(new RectF(20.0f, 95.0f, 45.0f, 25.0f));
pathObj.setFill(true);
pathObj.setStroke(true);
pathObj.setFillMode(OFDPathObject.e_PathFillNonZero);
pathObj.setFillColor(0x00FFFFC8);
pathObj.setStrokeColor(0x0028323C);
pathObj.setLineWidth(0.5f);
pathObj.setAbbreviatedData("M 0 0 L 20 0 L 20 20 L 0 20 B");
}
}
文本排版
OFDTextObject 支持 SetReadDirection、SetCharDirection 等阅读方向设置。复杂排版可通过 GetTextLayout 取得 TextLayout,设置文本内容、字号、对齐与行距后调用 Update 生效;字符级精确定位可使用 CharInfo 与 SetCharInfos。
c++
#include "ofd/graphics/fs_ofdtextobject.h"
using namespace foxit::ofd::graphics;
void ConfigureTextLayout(OFDTextObject& text_obj) {
if (text_obj.IsEmpty()) {
return;
}
text_obj.SetFontName(L"SimSun");
text_obj.SetFontSize(12);
text_obj.SetReadDirection(OFDTextObject::e_ReadDirectionLeftToRight);
TextLayout layout = text_obj.GetTextLayout();
if (layout.IsEmpty()) {
return;
}
layout.SetText(L"多行文本示例");
layout.SetFontName(L"SimSun");
layout.SetFontSize(12);
// 可继续设置水平/垂直对齐、行间距、自动换行等(参见 TextLayout 接口文档)
layout.Update();
}
java
import com.foxit.sdk.ofd.graphics.OFDTextObject;
import com.foxit.sdk.ofd.graphics.TextLayout;
public class OFDTextLayoutExample {
public static void configureTextLayout(OFDTextObject textObj) throws Exception {
if (textObj.isEmpty()) {
return;
}
textObj.setFontName("SimSun");
textObj.setFontSize(12);
textObj.setReadDirection(OFDTextObject.e_ReadDirectionLeftToRight);
TextLayout layout = textObj.getTextLayout();
if (layout.isEmpty()) {
return;
}
layout.setText("多行文本示例");
layout.setFontName("SimSun");
layout.setFontSize(12);
layout.update();
}
}
多媒体与复合内容
视频图元通过 OFDLayer::AddVideoObject 或 AddGraphicsObject(e_GraphicsObjectTypeVideo) 创建,使用 SetVideoFromFile 指定视频源,并可配置标题、格式与边框样式。复合对象通过 AddCompositeObject 创建,使用 AddUnit 添加子单元,并通过 SetThumbnail、SetSubstitution 设置缩略图与替代内容(资源 ID 须先加入文档资源库,请参阅 元数据、资源与自定义数据)。
c++
#include "ofd/fs_ofdlayer.h"
#include "ofd/graphics/fs_ofdcompositeobject.h"
#include "ofd/graphics/fs_ofdvideoobject.h"
using namespace foxit::ofd;
using namespace foxit::ofd::graphics;
void AddCompositeWithThumbnail(OFDLayer& layer, uint32 thumb_res_id) {
OFDCompositeObject composite_obj = layer.AddCompositeObject();
if (composite_obj.IsEmpty()) {
return;
}
OFDCompositeUnit unit = composite_obj.AddUnit();
if (!unit.IsEmpty()) {
unit.SetWidth(100.0f);
unit.SetHeight(80.0f);
unit.SetThumbnail(thumb_res_id);
unit.SetSubstitution(thumb_res_id);
}
}
void ConfigureVideoObject(OFDLayer& layer, const char* video_path) {
OFDGraphicsObject base_obj = layer.AddGraphicsObject(
OFDLayer::e_GraphicsObjectTypeVideo);
OFDVideoObject video_obj(base_obj);
if (video_obj.IsEmpty()) {
return;
}
video_obj.SetType("mp4");
video_obj.SetFormat("video/mp4");
video_obj.SetTitle("demo");
video_obj.SetVideoFromFile(video_path);
// 可选:配置视频边框圆角与虚线
video_obj.CreateBorder();
video_obj.SetBorderLineWidth(1.0f);
video_obj.SetBorderCornerRadius(1.0f, 2.0f);
}
java
import com.foxit.sdk.ofd.OFDLayer;
import com.foxit.sdk.ofd.graphics.OFDCompositeObject;
import com.foxit.sdk.ofd.graphics.OFDCompositeUnit;
import com.foxit.sdk.ofd.graphics.OFDGraphicsObject;
import com.foxit.sdk.ofd.graphics.OFDVideoObject;
public class OFDMediaExample {
public static void addCompositeWithThumbnail(OFDLayer layer, int thumbResId)
throws Exception {
OFDCompositeObject compositeObj = layer.addCompositeObject();
if (compositeObj.isEmpty()) {
return;
}
OFDCompositeUnit unit = compositeObj.addUnit();
if (!unit.isEmpty()) {
unit.setWidth(100.0f);
unit.setHeight(80.0f);
unit.setThumbnail(thumbResId);
unit.setSubstitution(thumbResId);
}
}
public static void configureVideoObject(OFDLayer layer, String videoPath)
throws Exception {
OFDGraphicsObject baseObj = layer.addGraphicsObject(
OFDLayer.e_GraphicsObjectTypeVideo);
OFDVideoObject videoObj = new OFDVideoObject(baseObj);
if (videoObj.isEmpty()) {
return;
}
videoObj.setType("mp4");
videoObj.setFormat("video/mp4");
videoObj.setTitle("demo");
videoObj.setVideoFromFile(videoPath);
videoObj.createBorder();
videoObj.setBorderLineWidth(1.0f);
videoObj.setBorderCornerRadius(1.0f, 2.0f);
}
}
图元交互(动作)
任意图元可通过 OFDGraphicsObject::CreateActions / GetActions 绑定动作(跳转、URI 等),用法与页面级动作一致。完整示例请参阅 OFD 注释与动作 中「配置动作响应」一节。
c++
#include "ofd/fs_ofdaction.h"
#include "ofd/graphics/fs_ofdgraphicsobject.h"
using namespace foxit::ofd;
using namespace foxit::ofd::graphics;
void BindUriActionToGraphicsObject(OFDGraphicsObject& obj) {
if (obj.IsEmpty()) {
return;
}
Actions actions = obj.CreateActions();
if (actions.IsEmpty()) {
return;
}
URIAction uri_action(actions.AddAction(Action::e_ActionTypeUri));
if (!uri_action.IsEmpty()) {
uri_action.SetEventTrigger(Action::e_ActionEventTriggerTypeClick);
uri_action.SetURI("https://www.example.com");
}
}
注意事项
- 对象有效性:调用图元或图层方法前,使用
IsEmpty()检查句柄是否有效。 - 类型安全:将
OFDGraphicsObject转为OFDImageObject、OFDPathObject等子类前,须先通过GetType()确认类型匹配,否则构造派生类可能抛出异常。 - 继承关系:
OFDLayer继承自OFDGraphicsObject;OFDBlockObject继承自OFDLayer,可作为嵌套容器继续遍历子图元。 - 坐标系与变换:图元实际位置由
GetBoundary()与GetMatrix()共同影响,修改位置时需同时考虑矩阵变换。 - 资源依赖:图像、视频、复合单元缩略图等依赖
resource_id,须先通过OFDDoc::AddImageResourceFromFile等接口将资源加入文档,请参阅 元数据、资源与自定义数据。 - 持久化:对图层与图元的增删改均在内存中进行,须调用
OFDPackage::Save或SaveAs写回文件。 - 公文语义节点:
OfficeNode不属于图元章节,请参阅 自定义标引与公文节点。