Skip to content

Редактирование полей форм

PDF Oxide обеспечивает полноценную работу с полями форм: чтение существующих значений, программное заполнение полей, добавление новых полей, настройка свойств, сведение форм в статическое содержимое и экспорт данных форм в форматах FDF/XFDF. XFA-формы можно проанализировать и преобразовать в AcroForm.

Чтение полей форм

Получить все поля формы

Rust

use pdf_oxide::editor::DocumentEditor;

let mut editor = DocumentEditor::open("form.pdf")?;
let fields = editor.get_form_fields()?;

for field in &fields {
    println!("Field: {} = {:?}", field.name(), field.value());
    if let Some(ft) = field.field_type() {
        println!("  Type: {:?}", ft);
    }
    if let Some(tooltip) = field.tooltip() {
        println!("  Tooltip: {}", tooltip);
    }
}

WASM

const doc = new WasmPdfDocument(bytes);
const fields = doc.getFormFields();

for (const f of fields) {
  console.log(`${f.name} (${f.fieldType}) = ${f.value}`);
}

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("form.pdf")
fields = doc.get_form_fields()

for field in fields:
    print(f"{field.name} ({field.field_type}) = {field.value}")

Go

doc, _ := pdfoxide.Open("form.pdf")
defer doc.Close()

fields, _ := doc.FormFields()
for _, f := range fields {
    fmt.Printf("%s (%s) = %s\n", f.Name, f.Type, f.Value)
}

C#

using var doc = PdfDocument.Open("form.pdf");
foreach (var f in doc.GetFormFields())
    Console.WriteLine($"{f.Name} ({f.Type}) = {f.Value}");

Java

try (PdfDocument doc = PdfDocument.open(java.nio.file.Path.of("form.pdf"))) {
    for (fyi.oxide.pdf.form.FormField f : doc.formFields()) {
        System.out.println(f.name() + " (" + f.type() + ") = " + f.value().orElse(""));
    }
}

Kotlin

PdfDocument.open(java.nio.file.Path.of("form.pdf")).use { doc ->
    for (f in doc.formFields()) {
        println("${f.name()} (${f.type()}) = ${f.value().orElse("")}")
    }
}

Scala

Using.resource(PdfDocument.open("form.pdf")) { doc =>
  doc.formFieldsSeq.foreach { f =>
    println(s"${f.name} (${f.`type`}) = ${f.valueOption.getOrElse("")}")
  }
}

Clojure

(with-open [doc (pdf/open "form.pdf")]
  (doseq [f (pdf/form-fields doc)]
    (println (.name f) "(" (.type f) ") =" (.orElse (.value f) ""))))

Ruby

PdfOxide::PdfDocument.open('form.pdf') do |doc|
  doc.form_fields.each do |f|
    puts "#{f[:name]} (#{f[:type]}) = #{f[:value]}"
  end
end

C++

auto doc = pdf_oxide::Document::open("form.pdf");
for (const auto& f : doc.get_form_fields())
    std::cout << f.name << " (" << f.type << ") = " << f.value << "\n";

Swift

let doc = try Document.open("form.pdf")
for f in try doc.formFields() {
    print("\(f.name) (\(f.type)) = \(f.value)")
}

Dart

final doc = PdfDocument.open('form.pdf');
for (final f in doc.getFormFields()) {
  print('${f.name} (${f.type}) = ${f.value}');
}

R

doc <- pdf_open("form.pdf")
for (f in pdf_get_form_fields(doc)) {
  cat(sprintf("%s (%s) = %s\n", f$name, f$type, f$value))
}

Julia

doc = open_document("form.pdf")
for f in get_form_fields(doc)
    println("$(f.name) ($(f.type)) = $(f.value)")
end

Zig

var doc = try pdf_oxide.Document.open("form.pdf");
var fields = try doc.formFields();
defer fields.deinit();
const n = try fields.count();
var i: i32 = 0;
while (i < n) : (i += 1) {
    const name = try fields.getName(a, i);
    defer a.free(name);
    const ftype = try fields.getType(a, i);
    defer a.free(ftype);
    const value = try fields.getValue(a, i);
    defer a.free(value);
    std.debug.print("{s} ({s}) = {s}\n", .{ name, ftype, value });
}

Objective-C

NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"form.pdf" error:&err];
for (POXFormField *f in [doc formFieldsWithError:&err]) {
    NSLog(@"%@ (%@) = %@", f.name, f.type, f.value);
}

Elixir

{:ok, doc} = PdfOxide.open("form.pdf")
{:ok, fields} = PdfOxide.form_fields(doc)
for f <- fields do
  IO.puts("#{f.name} (#{f.type}) = #{f.value}")
end

Получить значение конкретного поля

Rust

let mut editor = DocumentEditor::open("form.pdf")?;

let value = editor.get_form_field_value("first_name")?;
println!("First name: {:?}", value);

