Skip to content

OFD 图元与图层

本节介绍页面内容流中的图元对象(foxit::ofd::graphics::OFDGraphicsObject 及其子类型)与图层(OFDLayer)的遍历与编辑,用于版式分析、内容抽取或二次编辑。取得 OFDPage 的方式请参阅 OFD 文档与页面

任务场景

  • 遍历页面图层与图元树,提取文本、路径矢量或图像资源及坐标信息。
  • 在指定图层上动态添加路径、文本、图像等图元(如绘制边框、插入图片)。
  • 嵌入视频图元,或处理含缩略图/替代内容的复合对象(OFDCompositeObject)。
  • 通过 TextLayoutCharInfo 进行多行排版、对齐与字符级位置控制。
  • 为图元绑定跳转、URI 等交互动作,请参阅 OFD 注释与动作 中的动作章节。

API 概览

功能C++ API(foxit::ofd / graphics核心参数 / 描述
页面图层OFDPage::GetLayerCountGetLayer按索引获取页面图层;图层类型含背景 / 正文 / 前景(OFDLayer::e_LayerTypeBackground 等)
图层与图元容器OFDLayer继承自 OFDGraphicsObjectAddPathObjectAddTextObjectAddImageObject 等;GetGraphicsObjectCountGetGraphicsObjectRemoveGraphicsObject
图元基类graphics::OFDGraphicsObjectGetTypeGetIDGetBoundaryGetMatrixGetAlpha;颜色、线宽、虚线;CreateActions / GetActions
路径图元graphics::OFDPathObjectSetFill / SetStrokeSetFillModeOFDPathDataMoveToLineToCubicBezierTo 等)或 SetAbbreviatedData
文本图元graphics::OFDTextObject字体、字号、阅读/字符方向;GetTextLayoutSetCharInfosCharInfo
图像图元graphics::OFDImageObjectSetImage(resource_id) 或从文件/缓冲区加载;GetImageBufferGetImageFormat
视频图元graphics::OFDVideoObjectSetVideoFromFileSetTypeSetFormatSetTitle;视频边框(圆角、虚线等)
复合图元graphics::OFDCompositeObjectAddUnit 添加 OFDCompositeUnitSetThumbnailSetSubstitution
块对象graphics::OFDBlockObject继承自 OFDLayer,作为嵌套图元容器(e_ObjectTypePageBlock

遍历与访问页面图元

典型流程:从 OFDPage 获取图层 → 遍历图层内图元 → 根据 GetType 转为具体子类型。GetBoundaryGetMatrix 均基于 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::AddPathObjectAddTextObjectAddImageObject 等工厂方法创建图元,设置边界框、颜色、线宽等通用属性;不需要的图元可调用 RemoveGraphicsObjectRemoveAllGraphicsObjects 删除。修改后须 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 逐点构建(StartFigureMoveToLineToCubicBezierToArcToCloseFigure),也可通过 SetAbbreviatedData 设置 OFD 缩写路径字符串以减小文件体积。填充规则支持 e_PathFillNonZeroe_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 支持 SetReadDirectionSetCharDirection 等阅读方向设置。复杂排版可通过 GetTextLayout 取得 TextLayout,设置文本内容、字号、对齐与行距后调用 Update 生效;字符级精确定位可使用 CharInfoSetCharInfos

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::AddVideoObjectAddGraphicsObject(e_GraphicsObjectTypeVideo) 创建,使用 SetVideoFromFile 指定视频源,并可配置标题、格式与边框样式。复合对象通过 AddCompositeObject 创建,使用 AddUnit 添加子单元,并通过 SetThumbnailSetSubstitution 设置缩略图与替代内容(资源 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 转为 OFDImageObjectOFDPathObject 等子类前,须先通过 GetType() 确认类型匹配,否则构造派生类可能抛出异常。
  • 继承关系OFDLayer 继承自 OFDGraphicsObjectOFDBlockObject 继承自 OFDLayer,可作为嵌套容器继续遍历子图元。
  • 坐标系与变换:图元实际位置由 GetBoundary()GetMatrix() 共同影响,修改位置时需同时考虑矩阵变换。
  • 资源依赖:图像、视频、复合单元缩略图等依赖 resource_id,须先通过 OFDDoc::AddImageResourceFromFile 等接口将资源加入文档,请参阅 元数据、资源与自定义数据
  • 持久化:对图层与图元的增删改均在内存中进行,须调用 OFDPackage::SaveSaveAs 写回文件。
  • 公文语义节点OfficeNode 不属于图元章节,请参阅 自定义标引与公文节点