use crate::ast::document::{Document, Page};
use crate::ast::value::dim_to_px;
use crate::diagnostics::Diagnostic;
use super::nodes::{node_bbox, node_id_and_span, node_role};
struct LiveArea {
x: f64,
y: f64,
w: f64,
h: f64,
}
#[derive(Clone, Copy)]
pub(super) struct PageMarginCtx {
pub(super) page_w: f64,
pub(super) page_h: f64,
pub(super) is_recto: bool,
pub(super) mirror_margins: bool,
pub(super) rtl: bool,
}
fn live_area(doc: &Document, page: &Page, ctx: PageMarginCtx) -> Option<LiveArea> {
let PageMarginCtx {
page_w,
page_h,
is_recto,
mirror_margins,
rtl,
} = ctx;
let (inner_opt, outer_opt, top_opt, bottom_opt) = doc.effective_margins(page);
let inner_dim = inner_opt.as_ref()?;
let outer_dim = outer_opt.as_ref()?;
let top_dim = top_opt.as_ref()?;
let bottom_dim = bottom_opt.as_ref()?;
let inner = dim_to_px(inner_dim.value, &inner_dim.unit)?;
let outer = dim_to_px(outer_dim.value, &outer_dim.unit)?;
let top = dim_to_px(top_dim.value, &top_dim.unit)?;
let bottom = dim_to_px(bottom_dim.value, &bottom_dim.unit)?;
let inner_on_right = if rtl { is_recto } else { !is_recto };
let left_inset = if mirror_margins && inner_on_right {
outer
} else {
inner
};
Some(LiveArea {
x: left_inset,
y: top,
w: page_w - inner - outer,
h: page_h - top - bottom,
})
}
pub(super) fn check_margins(
doc: &Document,
page: &Page,
ctx: PageMarginCtx,
diagnostics: &mut Vec<Diagnostic>,
) {
let (page_w, page_h, is_recto) = (ctx.page_w, ctx.page_h, ctx.is_recto);
let Some(area) = live_area(doc, page, ctx) else {
return;
};
const EPSILON: f64 = 0.5;
let parity = if is_recto { "recto" } else { "verso" };
for node in &page.children {
if node_role(node) == Some("guide") {
continue;
}
let Some((nx, ny, nw, nh)) = node_bbox(node, page_w, page_h) else {
continue;
};
let mut edges: Vec<&str> = Vec::new();
if nx < area.x - EPSILON {
edges.push("left");
}
if ny < area.y - EPSILON {
edges.push("top");
}
if nx + nw > area.x + area.w + EPSILON {
edges.push("right");
}
if ny + nh > area.y + area.h + EPSILON {
edges.push("bottom");
}
if edges.is_empty() {
continue;
}
let (node_id, node_span) = node_id_and_span(node);
diagnostics.push(Diagnostic::advisory(
"margin.violation",
format!(
"node '{}' falls outside the {} page live area \
(x {:.0}, y {:.0}, w {:.0}, h {:.0}); crosses the {} margin edge(s)",
node_id,
parity,
area.x,
area.y,
area.w,
area.h,
edges.join(", ")
),
node_span,
Some(node_id.to_owned()),
));
}
}