互動式動態 XFA Widget 執行期

TXFAWidgetRuntime 為動態 XFA 表單提供與宿主無關的互動模型,不必把它們轉成 AcroForm 欄位,也不必攤平其內容

執行期暴露確定性的 widget 界限與狀態,桌面程式、服務或自訂轉譯器因此可以提供自己的輸入、繪製、無障礙與事件迴圈

建立執行期

直接從 XDP 位元組建立執行期,或在載入 AcroForm 帶有單串流或封包陣列 /XFA 項的 PDF 之後呼叫 THotPDF.CreateLoadedXFAWidgetRuntime

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 項

互動模型

互動式提交會把既有原生 scalar 值同步到 隱藏與不可見別名,包括實際的重複資料範圍與位元組精確的回復

原子驗證與計算

提交會先解析明確的 SOM 繫結與目前的重複列資料情境,然後執行 validate 與 calculate 腳本

編輯值、計算值、資料節點、widget 模型、焦點與編輯狀態、警告與通過計數器,只有在驗證與版面穩定之後才一起發布

被拒絕的腳本、耗盡的預算、無效的 UTF-16 選取邊界,以及版面或宿主量測例外,都會還原完整的先前狀態並設定 LastDiagnostic

預算

TXFAWidgetRuntimeOptions 限制 widget 數量、編輯值長度、計算輪數、重排輪數、版面操作、腳本操作、腳本耗時以及其他 FormCalc 或 JavaScript 資源

widget 上限在附加版面項目與分頁片段時生效,而已載入 XFA 的解壓縮與封包組裝則在解析之前就受 XFA DOM 輸入上限約束

MaxLayoutOperations 預設 200000,同時約束文件走訪與版面工作;版面深度上限 128,事件目標與執行個體初始化深度上限 64,遞迴的事件派發會被拒絕

受限的動態事件

DispatchEvent 接受以分號分隔的字面運算,放在內容類型為 FormCalc 或 JavaScript 的相符欄位事件腳本裡;這些運算會直接解析,不需要 JavaScript DLL

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

addInstance 附加一個執行個體,merge 參數接受 true、false、1 或 0;省略參數時視為 true,簡寫 _row.addInstance(1) 也接受

受支援的模型會綁定每個既有的同名資料群組,所以新執行個體會得到一個以範本預設值初始化的新資料群組,無論 merge 參數為何;建立執行個體不會複製前一列已輸入的值

removeInstance 在所選子表單的同名資料群組中使用零基整數索引;該子表單必須帶有可重複上限的 occur 元素,兩種操作都會強制其最小值與最大值,包括 max="-1"

執行個體變更需要隱含的具名 dataset 綁定與 ASCII 的 XML 資料名稱;明確的資料參照綁定與曖昧的父層上下文會在發布交易前被拒絕

目標透過事件外圍範圍中的具名範本子項解析,包括點路徑與 this.parent;巢狀在重複範圍內的管理器使用事件自己的資料群組

presence 接受 visible、hidden 與 invisible,適用於範本欄位、draw、子表單與排除群組;hidden 內容不佔流動空間,invisible 內容保留空間但不出現在 widget 與扁平化輸出中

使用預設字面解析器時,presence 變更會套用到所選範本節點,也因此套用到它全部的重複出現;逐執行個體的帶索引 presence、inactive、任意運算式、變數、條件、迴圈與其他事件腳本運算,則需要額外的事件宿主

事件批次會在重排穩定後,把資料變更、計算、版面、焦點與編輯狀態一起發布;任何被拒絕的操作或耗盡的輸入、操作、執行個體、值、widget、版面或耗時預算,都會還原完整的先前文件與互動狀態

移除一列時,存留資料群組的焦點與待處理編輯會被保留,即使它們的 widget 索引改變也一樣;移除或隱藏取得焦點的 widget 會清除焦點,並在成功後發布對應的焦點回呼

一般 JavaScript 與 FormCalc 事件執行

把 TXFAWidgetRuntimeOptions.ScriptOptions.EnableJavaScript 設為啟用,就能用內附的有界 QuickJS 橋接層執行事件;JavaScript 事件因此支援函式、closure、陣列、條件、迴圈與例外,而不只是預設的字面文法

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

事件宿主暴露 this、外圍的具名欄位與子表單、parent、可寫的 rawValue、presence 與 access、instanceManager.count/min/max、addInstance、removeInstance、insertInstance、moveInstance 與 setInstances、xfa.resolveNode、xfa.resolveNodes、節點層級解析,以及 xfa.layout.relayout