WASM

const value = doc.getFormFieldValue("first_name");
console.log(`First name: ${value}`);

Python

value = doc.get_form_field_value("first_name")
print(f"First name: {value}")

Проверить существование поля

let mut editor = DocumentEditor::open("form.pdf")?;

if editor.has_form_field("email")? {
    println!("Email field exists");
}

Установка значений полей форм

Задать значение поля

Rust

use pdf_oxide::editor::DocumentEditor;
use pdf_oxide::editor::form_fields::FormFieldValue;

let mut editor = DocumentEditor::open("form.pdf")?;

// Set a text field
editor.set_form_field_value("first_name", FormFieldValue::Text("Jane".to_string()))?;

// Set a checkbox
editor.set_form_field_value("agree_terms", FormFieldValue::Boolean(true))?;

// Set a choice field
editor.set_form_field_value("country", FormFieldValue::Choice("United States".to_string()))?;

editor.save("filled.pdf")?;

WASM

const doc = new WasmPdfDocument(bytes);

doc.setFormFieldValue("first_name", "Jane");
doc.setFormFieldValue("agree_terms", true);
doc.setFormFieldValue("country", "United States");

writeFileSync("filled.pdf", doc.save());
doc.free();

Python

doc = PdfDocument("form.pdf")

doc.set_form_field_value("first_name", "Jane")
doc.set_form_field_value("agree_terms", True)
doc.set_form_field_value("country", "United States")

doc.save("filled.pdf")

Go

editor, _ := pdfoxide.OpenEditor("form.pdf")
defer editor.Close()

_ = editor.SetFormFieldValue("first_name", "Jane")
_ = editor.SetFormFieldValue("agree_terms", "Yes")
_ = editor.SetFormFieldValue("country", "United States")

_ = editor.Save("filled.pdf")

C#

using var editor = DocumentEditor.Open("form.pdf");

editor.SetFormFieldValue("first_name", "Jane");
editor.SetFormFieldValue("agree_terms", "Yes");
editor.SetFormFieldValue("country", "United States");

editor.Save("filled.pdf");

Java

try (DocumentEditor editor = DocumentEditor.open("form.pdf")) {
    editor.setFormField("first_name", "Jane");
    editor.setFormField("agree_terms", true);
    editor.setFormField("country", "United States");
    editor.saveTo(java.nio.file.Path.of("filled.pdf"));
}

Kotlin

DocumentEditor.open("form.pdf").use { editor ->
    editor.setFormField("first_name", "Jane")
    editor.setFormField("agree_terms", true)
    editor.setFormField("country", "United States")
    editor.saveTo(java.nio.file.Path.of("filled.pdf"))
}

Scala

Using.resource(DocumentEditor.open("form.pdf")) { editor =>
  editor.setFormField("first_name", "Jane")
  editor.setFormField("agree_terms", true)
  editor.setFormField("country", "United States")
  editor.saveTo(java.nio.file.Path.of("filled.pdf"))
}

Clojure

(with-open [editor (pdf/editor "form.pdf")]
  (.setFormField editor "first_name" "Jane")
  (.setFormField editor "agree_terms" true)
  (.setFormField editor "country" "United States")
  (.saveTo editor (java.nio.file.Path/of "filled.pdf" (into-array String []))))

Ruby

PdfOxide::DocumentEditor.open('form.pdf') do |editor|
  editor.set_form_field('first_name', 'Jane')
  editor.set_form_field('agree_terms', true)
  editor.set_form_field('country', 'United States')
  editor.save_to('filled.pdf')
end

C++

auto editor = pdf_oxide::DocumentEditor::open("form.pdf");

editor.set_form_field_value("first_name", "Jane");
editor.set_form_field_value("agree_terms", "Yes");
editor.set_form_field_value("country", "United States");

editor.save("filled.pdf");

Swift

let editor = try DocumentEditor.open("form.pdf")

try editor.setFormFieldValue("first_name", "Jane")
try editor.setFormFieldValue("agree_terms", "Yes")
try editor.setFormFieldValue("country", "United States")

try editor.save("filled.pdf")

Dart

final editor = DocumentEditor.open('form.pdf');

editor.setFormFieldValue('first_name', 'Jane');
editor.setFormFieldValue('agree_terms', 'Yes');
editor.setFormFieldValue('country', 'United States');

editor.save('filled.pdf');

R

editor <- pdf_editor_open("form.pdf")

pdf_editor_set_form_field_value(editor, "first_name", "Jane")
pdf_editor_set_form_field_value(editor, "agree_terms", "Yes")
pdf_editor_set_form_field_value(editor, "country", "United States")

pdf_editor_save(editor, "filled.pdf")

Julia

editor = open_editor("form.pdf")

