Files
wordcraft/crates/doc/src/lib.rs
T
48b63611cf Watermark: picture watermarks (#3001)
Ported from #3001 by hand onto current main. Design > Watermark > Custom
Watermark gains No watermark / Picture watermark (Select Picture, Scale Auto or
a percentage, Washout) / Text watermark. The picture is drawn centred behind the
text on screen and in PDF; DOCX saves Word's VML picture watermark in every
header and reads Word's back when every page shows the same one.

Changed vs the PR:
- Mirror margins: the picture is centred in the page's own text area
  (`page.body`, as the text watermark), the same rect on screen and in PDF.
- Auto size is one answer: the room of the first section naming the page's
  header (`Document::watermark_fit`), used by screen, PDF and DOCX.
- Washout goes through the picture-corrections path (`WatermarkPicture::fx()` =
  PicFx::from_vml gain/blacklevel, `media::pixmap_for` / `Exporter::image`);
  the PR's washout()/4096 px resize copies are gone, so memory is that of any
  picture. The reader detects Washout from gain/blacklevel with PicFx.
- ODT writes the picture (a frame, `draw:color-mode="watermark"`) and reads it
  back, instead of writing the watermark's text.
- A watermark in a tracked deletion is not the document's (text and picture).
- `design.watermark` checks base64 length before decoding, caps pictures at
  64 MB, reads files through read_file_capped (wasm stub added), validates
  before touching the document.
- The dialog lives in dialogs/dialogs_watermark.rs; strings stay English.

Co-Authored-By: C00kieMonsta <22135261+C00kieMonsta@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCVCCEo7u5wetHaJRU4
2026-11-06 00:34:47 +00:00

149 lines
5.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! WordCraft document model.
//!
//! A [`Document`] holds the main story (`body`) and secondary stories (`parts`: headers,
//! footers, notes, comments, text boxes). A story is a list of [`Block`]s: paragraphs and tables;
//! table cells hold block lists of their own. Blocks, the style sheet and the list definitions
//! are `Arc`-shared, so cloning a document is cheap (undo snapshots, handing a frozen copy to the
//! renderer) and edits copy only what they touch (`Arc::make_mut`).
//!
//! Positions ([`Pos`]) name a story, a [`Path`] to a paragraph and a byte offset in it. Paths
//! compare in document order.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
pub mod bidi;
mod byte_store;
pub mod cfb_guard;
pub mod chart;
mod cjk_numbers;
pub mod cluster;
pub mod collate;
pub mod connector;
pub mod control;
pub mod edit;
pub mod effects;
pub mod encoding;
pub mod eq;
pub mod fields;
pub mod fill;
pub mod freeform;
pub mod graphic;
pub mod ime_text;
pub mod kept;
pub mod legacy_form;
pub mod line_dash;
pub mod link;
pub mod locale_number;
pub mod math;
pub mod math_edit;
pub mod math_latex;
pub mod math_linear;
pub mod math_symbols;
pub mod numbering;
pub mod para;
pub mod picfx;
pub mod props;
pub mod resolve;
pub mod section;
pub mod smart_art;
pub mod styles;
pub mod symbol_fonts;
pub mod table;
pub mod watermark;
pub mod wordart;
pub mod work;
pub mod wrap;
pub mod zip_guard;
use std::collections::BTreeMap;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
pub use control::{ContentControl, ControlKind, ControlLock, ControlRange, ControlWrap};
pub use fields::FieldRange;
pub use numbering::{ListKind, Numbering};
pub use para::{InlineObject, Paragraph, Run};
pub use props::{Align, CharProps, ParaProps, Rgb, TextColor};
pub use section::SectionProps;
pub use styles::{Style, StyleKind, StyleSheet};
pub use table::{Cell, Row, Table};
mod review;
pub use review::{AUTO_TEXT_GALLERY, BuildingBlock, Comment, QUICK_PARTS_GALLERY, Revision, RevisionKind};
mod block;
mod compare;
mod document;
mod error;
mod parts;
mod position;
mod settings;
mod text_walk;
pub use block::{Block, Blocks, BoxBudget, MAX_TEXT_BOX_DEPTH};
pub use compare::para_block;
use document::MediaIndex;
pub use error::{DocError, Result};
pub use parts::{MAX_LINKED_BOXES, MAX_TEXT_BOX_INSET, NoteSeparators, Part, PartKind, PermRange, TEXT_BOX_INSET, TextBody, free_id};
pub use position::{Path, Pos, StoryRef};
pub use settings::{
COMPAT_MODE_CURRENT, CoreProps, CustomProp, DEFAULT_GRID, DEFAULT_HYPHENATION_ZONE, LEGACY_COMPAT_MODE, OdfLayout, PagePicture, Settings, Source,
THEME_COLORS, Watermark, Zoom, ZoomFit,
};
pub(crate) use text_walk::{PARA_WALK_DEPTH, walk_between};
pub use text_walk::{count_chars, count_chars_no_spaces, count_words};
pub use watermark::WatermarkPicture;
/// A document.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Document {
pub body: Blocks,
/// Properties of the last section (OOXML's body-level `w:sectPr`).
pub last_section: SectionProps,
pub parts: BTreeMap<u32, Part>,
/// Shared, like the blocks: an undo step copies it only when a command changes it (a full
/// copy is kilobytes a style, so typing into a document with hundreds of styles cost megabytes
/// a step).
pub styles: Arc<StyleSheet>,
/// Shared like [`Document::styles`].
pub numbering: Arc<Numbering>,
pub comments: BTreeMap<u32, Comment>,
pub revisions: Vec<Revision>,
pub settings: Settings,
pub core: CoreProps,
/// Bibliography sources (References › Manage Sources).
pub sources: Vec<Source>,
/// Custom document properties, in file order.
pub custom_props: Vec<CustomProp>,
/// The document's own footnote separators and continuation notice.
#[serde(skip_serializing_if = "NoteSeparators::is_empty")]
pub footnote_separators: NoteSeparators,
/// The document's own endnote separators and continuation notice.
#[serde(skip_serializing_if = "NoteSeparators::is_empty")]
pub endnote_separators: NoteSeparators,
/// Embedded media (images) by key. Native JSON writes each as base64 (`byte_store.rs`).
#[serde(skip_serializing_if = "BTreeMap::is_empty", with = "byte_store")]
pub media: BTreeMap<String, Arc<Vec<u8>>>,
/// Source-format parts we keep verbatim for round-trip (e.g. custom XML), by part name.
#[serde(skip_serializing_if = "BTreeMap::is_empty", with = "byte_store")]
pub passthrough: BTreeMap<String, Arc<Vec<u8>>>,
/// The document's own building blocks (its glossary document), in file order.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub glossary: Vec<BuildingBlock>,
/// Lookup cache for [`Document::add_media`]; not part of the document (never saved, ignored
/// by `==`).
#[serde(skip)]
media_index: MediaIndex,
/// Markup kept from the file it was opened from, for writing back what isn't edited (see
/// [`kept`]); never saved, ignored by `==`.
#[serde(skip)]
pub kept: kept::Kept,
}
#[cfg(test)]
mod test_log;
#[cfg(test)]
mod tests;