Интерактивный runtime виджетов динамического XFA

TXFAWidgetRuntime предлагает не зависящую от хоста модель взаимодействия с динамическими XFA-формами — без превращения их в поля AcroForm и без уплощения содержимого

Runtime отдаёт детерминированные границы и состояние виджетов, так что настольное приложение, сервис или собственный renderer могут подставить свои обработку ввода, отрисовку, accessibility и event loop

Создание runtime

Runtime создаётся напрямую из байтов XDP либо вызовом THotPDF.CreateLoadedXFAWidgetRuntime после загрузки PDF, чей AcroForm содержит запись /XFA в виде одного stream или массива packet

var
  Runtime: TXFAWidgetRuntime;
  State: TXFAWidgetState;
begin
  Runtime := PDF.CreateLoadedXFAWidgetRuntime;
  try
    if (Runtime <> nil) and (Runtime.WidgetCount > 0) then
    begin
      State := Runtime.Widgets[0];
      Runtime.FocusWidget(State.ID);
      Runtime.BeginEdit(State.ID);
      Runtime.ReplaceSelection(0, Length(State.Value), 'Updated value');
      if not Runtime.CommitEdit then
        raise Exception.Create(Runtime.LastDiagnostic);
    end;
  finally
    Runtime.Free;
  end;
end;

Фабрика для загруженного документа работает с графом объектов PDF и существующими XFAFlattenWarnings только на чтение; при сохранении документа исходные записи /XFA и /NeedsRendering сохраняются

Модель взаимодействия

Интерактивные фиксации синхронизируют существующие нативные скалярные значения в скрытых и невидимых псевдонимах, включая реальные повторяющиеся области данных и побайтово точный откат

Атомарные валидация и вычисления

При commit сначала разрешаются явные SOM-привязки и текущий контекст данных повторяющихся строк, и только затем запускаются validate- и calculate-скрипты

Отредактированное значение, вычисленные значения, узлы данных, модель виджета, состояние focus и редактирования, предупреждения и счётчики проходов публикуются вместе — и только после того, как валидация и layout стабилизируются

Отклонённые скрипты, исчерпанные бюджеты, некорректные границы выделения UTF-16, а также исключения в layout или измерениях со стороны хоста полностью откатывают предыдущее состояние и записывают LastDiagnostic

Бюджеты

TXFAWidgetRuntimeOptions ограничивает число виджетов, длину отредактированного значения, количество проходов вычислений и reflow, операции layout, операции скриптов, затраченное скриптами время и прочие ресурсы FormCalc или JavaScript

Лимит виджетов действует при добавлении элементов layout и фрагментов пагинации, а распаковка загруженного XFA и сборка packet останавливаются на лимите входных данных XFA DOM ещё до парсинга

MaxLayoutOperations по умолчанию равен 200000 и ограничивает и обход документа, и работу layout; глубина layout ограничена 128, глубина цели события и инициализации экземпляров — 64, а рекурсивная диспетчеризация событий отклоняется

Литеральные динамические события по умолчанию

DispatchEvent принимает разделённые точками с запятой литеральные операции в совпадающих event-скриптах полей с типами содержимого FormCalc или JavaScript; эти операции разбираются напрямую и не требуют JavaScript DLL

row.instanceManager.addInstance(true);
row.instanceManager.removeInstance(0);
target.presence = "hidden";

addInstance добавляет один экземпляр и принимает true, false, 1 или 0 как аргумент merge; опущенный аргумент означает true, а сокращённая запись _row.addInstance(1) тоже принимается

Поддерживаемая модель привязывает каждую существующую одноимённую группу данных, поэтому новый экземпляр получает новую группу данных, инициализированную из дефолтов шаблона, при любом аргументе merge; создание экземпляра не клонирует введённые значения предыдущей строки

removeInstance использует целочисленный индекс с нуля среди одноимённых групп данных выбранного subform; у subform должен быть элемент occur с повторяемым максимумом, и обе операции соблюдают его минимум и максимум, включая max="-1"

Мутация экземпляров требует неявной именованной привязки dataset и ASCII-имён данных XML; явная привязка по data-reference и неоднозначные родительские контексты отклоняются до публикации транзакции