set_form_field_value(editor, "first_name", "Jane")
set_form_field_value(editor, "agree_terms", "Yes")
set_form_field_value(editor, "country", "United States")

save(editor, "filled.pdf")

Zig

var editor = try pdf_oxide.DocumentEditor.openEditor("form.pdf");
defer editor.deinit();

try editor.setFormFieldValue("first_name", "Jane");
try editor.setFormFieldValue("agree_terms", "Yes");
try editor.setFormFieldValue("country", "United States");

try editor.save("filled.pdf");

Objective-C

NSError *err = nil;
POXDocumentEditor *editor = [POXDocumentEditor openEditor:@"form.pdf" error:&err];

[editor setFormField:@"first_name" value:@"Jane" error:&err];
[editor setFormField:@"agree_terms" value:@"Yes" error:&err];
[editor setFormField:@"country" value:@"United States" error:&err];

[editor saveToPath:@"filled.pdf" error:&err];

Elixir

{:ok, editor} = PdfOxide.open_editor("form.pdf")

PdfOxide.set_form_field_value(editor, "first_name", "Jane")
PdfOxide.set_form_field_value(editor, "agree_terms", "Yes")
PdfOxide.set_form_field_value(editor, "country", "United States")

PdfOxide.editor_save(editor, "filled.pdf")

Варианты FormFieldValue

Вариант Описание Пример
Text(String) Значение текстового поля FormFieldValue::Text("Hello".into())
Boolean(bool) Состояние флажка/радиокнопки FormFieldValue::Boolean(true)
Choice(String) Единственный выбор FormFieldValue::Choice("Option A".into())
MultiChoice(Vec<String>) Множественный выбор FormFieldValue::MultiChoice(vec!["A".into(), "B".into()])
None Нет значения / очистить поле FormFieldValue::None

Добавление полей форм

Добавить новое поле формы

use pdf_oxide::editor::DocumentEditor;
use pdf_oxide::writer::form_fields::TextFieldWidget;

let mut editor = DocumentEditor::open("document.pdf")?;

// Create a text input field on page 0
let widget = TextFieldWidget::new("user_name")
    .with_rect(100.0, 700.0, 200.0, 20.0)
    .with_default_value("Enter name");

editor.add_form_field(widget, 0)?;
editor.save("with-form.pdf")?;

Добавить иерархические поля

Создайте родительско-дочерние отношения полей для структурированных форм.

use pdf_oxide::editor::form_fields::ParentFieldConfig;

let mut editor = DocumentEditor::open("document.pdf")?;

// Create a parent field
let parent = ParentFieldConfig::new("address");
editor.add_parent_field(parent)?;

// Add child fields under the parent
let street = TextFieldWidget::new("street")
    .with_rect(100.0, 600.0, 300.0, 20.0);
editor.add_child_field(street, 0, "address")?;

let city = TextFieldWidget::new("city")
    .with_rect(100.0, 570.0, 150.0, 20.0);
editor.add_child_field(city, 0, "address")?;

editor.save("hierarchical-form.pdf")?;

Удалить поле формы

let mut editor = DocumentEditor::open("form.pdf")?;
editor.remove_form_field("obsolete_field")?;
editor.save("cleaned.pdf")?;

Свойства полей форм

Настройка свойств

Задайте свойства отдельных полей по имени.

let mut editor = DocumentEditor::open("form.pdf")?;

// Access control
editor.set_form_field_readonly("signature_date", true)?;
editor.set_form_field_required("email", true)?;

// Tooltip
editor.set_form_field_tooltip("phone", "Enter phone number with area code")?;

// Position and size
editor.set_form_field_rect("name", pdf_oxide::geometry::Rect::new(100.0, 700.0, 200.0, 20.0))?;

// Text constraints
editor.set_form_field_max_length("zip_code", 10)?;
editor.set_form_field_alignment("amount", 2)?;  // 0=left, 1=center, 2=right

// Appearance
editor.set_form_field_background_color("highlight_field", [1.0, 1.0, 0.8])?;
editor.set_form_field_border_color("name", [0.0, 0.0, 0.0])?;
editor.set_form_field_border_width("name", 1.0)?;
editor.set_form_field_default_appearance("name", "/Helv 12 Tf 0 g")?;

// Raw flags
editor.set_form_field_flags("options", 0x100000)?;

editor.save("styled-form.pdf")?;

Свойства FormFieldWrapper

При работе с объектами FormFieldWrapper, возвращаемыми get_form_fields():

