Skip to main content

xfa_layout_engine/trace/
sites.rs

1//! Named emit helpers for the five M1.6 hot phases.
2//!
3//! Each helper builds a fully-populated [`super::TraceEvent`] for a
4//! specific phase and forwards it to [`super::emit`]. Callers who want
5//! finer control can construct events directly with
6//! [`super::TraceEvent::new`] and the builders.
7//!
8//! ## Why this module exists
9//!
10//! Engine code that wants to emit a trace event should call one of these
11//! helpers rather than re-typing the `Phase`/`Reason` literal tags. That
12//! keeps the call sites short, makes refactors localised, and gives
13//! reviewers a single place to inspect the shape of trace events for
14//! each phase.
15//!
16//! All helpers are zero-cost when no sink is installed (one thread-local
17//! read + an `Option::is_some` branch).
18//!
19//! ## Wiring status (M1 v1)
20//!
21//! These helpers are public API and exercised by the integration test
22//! in `tests/trace_sites.rs`. Production wiring of these calls into the
23//! XFA layout/data-binding code paths is staged behind a follow-up and
24//! is intentionally not part of M1.6 because:
25//!
26//! - The five hot phases live across `xfa-layout-engine::layout`,
27//!   `xfa-layout-engine::form`, and `pdf-xfa::dynamic`. Each call site
28//!   needs case-specific input/decision text that v1 does not yet
29//!   curate.
30//! - Wiring during M1 risks subtle regressions in layout/page-count
31//!   gates, which would make the determinism gate noisy precisely as
32//!   we are trying to land it.
33//!
34//! Therefore M1.6 ships:
35//! - The taxonomy (M1.5).
36//! - These five typed emit helpers (this module) — exercised end-to-end.
37//! - Documentation in `M1_RESULT.md` marking the production wiring as
38//!   PARTIAL with a clear hand-off pointer.
39
40use super::{emit, Phase, Reason, TraceEvent};
41
42/// Emit a `bind` phase event.
43///
44/// `som` is the SOM path of the template subform being bound.
45/// `decision` is a short summary like `"3 instances created"`.
46pub fn bind(som: &str, reason: Reason, decision: impl Into<String>) {
47    emit(
48        TraceEvent::new(Phase::Bind, reason)
49            .with_som(som.to_string())
50            .with_decision(decision)
51            .with_source("xfa_layout_engine::form::bind"),
52    );
53}
54
55/// Emit an `occur` phase event.
56///
57/// `count` is the number of instances after applying `occur.min/max`.
58pub fn occur(som: &str, reason: Reason, count: i64) {
59    emit(
60        TraceEvent::new(Phase::Occur, reason)
61            .with_som(som.to_string())
62            .with_input(format!("count={count}"))
63            .with_source("xfa_layout_engine::form::occur"),
64    );
65}
66
67/// Emit a `presence` phase event.
68pub fn presence(som: &str, reason: Reason, decision: impl Into<String>) {
69    emit(
70        TraceEvent::new(Phase::Presence, reason)
71            .with_som(som.to_string())
72            .with_decision(decision)
73            .with_source("xfa_layout_engine::form::presence"),
74    );
75}
76
77/// Emit a `paginate` phase event.
78///
79/// `available_h` is the available content-area height in points;
80/// `needed_h` is the height the container would need.
81pub fn paginate(som: &str, reason: Reason, available_h: f64, needed_h: f64) {
82    emit(
83        TraceEvent::new(Phase::Paginate, reason)
84            .with_som(som.to_string())
85            .with_input(format!(
86                "available_h={available_h:.4} needed_h={needed_h:.4}"
87            ))
88            .with_source("xfa_layout_engine::layout::paginate"),
89    );
90}
91
92/// Emit a `suppress` phase event.
93///
94/// `page_index` is the zero-based page index being considered for suppression.
95pub fn suppress(reason: Reason, page_index: u32, decision: impl Into<String>) {
96    emit(
97        TraceEvent::new(Phase::Suppress, reason)
98            .with_input(format!("page_index={page_index}"))
99            .with_decision(decision)
100            .with_source("xfa_layout_engine::layout::suppress"),
101    );
102}
103
104/// Emit a `fallback` phase event.
105///
106/// `decision` should be a short reason summary (e.g. the underlying
107/// `XfaError` rendered with `{:?}`, or `"timeout"`). Used by
108/// `pdf-xfa::flatten` to signal that the XFA pipeline could not
109/// produce output and the caller has been served a non-XFA
110/// passthrough.
111pub fn fallback(reason: Reason, decision: impl Into<String>) {
112    emit(
113        TraceEvent::new(Phase::Fallback, reason)
114            .with_decision(decision)
115            .with_source("pdf_xfa::flatten::fallback"),
116    );
117}