節點清單支援數值索引、length 與 item;重複欄位值保留自己的 dataset 目標,SOM 路徑則對活躍的宿主節點接受帶數值或萬用索引的具名子項

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";

每個重複子表單有自己的活躍物件與子欄位;index、父層子項 getter 與後續的 SOM 查詢,都會在腳本內立即跟隨 insert、move 與 remove 操作

addInstance 與 insertInstance 回傳以範本預設初始化的活躍子樹,腳本可以在事件發布前寫入其欄位值;已移除的 handle 會拒絕後續讀寫

一般腳本的 presence 與 access 變更按出現次數持久化在標準 XFA form 封包,欄位值與重複操作則持久化在 datasets;insert、move 與 remove 會讓對應的表單狀態與資料群組保持一致

同一個選用開關下,FormCalc 事件腳本會編譯到有界引擎,支援 var、if/elseif/else、帶 upto 或 downto 的 for、while、foreach、隱含結果的 func、算術與比較、字串串接、萬用節點聚合與隱含欄位值指派

事件函式庫包含 Sum、Count、Avg、Min、Max、Round、常用數學函式、字串切片、大小寫轉換、去空白、替換、HasValue、Exists、Within、Oneof 與 Choose;內建名稱不分大小寫,At(source, search) 遵循 XFA 參數順序(含空搜尋行為),財務函式遵循 XFA 參數順序,日期與財務目錄見 FormCalc Functions

腳本變更會記錄在引擎內,並在執行期交易中驗證與重放;例外、中斷、無效 Unicode、無效宿主操作與耗盡的預算都不會發布部分文件變更

一般腳本執行預設關閉,不使用瀏覽器或外部處理程序,也不暴露檔案系統、網路或應用程式 API;呼叫端可以改為提供 JavaScriptEvaluator 而不用橋接層

持久腳本物件

子表單 variables 元素裡的 JavaScript script 物件,會透過腳本名稱暴露其變數與函式;詞彙變數、物件狀態與巢狀 closure 在執行期工作階段中保持存活,並保留正常的詞彙遮蔽與 strict 指示詞

<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();重複子表單的各個出現擁有獨立的腳本物件,函式會對目前活躍的表單上下文解析具名欄位

擷取的欄位節點與管理器會跨移動與新執行個體的發布追蹤穩定的資料身分;存取已移除的節點會拒絕該事件並回復其模組狀態

一般的 calculate 與 validate 腳本共用工作階段與腳本物件;JavaScript 支援隱含完成值與明確 return,FormCalc 則回傳最後一個運算式

失敗的交易會重放已認可的虛擬日誌來還原詞彙與 closure 狀態,並使用記錄的時間與隨機輸入;重放與目前執行共用交易期限,預設日誌上限為 64 MiB 的腳本/結果與 8192 個條目

腳本物件狀態存在於執行期工作階段,XDP 重新載入後會重新初始化;標準文件值與表單覆寫仍透過 SaveToBytes 持久化

自訂 JavaScriptEvaluator callback 保留既有的原生 calculate/validate 信封;持久工作階段由內附引擎提供

設定明確的宿主傳輸

在 TXFAWidgetRuntimeOptions 設定 HostTransport 與選用的 OnHostTransactionCompleted,就能透過應用程式 callback 執行宿主對話框、列印、導覽、提交與資料傳輸

明確的 XFA 宿主傳輸 提供 xfa.host.messageBox、response、beep、print、gotoURL、submitForm、importData、exportData,以及 FormCalc 的 Get、Post 與 Put;應用程式回傳 typed 結果並掌控外部效果

重試與工作階段重放使用記錄的回應,callback 因此對每次派發的請求只執行一次;失敗完成讓應用程式可以捨棄暫存效果,或補償可逆操作

正數的 HostTransportLimits 在外圍執行期交易內約束請求數、引數數與請求/回應的彙總 bytes

Locale 與 picture 執行

一般 FormCalc 事件與 calculate、validate 腳本支援 locale 與 picture 函式,包括 Format、Parse、本地化日期時間轉換、單位、編碼、UUID 與英文數字詞

動態 FormCalc Eval 以隔離的變數與函式、相對欄位存取、原生編譯、記錄回應與交易式回復,執行動態提供的計算

明確的 FormCalc 參照 支援直寫指派、重新繫結、null 分離、函式引數,以及腳本物件跨重複執行個體移動保留的穩定 handle