Метод Возвращает Описание
name() &str Полное имя поля
partial_name() &str Частичное имя (без префикса родителя)
value() FormFieldValue Текущее значение поля
set_value(value) () Задать значение поля
field_type() Option<&FieldType> Тип поля
page_index() usize Страница, содержащая поле
bounds() Option<Rect> Положение и размер поля
tooltip() Option<&str> Текст подсказки
is_modified() bool Было ли значение изменено
is_new() bool Добавлено ли поле (не из источника)
is_readonly() bool Флаг «только для чтения»
set_readonly(bool) () Установить флаг «только для чтения»
is_required() bool Флаг обязательности
set_required(bool) () Установить флаг обязательности
is_no_export() bool Флаг запрета экспорта
set_no_export(bool) () Установить флаг запрета экспорта
set_tooltip(text) () Задать текст подсказки
set_rect(rect) () Задать положение и размер
set_max_length(len) () Задать максимальную длину текста
get_max_length() Option<u32> Получить максимальную длину текста
set_alignment(align) () Задать выравнивание текста
get_alignment() Option<u32> Получить выравнивание текста
set_background_color(rgb) () Задать цвет фона
get_background_color() Option<[f32; 3]> Получить цвет фона
set_border_color(rgb) () Задать цвет рамки
get_border_color() Option<[f32; 3]> Получить цвет рамки
set_border_width(width) () Задать ширину рамки
get_border_width() Option<f32> Получить ширину рамки
set_default_appearance(da) () Задать строку внешнего вида по умолчанию
get_default_appearance() Option<&str> Получить строку внешнего вида по умолчанию
set_default_value(value) () Задать значение по умолчанию
get_default_value() Option<&FormFieldValue> Получить значение по умолчанию
has_parent() bool Проверить наличие родительского поля
parent_name() Option<&str> Имя родительского поля

Сведение форм

Сведение превращает интерактивные поля формы в статическое содержимое страницы. Значения полей становятся частью отрисовки страницы и больше не доступны для редактирования.

Свести форму на одной странице

Rust

let mut editor = DocumentEditor::open("form.pdf")?;
editor.flatten_forms_on_page(0)?;
editor.save("flat-page0.pdf")?;

WASM

const doc = new WasmPdfDocument(bytes);
doc.flattenFormsOnPage(0);
writeFileSync("flat-page0.pdf", doc.save());
doc.free();

C++

auto editor = pdf_oxide::DocumentEditor::open("form.pdf");
editor.flatten_forms_on_page(0);
editor.save("flat-page0.pdf");

Swift

let editor = try DocumentEditor.open("form.pdf")
try editor.flattenFormsOnPage(0)
try editor.save("flat-page0.pdf")

Dart

final editor = DocumentEditor.open('form.pdf');
editor.flattenFormsOnPage(0);
editor.save('flat-page0.pdf');

R

editor <- pdf_editor_open("form.pdf")
pdf_editor_flatten_forms_on_page(editor, 0)
pdf_editor_save(editor, "flat-page0.pdf")

Julia

editor = open_editor("form.pdf")
flatten_forms_on_page(editor, 0)
save(editor, "flat-page0.pdf")

Zig

var editor = try pdf_oxide.DocumentEditor.openEditor("form.pdf");
defer editor.deinit();
try editor.flattenFormsOnPage(0);
try editor.save("flat-page0.pdf");

Objective-C

NSError *err = nil;
POXDocumentEditor *editor = [POXDocumentEditor openEditor:@"form.pdf" error:&err];
[editor flattenFormsOnPage:0 error:&err];
[editor saveToPath:@"flat-page0.pdf" error:&err];

Elixir

{:ok, editor} = PdfOxide.open_editor("form.pdf")
PdfOxide.flatten_forms_on_page(editor, 0)
PdfOxide.editor_save(editor, "flat-page0.pdf")

Свести все формы

Rust

let mut editor = DocumentEditor::open("form.pdf")?;
editor.flatten_forms()?;
editor.save("flat.pdf")?;

WASM

const doc = new WasmPdfDocument(bytes);
doc.flattenForms();
writeFileSync("flat.pdf", doc.save());
doc.free();

Go

editor, _ := pdfoxide.OpenEditor("form.pdf")
defer editor.Close()

_ = editor.FlattenForms()
_ = editor.Save("flat.pdf")

C#

using var editor = DocumentEditor.Open("form.pdf");
editor.FlattenForms();
editor.Save("flat.pdf");

C++

auto editor = pdf_oxide::DocumentEditor::open("form.pdf");
editor.flatten_forms();
editor.save("flat.pdf");

Swift

let editor = try DocumentEditor.open("form.pdf")
try editor.flattenForms()
try editor.save("flat.pdf")

Dart

final editor = DocumentEditor.open('form.pdf');
editor.flattenForms();
editor.save('flat.pdf');

R

editor <- pdf_editor_open("form.pdf")
pdf_editor_flatten_forms(editor)
pdf_editor_save(editor, "flat.pdf")

Julia

editor = open_editor("form.pdf")
flatten_forms(editor)
save(editor, "flat.pdf")

Zig

var editor = try pdf_oxide.DocumentEditor.openEditor("form.pdf");
defer editor.deinit();
try editor.flattenForms();
try editor.save("flat.pdf");

