# PDF 表单属性
本章节详细介绍了如何使用 Foxit PDF SDK for Web 实现 PDF 表单属性操作的技术方案,包括动态修改属性、变更监听机制,以及基于 form-designer 扩展的自定义属性组件开发。在阅读本章节之前,建议您先了解以下内容:
- 核心概念:
- UI 定制开发基础:
# 表单控件属性
PDF 表单控件属性主要分为两大类:视觉呈现属性(如旋转角度、颜色方案)和 行为控制属性(如可编辑状态、验证规则)。本节将深入解析 Foxit PDF SDK for Web 提供的属性操作接口以及其实时监听机制。
# 获取表单控件
Foxit PDF SDK for Web 提供了两种模式,用于获取表单控件实例。
# 主动检索模式
- 坐标定位【PDFForm.getWidgetAtPoint (opens new window)】:
- 输入: 页面索引、坐标点、可选表单域类型过滤器
- 输出: 匹配指定位置及类型的表单控件实例
- 对象标识检索【PDFPage.getAnnotsByObjectNumArray (opens new window)】:
- 输入: 预定义的 objectNumber 数组
- 输出: PDF 标注对象集合(需要进行二次类型筛选)
- 全量遍历 【PDFPage.getAnnots (opens new window)】:
- 输出: 当前页所有标注对象(需筛选 Annot_Type.widget 类型)
- 表单域关联检索 【PDFFormField.getWidget (opens new window)】:
- 前置条件: 需结合 getWidgetsCount (opens new window) 获取有效索引范围
- 输出: 指定表单域的关联表单控件集合
// 坐标定位示例
const widget = await form.getWidgetAtPoint(0, { x: 100, y: 100 });
const pushButtonWidget = await form.getWidgetAtPoint(0, { x: 100, y: 100 }, PDF.form.FieldType.PushButton);
// 对象标识检索示例
const [widget] = await page.getAnnotsByObjNumArray([12345]);
// 遍历页面中的所有注释,根据类型筛选表单控件
const annots = await page.getAnnots();
const filteredWidgets = annots.filter(it => {
return it.getType() === PDF.annots.constant.Annot_Type.widget;
});
// 表单域关联检索示例
const field = await form.getField("Field name");
const widgetCount = await field.getWidgetsCount();
const widgets = await Promise.all(
Array.from({ length: widgetCount }, (_, i) => {
return field.getWidget(i);
})
);
# 事件驱动模式
通过注册数据事件监听器,实现动态获取:
DataEvents.annotationAdded
标注创建事件: 当添加标注或表单控件时触发,需要根据类型筛选表单控件。DataEvents.annotationUpdated
标注属性更新事件: 当标注或表单控件的属性发生变更时触发,同样需要根据类型进行筛选。DataEvents.annotationRemoved
标注移除事件: 当标注或表单控件被删除时触发,并且被删除的对象仅保留getObjectNumber
和getAnnotId
可用。
# 获取和设置表单控件属性
通过直接调用 Widget (opens new window) 接口,可以实现属性的修改和获取操作。以下是一个简单示例:
// 获取所有标注
const annots = await page.getAnnots();
// 筛选表单控件
const widgets = annots.filter(it => {
return it.getType() === PDF.annots.constant.Annot_Type.widget;
});
// 设置所有表单控件旋转90°
await Promise.all(
widgets.map(widget => {
return widget.setRotation(PDF.form.FormWidgetRotation.ROTATION_90)
})
);
// 获取第一个表单控件的旋转角度
const rotation = await widgets[0].getRotation();
# 监听表单控件属性变更事件
通过监听 DataEvents.annotationUpdated
事件,可实现对表单控件属性变更的监控。
pdfui.addPDFViewerEventListener(DataEvents.annotationUpdated, (annots, page, updateType) => {
})
事件回调的参数:
annots
:发生属性变更的标注(或表单控件)对象数组,需要根据类型筛选表单控件;page
:发生属性变更的标注(或表单控件)所在的页面对象;updateType
:变更的类型,可以是PDF.constant.AnnotUpdatedType
(opens new window) 中的一个值。
通过 updateType
参数可以确定具体发生变更的属性。详细信息可参考 AnnotUpdatedType (opens new window)。
# 表单域属性
# 获取表单域
获取表单域的实例可以通过以下方式:
- PDFForm.getField (opens new window) 方法:
- 根据表单域名称获取表单域对象。
- PDFForm.getFieldAtPosition (opens new window) 方法:
- 根据页面索引和点坐标获取表单域对象。
- Widget.getField (opens new window) 方法:
- 通过表单控件获取表单域。
示例:
// 通过表单域名称获取表单域
const field = await form.getField("Field name");
// 通过页面索引和点坐标获取表单域
const field = await form.getFieldAtPosition(0, { x: 100, y: 100 });
// 通过表单控件获取表单域
const field = widget.getField();
# 获取和设置表单域属性
表单域属性的获取和设置可参考 PDFFormField (opens new window) API 文档。以下是示例代码:
const field = await form.getField("Field name");
// 获取表单域的值
const value = await field.getValue();
// 更新表单域的值
await field.setValue(value);
# 监听表单域属性变更事件
表单域属性变更事件是在表单域属性发生变化时触发的。可以通过监听 DataEvents.formFieldPropertyUpdated
事件来获取表单域属性变更。
pdfui.addPDFViewerEventListener(DataEvents.formFieldPropertyUpdated, (doc, fieldName, propertyName) => {
})
在表单域属性变更事件的回调中,可以获取以下三个参数:
doc
:发生属性变更表单域所属的文档对象;fieldName
:发生属性变更的表单域名称;propertyName
:变更的属性名称,参考PDF.form.FormFieldPropertyName
(opens new window)。
# 自定义属性编辑组件
form-designer Addon 为 "表单设计" 提供了强大的功能和组件。从 11.0.0 版本开始,该插件支持自定义属性编辑组件,进一步提升了表单设计的灵活性和可扩展性。
# PDFFormProperty 用法
PDFFormProperty (opens new window) 是 form-designer Addon 专门为自定义属性编辑组件提供支持的工具类。其提供了以下功能:
- 可以合并选中的多个表单域属性值。如果属性值不同且无法合并,则属性值会自动置为空,并将状态设置为不可用;
- 可以根据选中的表单域类型,判断是否显示对应的属性编辑组件;
- 可以根据选中的表单域类型和属性值,判断是否启用或禁用对应的属性编辑组件。
form-designer 已根据支持的表单属性预先构造了一系列 PDFFormProperty 实例,开发者可以通过 PDFFormPropertiesService (opens new window) 直接获取这些实例。
const formDesigner =await pdfui.getAddonInstance('FormDesigner')
const propertiesService = formDesigner.getPDFFormPropertiesService();
const fieldNameProperty = propertiesService.getFieldName();
fieldNameProperty.onChange(() => {
const {
// 表示属性是否有值。当用户未选中表单域,或选中多个表单域但属性值无法合并时,为 false;其他情况为 true。
hasValue,
// 属性值
value,
// 表示属性是否可用。当选中的表单域不支持该属性,或不支持同时编辑选中的多个表单域的属性时,为 false;其他情况为 true。
available,
// 表示属性编辑组件是否可见。当选中的表单域不支持该属性,或不支持同时编辑选中的多个表单域的属性时,为 false;其他情况为 true。
visible
} = fieldNameProperty;
})
const exportValueProperty = propertiesService.getExportValueProperty()
下面是几个典型的场景,用于解释 PDFFormProperty 的状态:
用户只选中一个 PushButton 表单域,对于
fieldNameProperty
, 其状态值如下:hasValue
: truevalue
: 'PushButton 0'available
: truevisible
: true 此时,fieldName 属性编辑组件上显示的值为 'PushButton 0', 组件可见且可编辑。
用户选中了一个 PushButton 和一个 ListBox 表单域,对于
fieldNameProperty
, 其状态值如下:hasValue
: falsevalue
: undefinedavailable
: falsevisible
: true 此时,fieldName 属性编辑组件上显示为空白,组件可见但不可编辑,因为无法同时设置两个表单域名字为相同。
用户选中了一个 PushButton, 对于
exportValueProperty
, 其状态值如下:hasValue
: falsevalue
: undefinedavailable
: falsevisible
: false 此时,ExportValue
属性编辑组件不可见且不可用,因为 PushButton 不支持该属性。
PDFFormProperty.onChange
方法:
该方法用于监听属性变更,当用户选中表单域或表单域的属性值发生变更时,会触发变更事件回调。以下以 React 组件代码作为示例演示:
首先,构造 hook 用于获取 PDFFormProperty 实例:
function useFormDesignerAddonInstance() { const context = useContext(PDFUIContext); // PDFUIContext,用于共享 PDFUI 对象 const pdfui = context.current; const [instance, setInstance] = useState(); if (pdfui && instance) { pdfui.getAddonInstance('FormDesigner').then(instance => { setInstance(instance); }); } return instance; } function useFormPropertiesService() { const formDesigner = useFormDesignerAddonInstance(); return formDesigner?.getPDFFormPropertiesService(); } function usePDFFormProperty(pdfFormPropertyFactory) { const formPropertiesService = useFormPropertiesService(); const [property, setProperty] = useState(); useEffect(() => { if(property) { return; } if(!formPropertiesService) { return; } const property = pdfFormPropertyFactory(formPropertiesService); setProperty(property); }, [property, formPropertiesService]) useEffect(() => { if(!property) { return; } return property.onChange(() => { setProperty(property); }); }, [property]) return property; }
在 React 组件中使用:
function FieldNameEditor() { const formPropertiesService = useFormPropertiesService(); const fieldNameProperty = usePDFFormProperty(pdfFormPropertyService => { return pdfFormPropertyService.getFieldName(); }) const [value, setValue] = useState() // 由于 fieldNameProperty.value 是一个只读属性,直接在 <input> 上使用会导致用户无法编辑。因此,需要构造一个新的 state useEffect(() => { if(!fieldNameProperty?.hasValue) { setValue(''); } else { setValue(fieldNameProperty.value) } }, [fieldNameProperty?.hasValue, fieldNameProperty?.value]) return <input readonly={!fieldNameProperty?.available} className={fieldNameProperty?.visible ? '' : 'hide'} value={value} onChange={(event) => { const newFieldName = event.target.value; setValue(newFieldName); const fields = formPropertiesService.getSelectedFields(); // 当用户修改内容后,需要将更新的数据设置到表单域中 fields.forEach(field => { field.setName(newFieldName); }); }} ></input> }
注意: 以上示例仅用于演示用法,实际开发时需根据具体场景进行调整和实现。
# 内置表单属性编辑组件
内置表单属性编辑组件可以参考 内置表单属性编辑组件 文档。
# 自定义表单属性对话框
自定义表单属性对话框可参考 自定义表单属性对话框 文档。