use zenith_core::{Diagnostic, Dimension, Document, Page, PropertyValue, Unit};
use super::super::record_affected;
pub(in crate::engine) fn parse_dimension_str(s: &str) -> Option<Dimension> {
let rest = s.strip_prefix('(')?;
let (unit_str, value_str) = rest.split_once(')')?;
let unit = Unit::from_annotation(unit_str);
let value: f64 = value_str.trim().parse().ok()?;
if !value.is_finite() {
return None;
}
Some(Dimension { value, unit })
}
pub(in crate::engine) struct AddPageSpec<'a> {
pub id: &'a str,
pub w: &'a str,
pub h: &'a str,
pub background: Option<&'a str>,
pub index: Option<usize>,
}
pub(in crate::engine) fn apply_add_page(
spec: &AddPageSpec<'_>,
doc: &mut Document,
diagnostics: &mut Vec<Diagnostic>,
affected: &mut Vec<String>,
) {
let AddPageSpec {
id,
w,
h,
background,
index,
} = *spec;
if doc.body.pages.iter().any(|p| p.id == id) {
diagnostics.push(Diagnostic::error(
"tx.duplicate_id",
format!("add_page: a page with id {:?} already exists", id),
None,
Some(id.to_owned()),
));
return;
}
let Some(width) = parse_dimension_str(w) else {
diagnostics.push(Diagnostic::error(
"tx.invalid_value",
format!(
"add_page: width {:?} is not a valid dimension (expected e.g. \"(px)1800\")",
w
),
None,
Some(id.to_owned()),
));
return;
};
let Some(height) = parse_dimension_str(h) else {
diagnostics.push(Diagnostic::error(
"tx.invalid_value",
format!(
"add_page: height {:?} is not a valid dimension (expected e.g. \"(px)1200\")",
h
),
None,
Some(id.to_owned()),
));
return;
};
let len = doc.body.pages.len();
let at = match index {
None => len,
Some(i) => {
if i > len {
diagnostics.push(Diagnostic::error(
"tx.out_of_range",
format!(
"add_page: index {} is out of range (document has {} page(s))",
i, len
),
None,
Some(id.to_owned()),
));
return;
}
i
}
};
let page = Page {
id: id.to_owned(),
name: None,
width,
height,
background: background.map(|b| PropertyValue::TokenRef(b.to_owned())),
bleed: None,
margin_inner: None,
margin_outer: None,
margin_top: None,
margin_bottom: None,
baseline_grid: None,
line_jumps: None,
parity: None,
master: None,
safe_zones: Vec::new(),
folds: Vec::new(),
block_styles: Vec::new(),
children: Vec::new(),
source_span: None,
};
doc.body.pages.insert(at, page);
record_affected(id, affected);
}
pub(in crate::engine) fn apply_delete_page(
page_id: &str,
doc: &mut Document,
diagnostics: &mut Vec<Diagnostic>,
affected: &mut Vec<String>,
) {
let Some(pos) = doc.body.pages.iter().position(|p| p.id == page_id) else {
diagnostics.push(Diagnostic::error(
"tx.unknown_node",
format!("delete_page: page {:?} not found", page_id),
None,
Some(page_id.to_owned()),
));
return;
};
doc.body.pages.remove(pos);
record_affected(page_id, affected);
}
pub(in crate::engine) fn apply_set_page_size(
page_id: &str,
w: &str,
h: &str,
doc: &mut Document,
diagnostics: &mut Vec<Diagnostic>,
affected: &mut Vec<String>,
) {
let w_dim = match parse_dimension_str(w) {
Some(d) if d.value > 0.0 => d,
Some(_) => {
diagnostics.push(Diagnostic::error(
"tx.invalid_value",
format!("set_page_size: w {:?} must be a finite value > 0", w),
None,
Some(page_id.to_owned()),
));
return;
}
None => {
diagnostics.push(Diagnostic::error(
"tx.invalid_value",
format!(
"set_page_size: w {:?} is not a valid dimension (expected e.g. \"(px)794\")",
w
),
None,
Some(page_id.to_owned()),
));
return;
}
};
let h_dim = match parse_dimension_str(h) {
Some(d) if d.value > 0.0 => d,
Some(_) => {
diagnostics.push(Diagnostic::error(
"tx.invalid_value",
format!("set_page_size: h {:?} must be a finite value > 0", h),
None,
Some(page_id.to_owned()),
));
return;
}
None => {
diagnostics.push(Diagnostic::error(
"tx.invalid_value",
format!(
"set_page_size: h {:?} is not a valid dimension (expected e.g. \"(px)1123\")",
h
),
None,
Some(page_id.to_owned()),
));
return;
}
};
let Some(page) = doc.body.pages.iter_mut().find(|p| p.id == page_id) else {
diagnostics.push(Diagnostic::error(
"tx.unknown_node",
format!("set_page_size: page {:?} not found", page_id),
None,
Some(page_id.to_owned()),
));
return;
};
page.width = w_dim;
page.height = h_dim;
record_affected(page_id, affected);
}
pub(in crate::engine) fn apply_reorder_pages(
order: &[String],
doc: &mut Document,
diagnostics: &mut Vec<Diagnostic>,
affected: &mut Vec<String>,
) {
let current: Vec<String> = doc.body.pages.iter().map(|p| p.id.clone()).collect();
let mut order_sorted: Vec<&String> = order.iter().collect();
order_sorted.sort();
let dup = order_sorted.windows(2).any(|w| w[0] == w[1]);
let mut current_sorted: Vec<&String> = current.iter().collect();
current_sorted.sort();
if dup || order_sorted != current_sorted {
diagnostics.push(Diagnostic::error(
"tx.invalid_value",
format!(
"reorder_pages: order {:?} is not a permutation of the existing \
page ids {:?}",
order, current
),
None,
None,
));
return;
}
let mut slots: Vec<Option<Page>> = doc.body.pages.drain(..).map(Some).collect();
let mut reordered: Vec<Page> = Vec::with_capacity(order.len());
for id in order {
if let Some(slot) = slots
.iter_mut()
.find(|s| s.as_ref().map(|p| p.id.as_str()) == Some(id.as_str()))
&& let Some(page) = slot.take()
{
reordered.push(page);
}
}
doc.body.pages = reordered;
for id in order {
record_affected(id, affected);
}
}