Objective-C

NSError *err = nil;
POXDocumentEditor *editor = [POXDocumentEditor openEditor:@"form.pdf" error:&err];
[editor flattenForms:&err];
[editor saveToPath:@"flat.pdf" error:&err];

Elixir

{:ok, editor} = PdfOxide.open_editor("form.pdf")
PdfOxide.flatten_forms(editor)
PdfOxide.editor_save(editor, "flat.pdf")

Проверить статус сведения

editor.flatten_forms_on_page(0)?;
assert!(editor.is_page_marked_for_form_flatten(0));
assert!(editor.will_remove_acroform());

Экспорт данных формы

Экспортируйте значения полей формы в формат FDF или XFDF для дальнейшей обработки.

Экспорт в FDF

Rust

let mut editor = DocumentEditor::open("filled-form.pdf")?;
editor.export_form_data_fdf("form-data.fdf")?;

WASM

const doc = new WasmPdfDocument(bytes);
const fdfData = doc.exportFormData("fdf");
writeFileSync("form-data.fdf", fdfData);
doc.free();

Python

doc = PdfDocument("filled-form.pdf")
doc.export_form_data("form-data.fdf", format="fdf")

C++

auto doc = pdf_oxide::Document::open("filled-form.pdf");
auto fdf = doc.export_form_data_to_bytes(0);  // 0 = FDF
std::ofstream("form-data.fdf", std::ios::binary)
    .write(reinterpret_cast<const char*>(fdf.data()), fdf.size());

Swift

let doc = try Document.open("filled-form.pdf")
let fdf = try doc.exportFormData(formatType: 0)  // 0 = FDF
try Data(fdf).write(to: URL(fileURLWithPath: "form-data.fdf"))

Dart

final doc = PdfDocument.open('filled-form.pdf');
final fdf = doc.exportFormDataToBytes(0);  // 0 = FDF
File('form-data.fdf').writeAsBytesSync(fdf);

R

doc <- pdf_open("filled-form.pdf")
fdf <- pdf_export_form_data_to_bytes(doc, 0)  # 0 = FDF
writeBin(fdf, "form-data.fdf")

Julia

doc = open_document("filled-form.pdf")
fdf = export_form_data_to_bytes(doc, 0)  # 0 = FDF
write("form-data.fdf", fdf)

Zig

var doc = try pdf_oxide.Document.open("filled-form.pdf");
const fdf = try doc.exportFormDataToBytes(a, 0);  // 0 = FDF
defer a.free(fdf);
try std.fs.cwd().writeFile(.{ .sub_path = "form-data.fdf", .data = fdf });

Objective-C

NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"filled-form.pdf" error:&err];
NSData *fdf = [doc exportFormDataToBytes:0 error:&err];  // 0 = FDF
[fdf writeToFile:@"form-data.fdf" atomically:YES];

Elixir

{:ok, doc} = PdfOxide.open("filled-form.pdf")
{:ok, fdf} = PdfOxide.export_form_data_to_bytes(doc, 0)  # 0 = FDF
File.write!("form-data.fdf", fdf)

Экспорт в XFDF

Rust

let mut editor = DocumentEditor::open("filled-form.pdf")?;
editor.export_form_data_xfdf("form-data.xfdf")?;

WASM

const doc = new WasmPdfDocument(bytes);
const xfdfData = doc.exportFormData("xfdf");
writeFileSync("form-data.xfdf", xfdfData);
doc.free();

C++

auto doc = pdf_oxide::Document::open("filled-form.pdf");
auto xfdf = doc.export_form_data_to_bytes(1);  // 1 = XFDF
std::ofstream("form-data.xfdf", std::ios::binary)
    .write(reinterpret_cast<const char*>(xfdf.data()), xfdf.size());

Swift

let doc = try Document.open("filled-form.pdf")
let xfdf = try doc.exportFormData(formatType: 1)  // 1 = XFDF
try Data(xfdf).write(to: URL(fileURLWithPath: "form-data.xfdf"))

Dart

final doc = PdfDocument.open('filled-form.pdf');
final xfdf = doc.exportFormDataToBytes(1);  // 1 = XFDF
File('form-data.xfdf').writeAsBytesSync(xfdf);

R

doc <- pdf_open("filled-form.pdf")
xfdf <- pdf_export_form_data_to_bytes(doc, 1)  # 1 = XFDF
writeBin(xfdf, "form-data.xfdf")

Julia

doc = open_document("filled-form.pdf")
xfdf = export_form_data_to_bytes(doc, 1)  # 1 = XFDF
write("form-data.xfdf", xfdf)

Zig

var doc = try pdf_oxide.Document.open("filled-form.pdf");
const xfdf = try doc.exportFormDataToBytes(a, 1);  // 1 = XFDF
defer a.free(xfdf);
try std.fs.cwd().writeFile(.{ .sub_path = "form-data.xfdf", .data = xfdf });

