mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-28 03:58:45 -06:00
refactor(engine): move layer visibility to layers.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4972a4e1eb
commit
8473403a5d
@@ -0,0 +1,60 @@
|
||||
//! Layer (optional content) visibility: what the view shows and what printing writes.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use pdfcraft_render::{Layer, LayerOp, RenderPool};
|
||||
|
||||
use crate::{Document, render_threads};
|
||||
|
||||
/// Write `layers`' visibility into the default optional-content configuration of `cos` (a copy
|
||||
/// made for printing): `/BaseState /ON`, `/ON` the visible layers, `/OFF` the hidden ones. Its
|
||||
/// other entries (`/Order`, `/RBGroups`, `/AS`…) are kept. A document without `/OCProperties`
|
||||
/// is left alone.
|
||||
pub(crate) fn set_default_layer_state(cos: &mut pdfcraft_cos::Document, layers: &[Layer]) {
|
||||
use pdfcraft_cos::{Dict, ObjRef, Object};
|
||||
let Some(root_ref) = cos.trailer().get(b"Root").and_then(Object::as_ref) else { return };
|
||||
let Some(mut root) = cos.get(root_ref).as_dict().cloned() else { return };
|
||||
let Some(properties) = root.get(b"OCProperties").cloned() else { return };
|
||||
let Some(mut properties) = cos.resolve(&properties).as_dict().cloned() else { return };
|
||||
let mut config = properties.get(b"D").and_then(|d| cos.resolve(d).as_dict().cloned()).unwrap_or_else(Dict::new);
|
||||
let refs =
|
||||
|visible: bool| Object::Array(layers.iter().filter(|l| l.visible == visible).map(|l| Object::Ref(ObjRef::new(l.id.0, l.id.1))).collect());
|
||||
config.set(b"BaseState".to_vec(), Object::name("ON"));
|
||||
config.set(b"ON".to_vec(), refs(true));
|
||||
config.set(b"OFF".to_vec(), refs(false));
|
||||
properties.set(b"D".to_vec(), Object::Dict(config));
|
||||
root.set(b"OCProperties".to_vec(), Object::Dict(properties));
|
||||
cos.set(root_ref, Object::Dict(root));
|
||||
}
|
||||
|
||||
/// Render `doc` with the layer visibility in its `info.layers`.
|
||||
pub(crate) fn use_layer_choices(doc: &mut Document) {
|
||||
let overrides: Vec<(i32, i32, bool)> = doc.info.layers.iter().map(|l| (l.id.0 as i32, l.id.1 as i32, l.visible)).collect();
|
||||
doc.config.layers = Arc::new(overrides);
|
||||
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone());
|
||||
}
|
||||
|
||||
/// Apply a set-layer-visibility action to `layers`, one change at a time, so a toggle flips the
|
||||
/// state the changes before it left. Returns whether any layer ended up changed.
|
||||
pub(crate) fn apply_layer_state(layers: &mut [Layer], groups: &[Vec<(u32, u16)>], changes: &[(LayerOp, (u32, u16))], preserve_rb: bool) -> bool {
|
||||
let before: Vec<bool> = layers.iter().map(|l| l.visible).collect();
|
||||
let index: std::collections::HashMap<(u32, u16), usize> = layers.iter().enumerate().map(|(i, l)| (l.id, i)).collect();
|
||||
for &(op, ocg) in changes {
|
||||
let Some(layer) = index.get(&ocg).and_then(|&i| layers.get_mut(i)) else { continue };
|
||||
let on = match op {
|
||||
LayerOp::On => true,
|
||||
LayerOp::Off => false,
|
||||
LayerOp::Toggle => !layer.visible,
|
||||
};
|
||||
layer.visible = on;
|
||||
// Turning a layer off leaves the rest of its groups alone.
|
||||
if on && preserve_rb {
|
||||
for other in groups.iter().filter(|g| g.contains(&ocg)).flatten().filter(|&&o| o != ocg) {
|
||||
if let Some(l) = index.get(other).and_then(|&i| layers.get_mut(i)) {
|
||||
l.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
layers.iter().zip(before).any(|(l, was)| l.visible != was)
|
||||
}
|
||||
@@ -32,6 +32,7 @@ pub mod export;
|
||||
pub mod field_table;
|
||||
mod history;
|
||||
pub mod js;
|
||||
mod layers;
|
||||
pub mod links;
|
||||
pub mod ocr;
|
||||
mod open;
|
||||
@@ -199,8 +200,8 @@ use document::{display_bytes, info_fields, recovered_render_password, same_pages
|
||||
pub use edit::{BATCH_LABELS, EDIT_LABELS, Edit};
|
||||
pub use error::{EditError, SourceProblem};
|
||||
pub use history::Label;
|
||||
use layers::{apply_layer_state, set_default_layer_state, use_layer_choices};
|
||||
pub use open::{OpenContext, PreparedDocument};
|
||||
use pdfcraft_render::{Layer, LayerOp, RenderPool};
|
||||
use protection::unrestricted;
|
||||
pub use protection::{Changes, Printing, Protection, SecuritySummary};
|
||||
pub use session::Session;
|
||||
@@ -223,59 +224,6 @@ fn render_threads() -> usize {
|
||||
std::thread::available_parallelism().map(std::num::NonZeroUsize::get).unwrap_or(4).clamp(2, 8) - 1
|
||||
}
|
||||
|
||||
/// Write `layers`' visibility into the default optional-content configuration of `cos` (a copy
|
||||
/// made for printing): `/BaseState /ON`, `/ON` the visible layers, `/OFF` the hidden ones. Its
|
||||
/// other entries (`/Order`, `/RBGroups`, `/AS`…) are kept. A document without `/OCProperties`
|
||||
/// is left alone.
|
||||
fn set_default_layer_state(cos: &mut pdfcraft_cos::Document, layers: &[Layer]) {
|
||||
use pdfcraft_cos::{Dict, ObjRef, Object};
|
||||
let Some(root_ref) = cos.trailer().get(b"Root").and_then(Object::as_ref) else { return };
|
||||
let Some(mut root) = cos.get(root_ref).as_dict().cloned() else { return };
|
||||
let Some(properties) = root.get(b"OCProperties").cloned() else { return };
|
||||
let Some(mut properties) = cos.resolve(&properties).as_dict().cloned() else { return };
|
||||
let mut config = properties.get(b"D").and_then(|d| cos.resolve(d).as_dict().cloned()).unwrap_or_else(Dict::new);
|
||||
let refs =
|
||||
|visible: bool| Object::Array(layers.iter().filter(|l| l.visible == visible).map(|l| Object::Ref(ObjRef::new(l.id.0, l.id.1))).collect());
|
||||
config.set(b"BaseState".to_vec(), Object::name("ON"));
|
||||
config.set(b"ON".to_vec(), refs(true));
|
||||
config.set(b"OFF".to_vec(), refs(false));
|
||||
properties.set(b"D".to_vec(), Object::Dict(config));
|
||||
root.set(b"OCProperties".to_vec(), Object::Dict(properties));
|
||||
cos.set(root_ref, Object::Dict(root));
|
||||
}
|
||||
|
||||
/// Render `doc` with the layer visibility in its `info.layers`.
|
||||
fn use_layer_choices(doc: &mut Document) {
|
||||
let overrides: Vec<(i32, i32, bool)> = doc.info.layers.iter().map(|l| (l.id.0 as i32, l.id.1 as i32, l.visible)).collect();
|
||||
doc.config.layers = Arc::new(overrides);
|
||||
doc.renderer = RenderPool::new(doc.display.clone(), render_threads(), doc.config.clone());
|
||||
}
|
||||
|
||||
/// Apply a set-layer-visibility action to `layers`, one change at a time, so a toggle flips the
|
||||
/// state the changes before it left. Returns whether any layer ended up changed.
|
||||
fn apply_layer_state(layers: &mut [Layer], groups: &[Vec<(u32, u16)>], changes: &[(LayerOp, (u32, u16))], preserve_rb: bool) -> bool {
|
||||
let before: Vec<bool> = layers.iter().map(|l| l.visible).collect();
|
||||
let index: std::collections::HashMap<(u32, u16), usize> = layers.iter().enumerate().map(|(i, l)| (l.id, i)).collect();
|
||||
for &(op, ocg) in changes {
|
||||
let Some(layer) = index.get(&ocg).and_then(|&i| layers.get_mut(i)) else { continue };
|
||||
let on = match op {
|
||||
LayerOp::On => true,
|
||||
LayerOp::Off => false,
|
||||
LayerOp::Toggle => !layer.visible,
|
||||
};
|
||||
layer.visible = on;
|
||||
// Turning a layer off leaves the rest of its groups alone.
|
||||
if on && preserve_rb {
|
||||
for other in groups.iter().filter(|g| g.contains(&ocg)).flatten().filter(|&&o| o != ocg) {
|
||||
if let Some(l) = index.get(other).and_then(|&i| layers.get_mut(i)) {
|
||||
l.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
layers.iter().zip(before).any(|(l, was)| l.visible != was)
|
||||
}
|
||||
|
||||
/// A document's working file captured for crash recovery.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RecoverySnapshot {
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use super::document::is_placeholder_title;
|
||||
use super::edit::check_permission;
|
||||
use super::*;
|
||||
use pdfcraft_render::{OpenError, ReadingColors};
|
||||
use pdfcraft_render::{Layer, LayerOp, OpenError, ReadingColors};
|
||||
|
||||
/// A minimal `n`-page document; page `i` shows "Page i+1".
|
||||
pub(crate) fn fixture(n: usize) -> Vec<u8> {
|
||||
|
||||
Reference in New Issue
Block a user