Skip to content

PDF 表单属性

本章节详细介绍了如何使用 福昕 PDF SDK Web 版 实现 PDF 表单属性操作的技术方案,包括动态修改属性、变更监听机制,以及基于 form-designer 扩展的自定义属性组件开发。在阅读本章节之前,建议您先了解以下内容:

  1. 核心概念:
  2. UI 定制开发基础:

表单控件属性

PDF 表单控件属性主要分为两大类:视觉呈现属性(如旋转角度、颜色方案)和 行为控制属性(如可编辑状态、验证规则)。本节将深入解析 福昕 PDF SDK Web 版 提供的属性操作接口以及其实时监听机制。

获取表单控件

福昕 PDF SDK Web 版 提供了两种模式,用于获取表单控件实例。

主动检索模式

  1. 坐标定位 PDFForm.getWidgetAtPoint
    • 输入: 页面索引、坐标点、可选表单域类型过滤器
    • 输出: 匹配指定位置及类型的表单控件实例
  2. 对象标识检索 PDFPage.getAnnotsByObjectNumArray
    • 输入: 预定义的 objectNumber 数组
    • 输出: PDF 标注对象集合(需要进行二次类型筛选)
  3. 全量遍历 PDFPage.getAnnots
    • 输出: 当前页所有标注对象(需筛选 Annot_Type.widget 类型)
  4. 表单域关联检索 PDFFormField.getWidget
    • 前置条件: 需结合 getWidgetsCount 获取有效索引范围
    • 输出: 指定表单域的关联表单控件集合
javascript
// 坐标定位示例
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 标注移除事件: 当标注或表单控件被删除时触发,并且被删除的对象仅保留 getObjectNumbergetAnnotId 可用。

获取和设置表单控件属性

通过直接调用 Widget 接口,可以实现属性的修改和获取操作。以下是一个简单示例:

javascript
// 获取所有标注
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 事件,可实现对表单控件属性变更的监控。

javascript
pdfui.addPDFViewerEventListener(DataEvents.annotationUpdated, (annots, page, updateType) => {

})

事件回调的参数:

  • annots:发生属性变更的标注(或表单控件)对象数组,需要根据类型筛选表单控件;
  • page:发生属性变更的标注(或表单控件)所在的页面对象;
  • updateType:变更的类型,可以是 PDF.constant.AnnotUpdatedType 中的一个值。

通过 updateType 参数可以确定具体发生变更的属性。详细信息可参考 AnnotUpdatedType

表单域属性

获取表单域

获取表单域的实例可以通过以下方式:

示例:

javascript
// 通过表单域名称获取表单域
const field = await form.getField("Field name");
// 通过页面索引和点坐标获取表单域
const field = await form.getFieldAtPosition(0, {x: 100, y: 100});
// 通过表单控件获取表单域
const field = widget.getField();

获取和设置表单域属性

表单域属性的获取和设置可参考 PDFFormField API 文档。以下是示例代码:

javascript
const field = await form.getField("Field name");
// 获取表单域的值
const value = await field.getValue();
// 更新表单域的值
await field.setValue(value);

监听表单域属性变更事件

表单域属性变更事件是在表单域属性发生变化时触发的。可以通过监听 DataEvents.formFieldPropertyUpdated 事件来获取表单域属性变更。

javascript
pdfui.addPDFViewerEventListener(DataEvents.formFieldPropertyUpdated, (doc, fieldName, propertyName) => {

})

在表单域属性变更事件的回调中,可以获取以下三个参数:

  • doc:发生属性变更表单域所属的文档对象;
  • fieldName:发生属性变更的表单域名称;
  • propertyName:变更的属性名称,参考 PDF.form.FormFieldPropertyName

自定义属性编辑组件

form-designer Addon 为 "表单设计" 提供了强大的功能和组件。从 11.0.0 版本开始,该插件支持自定义属性编辑组件,进一步提升了表单设计的灵活性和可扩展性。

PDFFormProperty 用法

PDFFormPropertyform-designer Addon 专门为自定义属性编辑组件提供支持的工具类。其提供了以下功能:

  • 可以合并选中的多个表单域属性值。如果属性值不同且无法合并,则属性值会自动置为空,并将状态设置为不可用;
  • 可以根据选中的表单域类型,判断是否显示对应的属性编辑组件;
  • 可以根据选中的表单域类型和属性值,判断是否启用或禁用对应的属性编辑组件。

form-designer 已根据支持的表单属性预先构造了一系列 PDFFormProperty 实例,开发者可以通过 PDFFormPropertiesService 直接获取这些实例。

javascript

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 的状态:

  1. 用户只选中一个 PushButton 表单域,对于 fieldNameProperty, 其状态值如下:

    • hasValue: true
    • value: 'PushButton 0'
    • available: true
    • visible: true 此时,fieldName 属性编辑组件上显示的值为 'PushButton 0', 组件可见且可编辑。
  2. 用户选中了一个 PushButton 和一个 ListBox 表单域,对于 fieldNameProperty, 其状态值如下:

    • hasValue: false
    • value: undefined
    • available: false
    • visible: true 此时,fieldName 属性编辑组件上显示为空白,组件可见但不可编辑,因为无法同时设置两个表单域名字为相同。
  3. 用户选中了一个 PushButton, 对于 exportValueProperty, 其状态值如下:

    • hasValue: false
    • value: undefined
    • available: false
    • visible: false 此时,ExportValue 属性编辑组件不可见且不可用,因为 PushButton 不支持该属性。

PDFFormProperty.onChange 方法

该方法用于监听属性变更,当用户选中表单域或表单域的属性值发生变更时,会触发变更事件回调。以下以 React 组件代码作为示例演示:

  1. 首先,构造 hook 用于获取 PDFFormProperty 实例:

    jsx
    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;
    }
  2. 在 React 组件中使用:

    jsx
    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>
    }

注意: 以上示例仅用于演示用法,实际开发时需根据具体场景进行调整和实现。

内置表单属性编辑组件

内置表单属性编辑组件可以参考 内置表单属性编辑组件 文档。