Files
pdfcraft/crates/automation/src/layers.rs
T
mtlcl410andClaude Opus 5.5 92990b2e2c M3: layer_set shows or hides a layer, and page_render draws with the document's view settings (#2986)
The Layers panel had no automation tool: doc_info listed the layers, but
nothing could show or hide one, so layer visibility could not be checked
headlessly. layer_set does what the panel's eye icon does, naming a layer
by name (every group with that name, as CAD exporters repeat names) or by
its 1-based position in doc_info's list.

page_render built its own RenderConfig with only the password and the
reading colours, so it ignored layer visibility and comments_hide as
well: hiding comments through the tool changed nothing it drew. The
automation renderer now takes the document's own RenderConfig
(Session::render_config), and both tools drop the cached renderer when
they change a setting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-17 21:09:57 +08:00

48 lines
2.3 KiB
Rust

//! Layers (optional content groups) as a tool: the Layers panel's eye icon (#2986, AGENTS.md §3).
//!
//! A per-document view setting, like hidden comments: `page_render` draws with it, and the file
//! itself never changes.
use serde_json::{Value, json};
use crate::{Args, Automation, Result, ToolError};
impl Automation {
/// Show or hide a layer, named by `layer`: its name as `doc_info` lists it, or its 1-based
/// position in that list. A name picks every layer that has it, since CAD exporters often
/// write one group per page or per drawing under the same name.
pub(crate) fn layer_set(&mut self, a: &Args) -> Result<Value> {
let doc = self.doc(a)?;
let id = doc.id;
let visible = a.opt_bool("visible")?.ok_or_else(|| ToolError::InvalidArgs("missing argument visible".into()))?;
let layers = &doc.info.layers;
let targets: Vec<usize> = match a.get("layer") {
Some(Value::String(name)) => layers.iter().enumerate().filter(|(_, l)| l.name == *name).map(|(i, _)| i).collect(),
Some(v) => match v.as_u64() {
Some(n) if n >= 1 && usize::try_from(n - 1).is_ok_and(|i| i < layers.len()) => vec![(n - 1) as usize],
Some(_) => Vec::new(),
None => return Err(ToolError::InvalidArgs("layer must be a layer name or its 1-based position in doc_info's layers".into())),
},
None => return Err(ToolError::InvalidArgs("missing argument layer".into())),
};
if targets.is_empty() {
let names: Vec<&str> = layers.iter().map(|l| l.name.as_str()).collect();
return Err(ToolError::InvalidArgs(if names.is_empty() {
"the document has no layers".into()
} else {
format!("no such layer; the document's layers are: {}", names.join(", "))
}));
}
let mut changed = false;
for i in targets {
changed |= self.session.set_layer_visible(id, i, visible);
}
if changed {
// The cached renderer drew with the old visibility; page_render must read it again.
self.renderers.remove(&id);
}
let layers = &self.doc(a)?.info.layers;
Ok(json!({ "layers": layers.iter().map(|l| json!({ "name": l.name, "visible": l.visible })).collect::<Vec<_>>() }))
}
}