Цели разрешаются через именованных детей шаблона в объемлющих областях видимости события, включая пути через точку и this.parent; manager, вложенный в повторяющуюся область, использует собственную группу данных события

presence принимает visible, hidden и invisible на полях, draws, subform и exclusion group шаблона; скрытое содержимое не занимает места в потоке, а невидимое сохраняет своё место, но опускается из виджетов и уплощённого вывода

При литеральном парсере по умолчанию изменения presence применяются к выбранному узлу шаблона, а значит и ко всем его повторяющимся вхождениям; presence по индексу экземпляра, inactive, произвольные выражения, переменные, условные конструкции, циклы и прочие операции event-скриптов требуют дополнительного event-хоста

Пакет событий публикует изменения данных, вычисления, layout, focus и состояние редактирования вместе после стабилизации reflow; любое отклонённое operation или исчерпанный бюджет входа, операций, экземпляров, значений, виджетов, layout или времени полностью восстанавливают предыдущее состояние документа и взаимодействия

Удаление строки сохраняет focus и ожидающие правки для выживших групп данных, даже когда их индексы виджетов меняются; удаление или скрытие сфокусированного виджета сбрасывает focus и после успеха публикует соответствующий focus callback

Общее исполнение событий JavaScript и FormCalc

Установите TXFAWidgetRuntimeOptions.ScriptOptions.EnableJavaScript, чтобы включить поставляемый ограниченный QuickJS bridge для исполнения событий; события JavaScript тогда поддерживают функции, замыкания, массивы, условия, циклы и исключения вместо литеральной грамматики по умолчанию

Options := TXFAWidgetRuntimeOptions.Default;
Options.ScriptOptions.EnableJavaScript := True;
Runtime := TXFAWidgetRuntime.Create(XDPBytes, 595, 842, Options);

Event-хост открывает this, объемлющие именованные поля и subform-ы, parent, перезаписываемые rawValue, presence и access, instanceManager.count/min/max, addInstance, removeInstance, insertInstance, moveInstance и setInstances, xfa.resolveNode, xfa.resolveNodes, разрешение на уровне узлов и xfa.layout.relayout

Списки узлов поддерживают числовую индексацию, length и item; значения повторяющихся полей удерживают собственные цели dataset, а SOM-пути принимают именованных детей с числовыми или wildcard-индексами для живых host-узлов

const values = xfa.resolveNodes("main.row[*].amount[*]");
let total = 0;
for (const field of values) total += Number(field.rawValue);
xfa.resolveNode("main.total").rawValue = total;
if (total > 100) this.parent.warning.presence = "visible";

У каждого повторяющегося subform свой живой объект и дочерние поля; index, родительские getter-ы детей и последующие SOM-запросы внутри скрипта сразу следуют операциям insert, move и remove

addInstance и insertInstance возвращают живое поддерево, инициализированное из дефолтов шаблона, так что скрипт может записать значения его полей до публикации события; удалённые handle-ы отвергают последующие чтения и записи

Смены presence и access общего скрипта персистентны в стандартном packet-е form XFA на каждое вхождение, тогда как значения полей и repeat-операции персистентны в datasets; insert, move и remove держат соответствующее состояние form согласованным с группами данных

С тем же opt-in event-скрипты FormCalc компилируются в ограниченный движок и поддерживают var, if/elseif/else, for с upto или downto, while, foreach, func с неявными результатами, арифметику и сравнения, конкатенацию строк, агрегацию wildcard-узлов и неявное присваивание значений полей

Библиотека функций событий включает Sum, Count, Avg, Min, Max, Round, обычные математические функции, срезы строк, смену регистра, обрезку, замену, HasValue, Exists, Within, Oneof и Choose; встроенные имена нечувствительны к регистру, At(source, search) следует порядку аргументов XFA, включая поведение пустого поиска, финансовые функции следуют порядку параметров XFA, а каталоги дат и финансов описаны в FormCalc Functions

Мутации скриптов записываются внутри движка, валидируются и воспроизводятся внутри транзакции рантайма; исключения, прерывание, некорректный Unicode, невалидные host-операции и исчерпанные бюджеты не публикуют частичных изменений документа

Исполнение общего скрипта по умолчанию выключено, не использует браузер или внешний процесс и не открывает API файловой системы, сети или приложения; вместо bridge вызывающий может предоставить JavaScriptEvaluator

