# PDF 表单属性

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

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

# 表单控件属性

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

# 获取表单控件

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

# 主动检索模式

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

# 获取和设置表单控件属性

通过直接调用 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)

# 表单域属性

# 获取表单域

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

示例:

// 通过表单域名称获取表单域
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) => {
    
})

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

# 自定义属性编辑组件

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

  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 实例:

    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 组件中使用:

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

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

# 内置表单属性编辑组件

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

# 自定义表单属性对话框

自定义表单属性对话框可参考 自定义表单属性对话框 文档。