Objective-C

NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"filled-form.pdf" error:&err];
NSData *xfdf = [doc exportFormDataToBytes:1 error:&err];  // 1 = XFDF
[xfdf writeToFile:@"form-data.xfdf" atomically:YES];

Elixir

{:ok, doc} = PdfOxide.open("filled-form.pdf")
{:ok, xfdf} = PdfOxide.export_form_data_to_bytes(doc, 1)  # 1 = XFDF
File.write!("form-data.xfdf", xfdf)

Импорт данных формы

Операция, обратная экспорту: загрузка значений полей из файла FDF или XFDF (либо из «сырых» байт FDF/XFDF) обратно в документ. Это позволяет взять данные, заполненные в стороннем PDF-ридере, другой библиотеке или серверном пайплайне, и применить их к полям AcroForm.

Как импортировать данные FDF или XFDF в PDF?

В C ABI объявлено специализированное семейство функций импорта, а обёртка Swift раскрывает все четыре точки входа:

Символ C ABI Метод Swift Источник данных
pdf_form_import_from_file importFormFromFile(_:) Путь к файлу FDF/XFDF
pdf_document_import_form_data importFormData(_:) Путь к файлу FDF/XFDF
pdf_editor_import_fdf_bytes importFdfBytes(_:) Байты FDF в памяти
pdf_editor_import_xfdf_bytes importXfdfBytes(_:) Байты XFDF в памяти

Точные сигнатуры C ABI (из include/pdf_oxide_c/pdf_oxide.h):

bool    pdf_form_import_from_file(const void *document, const char *filename, int32_t *error_code);
int32_t pdf_document_import_form_data(const void *document, const char *data_path, int32_t *error_code);
int32_t pdf_editor_import_fdf_bytes(const void *document, const uint8_t *data, uintptr_t data_len, int32_t *error_code);
int32_t pdf_editor_import_xfdf_bytes(const void *document, const uint8_t *data, uintptr_t data_len, int32_t *error_code);

Доступность в v0.3.69. Все четыре точки входа объявлены в C ABI и раскрыты обёрткой Swift, однако базовая реализация ещё не подключена: в v0.3.69 каждая из них возвращает статус Unsupported (код ошибки 8) во время выполнения. Ни Python, ни Node.js, ни Go, ни WASM-привязки их вообще не предоставляют, а Rust-уровневого читателя FDF/XFDF тоже не существует (модуль pdf_oxide::fdf содержит лишь экспортные стороны — FdfWriter/XfdfWriter). До появления нативного пути импорта используйте обходное решение «разобрать и заполнить», описанное ниже, — оно работает во всех привязках уже сегодня.

Swift (интерфейс присутствует; до реализации бэкенда бросает Unsupported)

import PdfOxide

let editor = try DocumentEditor.open(path: "form.pdf")

// From a file on disk (FDF or XFDF):
try editor.importFormFromFile("submission.fdf")   // -> Bool
try editor.importFormData("submission.xfdf")      // -> Int32 status

// From in-memory bytes:
let fdf = Array("%FDF-1.2\n...".utf8)
try editor.importFdfBytes(fdf)                     // -> Int32 status

let xfdf = Array("<?xml version=\"1.0\"?><xfdf>...</xfdf>".utf8)
try editor.importXfdfBytes(xfdf)                   // -> Int32 status

try editor.save(to: "filled.pdf")

Каждый метод возвращает код статуса FFI (или Bool для importFormFromFile) и бросает PdfOxideError при ненулевом коде ошибки, поэтому сейчас следует ожидать ошибку Unsupported, а не изменения полей.

Как применить данные FDF/XFDF сейчас (межязыковое обходное решение)?

Поскольку set_form_field_value доступен во всех привязках, переносимый способ импорта данных формы в v0.3.69 — самостоятельно разобрать FDF/XFDF и записать каждую пару имя -> значение в документ. Оба формата — небольшие, хорошо специфицированные текстовые форматы: FDF оборачивает значения в синтаксис словарей PDF, XFDF — это обычный XML.

Python – импорт XFDF-файла

# `defusedxml` guards against XXE and billion-laughs attacks when parsing
# untrusted XFDF (the stdlib xml parsers are unsafe by default):
#   pip install defusedxml
import defusedxml.ElementTree as ET
from pdf_oxide import PdfDocument

# XFDF: <xfdf><fields><field name="..."><value>...</value></field></fields></xfdf>
tree = ET.parse("submission.xfdf")
ns = {"x": "http://ns.adobe.com/xfdf/"}

doc = PdfDocument("form.pdf")
for field in tree.findall(".//x:field", ns) or tree.findall(".//field"):
    name = field.get("name")
    value_el = field.find("x:value", ns)
    if value_el is None:
        value_el = field.find("value")
    if name and value_el is not None:
        doc.set_form_field_value(name, value_el.text or "")