Персистентные объекты-скрипты

JavaScript script-объекты внутри элемента variables subform открывают свои переменные и функции через имя скрипта; лексические переменные, состояние объектов и вложенные замыкания живут на протяжении сессии рантайма, с обычным лексическим затенением и строгими директивами

<variables>
  <script name="Helpers" contentType="application/x-javascript"><![CDATA[
    let count = 0;
    const nextPrivate = (() => { let value = 0; return () => ++value; })();
    function next() { return ++count + ":" + nextPrivate(); }
  ]]></script>
</variables>

Код событий может вызывать Helpers.next(); повторяющиеся вхождения subform владеют независимыми объектами-скриптами, а функции разрешают именованные поля против текущего живого контекста form

Захваченные узлы полей и manager-ы следуют стабильной идентичности данных через перемещения и публикацию вновь созданных экземпляров; обращение к удалённому узлу отвергает событие и откатывает состояние его модуля

Общие calculate- и validate-скрипты разделяют сессию и объекты-скрипты; JavaScript поддерживает неявные значения завершения и явный return, а FormCalc возвращает своё финальное выражение

Неудачные транзакции восстанавливают лексическое состояние и замыкания воспроизведением закоммиченного виртуального журнала, с записанными временем и случайными входами; воспроизведение и текущее исполнение разделяют дедлайн транзакции, а дефолтные лимиты журнала — 64 МиБ скриптов/результатов и 8192 записи

Состояние объектов-скриптов живёт в сессии рантайма и инициализируется заново после перезагрузки XDP; стандартные значения документа и переопределения form продолжают персистентно сохраняться через SaveToBytes

Пользовательские callback-и JavaScriptEvaluator удерживают существующий нативный конверт calculate/validate; персистентную сессию предоставляет поставляемый движок

Настройка явного транспорта хоста

Задайте HostTransport и опциональный OnHostTransactionCompleted в TXFAWidgetRuntimeOptions, чтобы исполнять host-диалоги, печать, навигацию, отправку и передачу данных через callback-и приложения

Явный XFA Host Transport предоставляет xfa.host.messageBox, response, beep, print, gotoURL, submitForm, importData, exportData и FormCalc Get, Post и Put; приложение возвращает типизированные результаты и управляет внешними эффектами

Повторы и воспроизведение сессии используют записанные ответы, так что callback-и исполняются один раз на отправленный запрос; завершение со сбоем позволяет приложению отбросить staged-эффекты или компенсировать обратимые операции

Положительные HostTransportLimits ограничивают число запросов, число аргументов и совокупные байты запросов/ответов в пределах объемлющей транзакции рантайма

Исполнение локалей и картинок

Общие события FormCalc, calculate- и validate-скрипты поддерживают функции локалей и картинок, включая Format, Parse, локализованные преобразования даты/времени, единицы, кодирование, UUID и английские числительные

Динамический FormCalc Eval исполняет динамически поданные вычисления с изолированными переменными и функциями, относительным доступом к полям, нативной компиляцией, записанными ответами и транзакционным откатом

Явные ссылки FormCalc поддерживают присваивание со сквозной записью, перепривязку, отсоединение null, аргументы функций и стабильные handle-ы, удерживаемые объектами-скриптами через перемещения повторяющихся экземпляров

Живые SOM-пути поддерживают выведенные, абсолютные и относительные индексы вхождений, селекторы классов и потомков, прозрачные контейнеры, предикаты кандидатов и прямое индексированное присваивание FormCalc

Нативный Data DOM открывает datasets через $data и $record, немедленно синхронизирует чтения и записи привязанной form, зеркалит повторяющиеся экземпляры и сохраняет удерживаемые ссылки на данные через публикацию нативной идентичности

Нативный Property DOM открывает объявленные дочерние свойства значений, шрифтов, UI и прочих, синхронизирует типизированные значения полей и персистентно хранит атрибуты на экземпляр, используемые измерением шрифтов и поддерживаемым стилизованием flatten

Исполняемый узел наследует ближайший атрибут locale; записи document localeSet переопределяют системные символы и шаблоны, а вычисленные имена локалей разрешаются через ограниченные внутренние запросы и сохраняются в журнале сессии