活躍 SOM 路徑 支援推斷、絕對與相對的出現索引、class 與後代選擇器、透明容器、候選述詞與直接的 FormCalc 帶索引指派

原生 Data DOM 透過 $data 與 $record 暴露 datasets,立即同步已繫結的表單讀寫,鏡射重複執行個體,並透過原生身分發布保留資料參照

原生 Property DOM 暴露宣告的 value、font、UI 與其他 property 子項,同步 typed 欄位值,並持久化供字型量測與受支援扁平化樣式使用的逐執行個體屬性

執行節點繼承最近的 locale 屬性;文件 localeSet 項覆寫系統符號與模式,計算出的 locale 名稱則透過有界的內部請求解析,並保留在工作階段日誌中

不帶 picture 的 Num2Date 現在使用環境的預設日期模式;需要 ISO 輸出時請明確提供 YYYY-MM-DD

有狀態的宿主模型

XFA 宿主模型 提供應用程式中繼資料、實際頁數、頁面導覽、Unicode 標題、計算/驗證旗標、範圍限定的重設與欄位焦點,並帶交易式的 enter/exit 生命週期

TXFAWidgetRuntimeOptions.HostModel 設定初始應用程式狀態;TXFAWidgetRuntime.HostModel 回傳目前狀態,失敗的事件會把它連同文件與互動快照一起還原

沒有 enter/exit 腳本、待處理編輯或已初始化生命週期的焦點移動,只改變焦點,不啟動腳本計算或期限處理;帶腳本的焦點與待處理編輯認可仍使用交易式預算、驗證與重新計算

進階 picture 子句 透過相同的執行期交易、locale 重試日誌與計算生命週期執行,並對複合解析設有邊界

初始化與維護生命週期

設定好執行期與 callback 後呼叫 InitializeForm,以範本順序執行 initialize 事件,然後執行 calculate 與 validate、form-ready 事件與 layout-ready 事件;成功後此呼叫具有等冪性

初始化或 ready 事件建立的新執行個體,會先收到自己的 initialize 事件再執行 ready 處理器;layout-ready 只在版面變更時重複執行,並受重排與交易預算約束

初始化之後,成功的事件派發與編輯認可會初始化新建立的執行個體,並執行 calculate、validate 與 layout-ready;既有出現會跨越移動與交易回復保留其初始化狀態

生命週期失敗會把文件 bytes、widget 互動與初始化簿記一起回復,callback 只在外圍交易成功後發布

儲存更新後的表單

SaveToBytes 回傳執行期完整更新後的 XDP,包括修改過的 dataset 與標準的逐執行個體表單狀態;字面解析器的 presence 變更仍是範本屬性;寫 PDF 時把這些位元組交給 SetXFADocument,要更新後的扁平化輸出則交給 HPDFXFAFlatten

已載入文件的工廠建立的是獨立執行期,執行期事件不會自動改到來源 PDF 的物件圖

選用的 instance 呈現設定檔

動態 XFA Instance 呈現 透過獨立的輔助器加入明確的重複繫結、穩定的逐執行個體 ID、帶索引的 presence 與 access、有界的條件事件、宿主 canvas 繪製與無障礙支援;預設執行期與其受限的事件文法保持相容

UpdateDocument 提供交易式文件動作;選用的執行期 property、繫結範圍、身分、解析開始與文件驗證 callback 支援這個輔助器,nil 預設值則保留既有呼叫端

較底層的事件與表單 API

HPDFXFACompileFormCalcEvent 編譯事件語法;HPDFXFAExecuteJavaScriptEvent 執行 TXFAEventBinding 與 TXFAEventBindings 所描述的虛擬上下文

TXFAJavaScriptSession 為較底層的呼叫端提供持久執行與有界檢查點

回傳的 TXFAEventAction 值使用 TXFAEventActionKind;交易式重放由執行期提供

HPDFXFAResolveFormInstanceNode 與 HPDFXFAResolveFormInstanceProperty 提供有界的標準表單封包查找

目前的邊界

執行期是單執行緒的宿主物件;GUI 與繪製仍是宿主或選用呈現輔助器的責任

受支援的宿主狀態、活躍表單 DOM 與生命週期,尚未涵蓋每一個 Acrobat 宿主屬性、交替年代與本地化數字 picture、同步的動態重排查詢,或每一個 SOM 運算子

請參閱有界的 XFA FormCalc 與 JavaScript、XFA 封包 DOM 與互動式文件處理