doc.save("filled.pdf")

Rust – импорт XFDF-файла через DocumentEditor

use pdf_oxide::editor::DocumentEditor;
use pdf_oxide::editor::form_fields::FormFieldValue;

fn import_xfdf(pdf: &str, xfdf: &str, out: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut editor = DocumentEditor::open(pdf)?;
    let xml = std::fs::read_to_string(xfdf)?;

    // Minimal extraction of <field name="..."><value>...</value></field> pairs.
    for chunk in xml.split("<field ").skip(1) {
        let name = chunk
            .split("name=\"").nth(1)
            .and_then(|s| s.split('"').next());
        let value = chunk
            .split("<value>").nth(1)
            .and_then(|s| s.split("</value>").next());
        if let (Some(name), Some(value)) = (name, value) {
            editor.set_form_field_value(name, FormFieldValue::Text(value.to_string()))?;
        }
    }

    editor.save(out)?;
    Ok(())
}

Node.js – импорт XFDF-файла

const fs = require("fs");
const { PdfDocument } = require("pdf-oxide");

const xfdf = fs.readFileSync("submission.xfdf", "utf8");
const doc = new PdfDocument("form.pdf");

const re = /<field\s+name="([^"]+)"[^>]*>\s*<value>([\s\S]*?)<\/value>/g;
let m;
while ((m = re.exec(xfdf)) !== null) {
  doc.setFormFieldValue(m[1], m[2]);
}

fs.writeFileSync("filled.pdf", doc.save());
doc.close();

Тот же подход работает и для FDF: извлеките каждую запись /T (name) /V (value) из массива /Fields и вызовите set_form_field_value. Как только нативный путь импорта будет реализован, ручной разбор можно убрать и вызывать importFdfBytes / importXfdfBytes / importFormFromFile напрямую.

Поддержка XFA-форм

PDF Oxide умеет обнаруживать, анализировать и конвертировать XFA-формы в стандартный AcroForm.

Проверить наличие XFA

Rust

let mut editor = DocumentEditor::open("xfa-form.pdf")?;

if editor.has_xfa()? {
    println!("Document contains XFA form data");
}

WASM

const doc = new WasmPdfDocument(bytes);
if (doc.hasXfa()) {
  console.log("Document contains XFA form data");
}

Python

doc = PdfDocument("xfa-form.pdf")
if doc.has_xfa():
    print("Document contains XFA form data")

C++

auto doc = pdf_oxide::Document::open("xfa-form.pdf");
if (doc.has_xfa())
    std::cout << "Document contains XFA form data\n";

Swift

let doc = try Document.open("xfa-form.pdf")
if try doc.hasXfa() {
    print("Document contains XFA form data")
}

Dart

final doc = PdfDocument.open('xfa-form.pdf');
if (doc.hasXfa()) {
  print('Document contains XFA form data');
}

R

doc <- pdf_open("xfa-form.pdf")
if (pdf_has_xfa(doc)) {
  cat("Document contains XFA form data\n")
}

Julia

doc = open_document("xfa-form.pdf")
if has_xfa(doc)
    println("Document contains XFA form data")
end

Zig

var doc = try pdf_oxide.Document.open("xfa-form.pdf");
if (doc.hasXfa()) {
    std.debug.print("Document contains XFA form data\n", .{});
}

Objective-C

NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"xfa-form.pdf" error:&err];
if ([doc hasXfa]) {
    NSLog(@"Document contains XFA form data");
}

Elixir

{:ok, doc} = PdfOxide.open("xfa-form.pdf")
if PdfOxide.has_xfa?(doc) do
  IO.puts("Document contains XFA form data")
end

Анализ структуры XFA

let mut editor = DocumentEditor::open("xfa-form.pdf")?;

let analysis = editor.analyze_xfa()?;
println!("XFA analysis: {:?}", analysis);

Преобразовать XFA в AcroForm

Конвертируйте XFA-формы в стандартные поля AcroForm для более широкой совместимости.

let mut editor = DocumentEditor::open("xfa-form.pdf")?;
editor.convert_xfa_to_acroform(&Default::default())?;
editor.save("acroform.pdf")?;

Полный справочник API

Операции с полями

Метод Возвращает Описание
get_form_fields() Result<Vec<FormFieldWrapper>> Список всех полей формы
get_form_field_value(name) Result<FormFieldValue> Получить значение поля
has_form_field(name) Result<bool> Проверить существование поля
set_form_field_value(name, value) Result<()> Задать значение поля
add_form_field(widget, page) Result<()> Добавить новое поле
add_parent_field(config) Result<()> Добавить родительское поле
add_child_field(widget, page, parent) Result<()> Добавить дочернее поле
remove_form_field(name) Result<()> Удалить поле