Num2Date без картинки теперь использует объемлющий дефолтный шаблон даты; когда нужен вывод ISO, задавайте YYYY-MM-DD явно

Модель хоста с состоянием

XFA Host Model предоставляет метаданные приложения, реальное число страниц, навигацию по страницам, заголовок Unicode, флаги вычисления/валидации, scoped-сброс и focus полей с транзакционным жизненным циклом enter/exit

TXFAWidgetRuntimeOptions.HostModel настраивает исходное состояние приложения; TXFAWidgetRuntime.HostModel возвращает текущее состояние, а неудачные события восстанавливают его вместе со снимками документа и взаимодействия

Перемещение focus без enter/exit-скрипта, ожидающей правки или инициализированного жизненного цикла меняет focus без запуска вычислений скриптами и обработки дедлайнов; scripted focus и коммиты ожидающих правок по-прежнему используют транзакционные бюджеты, валидацию и пересчёт

Продвинутые picture clause исполняются через те же транзакции рантайма, журнал повторов локалей и жизненный цикл вычислений, с ограниченным составным разбором

Инициализация и сопровождение жизненного цикла

Вызовите InitializeForm после настройки рантайма и callback-ов, чтобы исполнить события initialize в порядке шаблона, затем calculate и validate, события form-ready и layout-ready; вызов идемпотентен после успеха

Новые экземпляры, созданные инициализацией или ready-событиями, получают собственные события initialize до своих ready-обработчиков; исполнение layout-ready повторяется только при изменении layout, в пределах бюджетов reflow и транзакций

После инициализации успешная диспетчеризация событий и коммит правок инициализируют вновь созданные экземпляры и исполняют calculate, validate и layout-ready; существующие вхождения удерживают состояние инициализации через перемещения и транзакционный откат

Сбои жизненного цикла откатывают байты документа, взаимодействие виджетов и bookkeeping инициализации вместе, а callback-и публикуются только после успеха объемлющей транзакции

Сохранение обновлённой формы

SaveToBytes возвращает полное обновлённое XDP рантайма, включая изменённые datasets и стандартное состояние form на экземпляр; изменения presence литерального парсера остаются атрибутами шаблона; используйте эти байты с SetXFADocument при записи PDF или с HPDFXFAFlatten для обновлённого уплощённого вывода

Фабрика для загруженного документа создаёт независимый рантайм, поэтому события рантайма не изменяют граф объектов исходного PDF автоматически

Опциональный профиль представления экземпляров

Динамическое представление экземпляров XFA добавляет явные повторяющиеся привязки, стабильные ID на экземпляр, индексированные presence и access, ограниченные условные события, рисование на canvas хоста и accessibility через независимый хелпер; рантайм по умолчанию и его ограниченная грамматика событий остаются совместимыми

UpdateDocument предоставляет транзакционные действия над документом; опциональные callback-и рантайма — свойства, области привязки, идентичности, старта разрешения и валидации документа — поддерживают хелпер, а nil-дефолты сохраняют существующих вызывающих

Низкоуровневые API событий и форм

HPDFXFACompileFormCalcEvent компилирует синтаксис событий; HPDFXFAExecuteJavaScriptEvent исполняет виртуальный контекст, описанный TXFAEventBinding и TXFAEventBindings

TXFAJavaScriptSession предоставляет персистентное исполнение и ограниченные контрольные точки для низкоуровневых вызывающих

Возвращаемые значения TXFAEventAction используют TXFAEventActionKind; транзакционное воспроизведение предоставляет рантайм

HPDFXFAResolveFormInstanceNode и HPDFXFAResolveFormInstanceProperty дают ограниченный стандартный поиск по form packet-у

Текущие границы

Runtime — однопоточный host-объект; GUI и рисование остаются ответственностью хоста или опционального хелпера представления

Поддерживаемое host-состояние, живой form DOM и жизненный цикл пока покрывают не каждое свойство хоста Acrobat, не картинки цифр альтернативных эпох и локализованные, не синхронные запросы динамического reflow и не каждый SOM-оператор

См. Ограниченные FormCalc и JavaScript для XFA-форм, XFA Packet DOM и Интерактивная обработка документов