Files
pdfcraft/crates/engine/src/stamp_preview.rs
T
Joel Tony 99c78557eb M5: a stamp grows and shrinks with its handles before release (#3518)
While a handle of a Fill & Sign line, check, cross, dot, typed signature
or comment stamp was dragged, only the selection frame followed the
pointer; the stamp kept its old size (or place) until release. Image
signatures already had a live layer. Every stamp now gets one: the page
without the stamp, and the stamp drawn alone on a transparent page,
whose pixels are stretched with the frame. Resizing a stamp only changes
its /Rect, which its appearance is stretched to fit, so the preview is
what the release produces.

- render: RenderConfig.transparent clears the page to transparent.
- annot: isolate_annotation leaves a page showing one annotation alone.
- engine: Document::stamp_preview builds the two read-only layers; the
  page beneath gets the appearances drawn for comments without one.
- ui-egui: the signature drag layer covers every stamp, rendering the
  stamp's own tile sharp at the current zoom.
2026-10-19 23:42:32 +05:30

84 lines
3.8 KiB
Rust

//! A stamp while it is moved or resized: the page without it, and the stamp alone.
use std::sync::Arc;
use pdfcraft_cos::{SaveOptions, write_incremental};
use pdfcraft_render::{PageRenderer, RenderConfig, RenderPool, RenderRequest, RenderedPage};
use crate::{Document, guard};
/// Read-only layers for interactive movement and resizing: the page without the stamp, and the
/// stamp alone on a transparent page. Moving or resizing a stamp only changes its `/Rect`, which
/// its appearance is stretched to fit, so these two show the result before it is applied.
/// Preparing them never changes the working bytes, dirty flag or undo history.
pub struct StampPreview {
page: usize,
background: Arc<Vec<u8>>,
stamp: Arc<Vec<u8>>,
config: RenderConfig,
}
impl StampPreview {
/// One worker for the page beneath the stamp.
pub fn background_renderer(&self) -> RenderPool {
RenderPool::new(self.background.clone(), 1, self.config.clone())
}
/// One worker for the stamp alone: its page has no content, no other annotations and no
/// background, so only the stamp's own pixels are drawn.
pub fn stamp_renderer(&self) -> RenderPool {
RenderPool::new(self.stamp.clone(), 1, self.stamp_config())
}
/// The page beneath the stamp, rendered synchronously with the renderer's normal pixel limits.
pub fn render_background(&self, scale: f32) -> Result<RenderedPage, String> {
self.render(&self.background, self.config.clone(), scale)
}
/// The stamp alone on a transparent page, rendered like [`Self::render_background`].
pub fn render_stamp(&self, scale: f32) -> Result<RenderedPage, String> {
self.render(&self.stamp, self.stamp_config(), scale)
}
fn stamp_config(&self) -> RenderConfig {
RenderConfig { transparent: true, ..self.config.clone() }
}
fn render(&self, bytes: &Arc<Vec<u8>>, config: RenderConfig, scale: f32) -> Result<RenderedPage, String> {
if !scale.is_finite() || scale <= 0.0 {
return Err("preview scale must be finite and positive".into());
}
guard(|| {
let out = PageRenderer::new(bytes.clone(), config).render(RenderRequest { page: self.page, scale, ..Default::default() });
if let Some(e) = &out.error {
return Err(e.clone());
}
Ok(out)
})?
}
}
impl Document {
/// The layers that show the stamp at `(page, index)` moved or resized as it is dragged.
/// `None` for an annotation that isn't a stamp, or one without an appearance to draw.
pub fn stamp_preview(&self, page: usize, index: usize) -> Result<Option<StampPreview>, String> {
guard(|| {
let editor = self.editor.as_ref().ok_or("the document can't be read")?;
let is_stamp = self.info.annotations.iter().any(|a| a.page == page && a.index == index && a.subtype == "Stamp");
if !is_stamp {
return Ok(None);
}
let mut stamp = editor.cos.clone();
if !pdfcraft_annot::isolate_annotation(&mut stamp, page, index).map_err(|e| e.to_string())? {
return Ok(None);
}
let mut background = editor.cos.clone();
pdfcraft_annot::delete_annotation(&mut background, page, index).map_err(|e| e.to_string())?;
// The page beneath as it is displayed: comments without an appearance get theirs.
let background = pdfcraft_annot::with_missing_appearances(&background).unwrap_or(background);
let write = |cos: &pdfcraft_cos::Document| write_incremental(cos, &SaveOptions::default()).map(Arc::new).map_err(|e| e.to_string());
Ok(Some(StampPreview { page, background: write(&background)?, stamp: write(&stamp)?, config: self.config.clone() }))
})?
}
}