Свойства полей (по имени)

Метод Возвращает Описание
set_form_field_readonly(name, bool) Result<()> Задать «только для чтения»
set_form_field_required(name, bool) Result<()> Задать обязательность
set_form_field_tooltip(name, text) Result<()> Задать подсказку
set_form_field_rect(name, rect) Result<()> Задать положение и размер
set_form_field_max_length(name, len) Result<()> Задать максимальную длину текста
set_form_field_alignment(name, align) Result<()> Задать выравнивание текста
set_form_field_background_color(name, rgb) Result<()> Задать цвет фона
set_form_field_border_color(name, rgb) Result<()> Задать цвет рамки
set_form_field_border_width(name, width) Result<()> Задать ширину рамки
set_form_field_default_appearance(name, da) Result<()> Задать внешний вид по умолчанию
set_form_field_flags(name, flags) Result<()> Задать «сырые» флаги поля

Сведение

Метод Возвращает Описание
flatten_forms_on_page(page) Result<()> Свести формы на одной странице
flatten_forms() Result<()> Свести все формы
is_page_marked_for_form_flatten(page) bool Проверить статус сведения
will_remove_acroform() bool Проверить, будет ли удалён AcroForm

Экспорт

Метод Возвращает Описание
export_form_data_fdf(path) Result<()> Экспортировать в файл FDF
export_form_data_xfdf(path) Result<()> Экспортировать в файл XFDF

Импорт (C ABI + Swift; в v0.3.69 возвращает Unsupported во время выполнения)

Символ C ABI Метод Swift Возвращает Описание
pdf_form_import_from_file importFormFromFile(_:) bool / Bool Импорт FDF/XFDF из пути к файлу
pdf_document_import_form_data importFormData(_:) int32_t / Int32 Импорт данных формы из пути к файлу
pdf_editor_import_fdf_bytes importFdfBytes(_:) int32_t / Int32 Импорт FDF из байт в памяти
pdf_editor_import_xfdf_bytes importXfdfBytes(_:) int32_t / Int32 Импорт XFDF из байт в памяти

До появления нативного бэкенда импорта возвращают код ошибки 8 (Unsupported); применяйте set_form_field_value для работы с разобранными данными FDF/XFDF уже сейчас.

XFA

Метод Возвращает Описание
has_xfa() Result<bool> Проверить наличие данных XFA-формы
analyze_xfa() Result<XfaAnalysis> Проанализировать структуру XFA
convert_xfa_to_acroform(options) Result<()> Преобразовать XFA в AcroForm

Расширенный пример: пакетное заполнение и сведение

use pdf_oxide::editor::DocumentEditor;
use pdf_oxide::editor::form_fields::FormFieldValue;

let mut editor = DocumentEditor::open("template.pdf")?;

// Fill form fields
editor.set_form_field_value("name", FormFieldValue::Text("John Doe".to_string()))?;
editor.set_form_field_value("date", FormFieldValue::Text("2025-12-01".to_string()))?;
editor.set_form_field_value("approved", FormFieldValue::Boolean(true))?;

// Make the completed form non-editable
editor.flatten_forms()?;

// Export data before saving
editor.export_form_data_xfdf("submission.xfdf")?;

editor.save("completed.pdf")?;

Часто задаваемые вопросы

Поддерживает ли PDF Oxide импорт данных FDF/XFDF обратно в PDF в v0.3.69? Нативного одноразового импорта пока нет. В C ABI объявлены pdf_form_import_from_file, pdf_document_import_form_data, pdf_editor_import_fdf_bytes и pdf_editor_import_xfdf_bytes, Swift-обёртка их раскрывает, но во время выполнения все они возвращают статус Unsupported (код ошибки 8). Разберите FDF/XFDF и вызывайте set_form_field_value для каждого поля — это работает во всех привязках уже сегодня.

Какие привязки раскрывают методы импорта? Только C ABI и Swift-обёртка предоставляют интерфейс импорта. Привязки Python, Node.js, Go и WASM его не раскрывают. Экспорт (export_form_data_fdf / export_form_data_xfdf и export_form_data) полностью реализован во всех привязках.

В чём разница между FDF и XFDF? FDF — производный от PDF бинарный/текстовый контейнер (заголовок %FDF-1.2, массив /Fields). XFDF — его XML-эквивалент (<xfdf><fields>...), который удобнее генерировать и разбирать стандартными инструментами. Оба формата несут одни и те же данные имя поля -> значение; PDF Oxide умеет экспортировать оба.

Достаточно ли быстра библиотека для пакетных задач? Да. Ядро извлечения PDF Oxide показывает среднее время 0,8 мс со 100%-ным процентом успеха на контрольном корпусе, поэтому чтение полей, установка значений и повторное сохранение занимают значительно меньше миллисекунды на уровне полей для типичных форм.

Связанные страницы