use crate::error::{Error, Result};
use crate::ppt97::record::{
find_main_masters, find_ppdrawing_in_master, parse_record_header, read_u32_le, write_u32_le,
RT_MAIN_MASTER,
};
const RT_DG_CONTAINER: u16 = 0xF002;
pub const RT_SPGR_CONTAINER: u16 = 0xF003;
pub const RT_SP_CONTAINER: u16 = 0xF004;
pub const RT_FSP: u16 = 0xF00A;
pub const RT_FOPT: u16 = 0xF00B;
pub const RT_CLIENT_TEXTBOX: u16 = 0xF00D;
pub const RT_CLIENT_ANCHOR: u16 = 0xF010;
const RT_TEXT_HEADER_ATOM: u16 = 0x0F9F;
const RT_TEXT_CHARS_ATOM: u16 = 0x0FA0;
const RT_STYLE_TEXT_PROP_ATOM: u16 = 0x0FA1;
#[derive(Clone, Debug)]
pub struct WatermarkConfig {
pub text: String,
pub font_size_pt: u16,
pub color_rgb: (u8, u8, u8),
pub rotation_deg: i32,
}
impl Default for WatermarkConfig {
fn default() -> Self {
Self {
text: "pptx-rs 水印".to_string(),
font_size_pt: 44,
color_rgb: (200, 200, 200),
rotation_deg: 45,
}
}
}
#[allow(clippy::type_complexity)]
pub fn inject_watermark(
ppt_data: &mut Vec<u8>,
config: &WatermarkConfig,
persist_entries: &[(u32, u32)],
pd_offset_old: usize,
) -> Result<(usize, Vec<(u32, u32)>, usize)> {
let master_offsets = find_main_masters(ppt_data)?;
let mut insertions: Vec<(usize, usize)> = Vec::new();
let mut total_inserted = 0;
let watermark_utf16: Vec<u8> = config
.text
.encode_utf16()
.flat_map(|c| c.to_le_bytes())
.collect();
let mut next_shape_id: u32 = find_max_shape_id(ppt_data, &master_offsets)? + 1;
if next_shape_id < 0x1000 {
next_shape_id = 0x1000;
}
for master_offset in master_offsets.iter().rev() {
let master_offset = *master_offset;
if let Some(ppd_offset) = find_ppdrawing_in_master(ppt_data, master_offset)? {
if has_watermark_text(ppt_data, ppd_offset, &watermark_utf16) {
continue;
}
let shape_id = next_shape_id;
next_shape_id += 1;
let watermark_sp = build_watermark_spcontainer(shape_id, config);
let (insert_pos, insert_len) =
inject_watermark_into_ppdrawing(ppt_data, ppd_offset, &watermark_sp)?;
let (_, _, _, master_len) = parse_record_header(ppt_data, master_offset)?;
write_u32_le(ppt_data, master_offset + 4, master_len + insert_len as u32)?;
insertions.push((insert_pos, insert_len));
total_inserted += insert_len;
}
}
let mut new_entries = Vec::with_capacity(persist_entries.len());
for (pid, offset) in persist_entries {
let mut new_offset = *offset;
for (pos, len) in &insertions {
if *pos < new_offset as usize {
new_offset += *len as u32;
}
}
new_entries.push((*pid, new_offset));
}
let mut pd_offset_new = pd_offset_old;
for (pos, len) in &insertions {
if *pos < pd_offset_new {
pd_offset_new += len;
}
}
let pd_data_start = pd_offset_new + 8;
for (i, (_, new_offset)) in new_entries.iter().enumerate() {
let offset_pos = pd_data_start + 4 + i * 4;
write_u32_le(ppt_data, offset_pos, *new_offset)?;
}
Ok((total_inserted, new_entries, pd_offset_new))
}
pub fn inject_watermark_into_ppdrawing(
data: &mut Vec<u8>,
ppd_offset: usize,
watermark_sp: &[u8],
) -> Result<(usize, usize)> {
let (_, _, _, ppd_len) = parse_record_header(data, ppd_offset)?;
let ppd_end = ppd_offset + 8 + ppd_len as usize;
let mut pos = ppd_offset + 8;
let mut dg_offset = None;
while pos + 8 <= ppd_end {
let (ver, _, rec_type, rec_len) = parse_record_header(data, pos)?;
let is_container = ver == 0xF;
let total_len = 8 + rec_len as usize;
if is_container && rec_type == RT_DG_CONTAINER {
dg_offset = Some(pos);
break;
}
pos += total_len;
if !is_container && rec_len == 0 {
break;
}
}
let dg_offset = dg_offset.ok_or_else(|| {
Error::ppt97("inject_watermark: DgContainer (0xF002) not found in PPDrawing")
})?;
let (_, _, _, dg_len) = parse_record_header(data, dg_offset)?;
let dg_end = dg_offset + 8 + dg_len as usize;
let mut pos = dg_offset + 8;
let mut spgr_offset = None;
while pos + 8 <= dg_end {
let (ver, _, rec_type, rec_len) = parse_record_header(data, pos)?;
let is_container = ver == 0xF;
let total_len = 8 + rec_len as usize;
if is_container && rec_type == RT_SPGR_CONTAINER {
spgr_offset = Some(pos);
break;
}
pos += total_len;
if !is_container && rec_len == 0 {
break;
}
}
let spgr_offset = spgr_offset.ok_or_else(|| {
Error::ppt97("inject_watermark: SpgrContainer (0xF003) not found in DgContainer")
})?;
let (_, _, _, spgr_len) = parse_record_header(data, spgr_offset)?;
let spgr_end = spgr_offset + 8 + spgr_len as usize;
let mut pos = spgr_offset + 8;
let mut first_sp_end = None;
while pos + 8 <= spgr_end {
let (ver, _, rec_type, rec_len) = parse_record_header(data, pos)?;
let is_container = ver == 0xF;
let total_len = 8 + rec_len as usize;
if is_container && rec_type == RT_SP_CONTAINER {
first_sp_end = Some(pos + total_len);
break;
}
pos += total_len;
if !is_container && rec_len == 0 {
break;
}
}
let insert_pos = first_sp_end.unwrap_or(spgr_end);
data.splice(insert_pos..insert_pos, watermark_sp.iter().copied());
let insert_len = watermark_sp.len();
write_u32_le(data, spgr_offset + 4, spgr_len + insert_len as u32)?;
write_u32_le(data, dg_offset + 4, dg_len + insert_len as u32)?;
write_u32_le(data, ppd_offset + 4, ppd_len + insert_len as u32)?;
Ok((insert_pos, insert_len))
}
pub fn build_watermark_spcontainer(shape_id: u32, config: &WatermarkConfig) -> Vec<u8> {
let mut children = Vec::new();
let mut fsp = Vec::new();
let ver_inst: u16 = (0xCA << 4) | 0x2; fsp.extend_from_slice(&ver_inst.to_le_bytes());
fsp.extend_from_slice(&RT_FSP.to_le_bytes());
fsp.extend_from_slice(&8u32.to_le_bytes()); fsp.extend_from_slice(&shape_id.to_le_bytes()); fsp.extend_from_slice(&0x00000280u32.to_le_bytes()); children.extend_from_slice(&fsp);
let mut fopt_props = Vec::new();
let rotation_fixed: u32 = ((config.rotation_deg as i64) * 65536) as u32;
fopt_props.extend_from_slice(&0x00BDu16.to_le_bytes());
fopt_props.extend_from_slice(&rotation_fixed.to_le_bytes());
fopt_props.extend_from_slice(&0x0180u16.to_le_bytes());
fopt_props.extend_from_slice(&0x00000000u32.to_le_bytes());
fopt_props.extend_from_slice(&0x01BFu16.to_le_bytes());
fopt_props.extend_from_slice(&0x00000043u32.to_le_bytes());
fopt_props.extend_from_slice(&0x01C1u16.to_le_bytes());
fopt_props.extend_from_slice(&0x00000043u32.to_le_bytes());
fopt_props.extend_from_slice(&0x01C2u16.to_le_bytes());
fopt_props.extend_from_slice(&0x0000000Du32.to_le_bytes());
let num_props = fopt_props.len() / 6;
let mut fopt = Vec::new();
let ver_inst: u16 = ((num_props as u16) << 4) | 0x3; fopt.extend_from_slice(&ver_inst.to_le_bytes());
fopt.extend_from_slice(&RT_FOPT.to_le_bytes());
fopt.extend_from_slice(&(fopt_props.len() as u32).to_le_bytes());
fopt.extend_from_slice(&fopt_props);
children.extend_from_slice(&fopt);
let mut anchor = Vec::new();
let ver_inst: u16 = 0; anchor.extend_from_slice(&ver_inst.to_le_bytes());
anchor.extend_from_slice(&RT_CLIENT_ANCHOR.to_le_bytes());
anchor.extend_from_slice(&8u32.to_le_bytes()); anchor.extend_from_slice(&0i16.to_le_bytes()); anchor.extend_from_slice(&0i16.to_le_bytes()); anchor.extend_from_slice(&5760i16.to_le_bytes()); anchor.extend_from_slice(&4320i16.to_le_bytes()); children.extend_from_slice(&anchor);
let mut textbox_children = Vec::new();
let mut text_header = Vec::new();
let ver_inst: u16 = 0;
text_header.extend_from_slice(&ver_inst.to_le_bytes());
text_header.extend_from_slice(&RT_TEXT_HEADER_ATOM.to_le_bytes());
text_header.extend_from_slice(&4u32.to_le_bytes()); text_header.extend_from_slice(&4u32.to_le_bytes()); textbox_children.extend_from_slice(&text_header);
let text_utf16: Vec<u8> = config
.text
.encode_utf16()
.flat_map(|c| c.to_le_bytes())
.collect();
let text_char_count = config.text.encode_utf16().count() as u32;
let mut text_chars = Vec::new();
let ver_inst: u16 = 0;
text_chars.extend_from_slice(&ver_inst.to_le_bytes());
text_chars.extend_from_slice(&RT_TEXT_CHARS_ATOM.to_le_bytes());
text_chars.extend_from_slice(&(text_utf16.len() as u32).to_le_bytes());
text_chars.extend_from_slice(&text_utf16);
textbox_children.extend_from_slice(&text_chars);
let mut style_data = Vec::new();
style_data.extend_from_slice(&1u32.to_le_bytes()); style_data.extend_from_slice(&text_char_count.to_le_bytes()); style_data.extend_from_slice(&0u16.to_le_bytes()); style_data.extend_from_slice(&0u32.to_le_bytes()); style_data.extend_from_slice(&text_char_count.to_le_bytes()); style_data.extend_from_slice(&0x000000C0u32.to_le_bytes()); let font_size_value = config.font_size_pt as u32 * 100;
style_data.extend_from_slice(&(font_size_value as u16).to_le_bytes()); style_data.extend_from_slice(&config.color_rgb.0.to_le_bytes()); style_data.extend_from_slice(&config.color_rgb.1.to_le_bytes()); style_data.extend_from_slice(&config.color_rgb.2.to_le_bytes()); style_data.extend_from_slice(&0u8.to_le_bytes());
let mut style_atom = Vec::new();
let ver_inst: u16 = 0;
style_atom.extend_from_slice(&ver_inst.to_le_bytes());
style_atom.extend_from_slice(&RT_STYLE_TEXT_PROP_ATOM.to_le_bytes());
style_atom.extend_from_slice(&(style_data.len() as u32).to_le_bytes());
style_atom.extend_from_slice(&style_data);
textbox_children.extend_from_slice(&style_atom);
let mut client_textbox = Vec::new();
let ver_inst: u16 = 0xF; client_textbox.extend_from_slice(&ver_inst.to_le_bytes());
client_textbox.extend_from_slice(&RT_CLIENT_TEXTBOX.to_le_bytes());
client_textbox.extend_from_slice(&(textbox_children.len() as u32).to_le_bytes());
client_textbox.extend_from_slice(&textbox_children);
children.extend_from_slice(&client_textbox);
let mut sp_container = Vec::new();
let ver_inst: u16 = 0xF; sp_container.extend_from_slice(&ver_inst.to_le_bytes());
sp_container.extend_from_slice(&RT_SP_CONTAINER.to_le_bytes());
sp_container.extend_from_slice(&(children.len() as u32).to_le_bytes());
sp_container.extend_from_slice(&children);
sp_container
}
pub fn find_max_shape_id(data: &[u8], master_offsets: &[usize]) -> Result<u32> {
let mut max_id: u32 = 0;
for &master_offset in master_offsets {
if let Some(ppd_offset) = find_ppdrawing_in_master(data, master_offset)? {
max_id = max_id.max(scan_shape_ids_in_ppdrawing(data, ppd_offset)?);
}
}
Ok(max_id)
}
fn scan_shape_ids_in_ppdrawing(data: &[u8], ppd_offset: usize) -> Result<u32> {
let (_, _, _, ppd_len) = parse_record_header(data, ppd_offset)?;
let ppd_end = ppd_offset + 8 + ppd_len as usize;
let mut max_id: u32 = 0;
let mut pos = ppd_offset + 8;
while pos + 8 <= ppd_end {
let (ver, _, rec_type, rec_len) = parse_record_header(data, pos)?;
let is_container = ver == 0xF;
let total_len = 8 + rec_len as usize;
if is_container {
let child_max = scan_shape_ids_in_ppdrawing(data, pos)?;
max_id = max_id.max(child_max);
} else if rec_type == RT_FSP && rec_len >= 8 {
let shape_id = read_u32_le(data, pos + 8)?;
if shape_id > max_id {
max_id = shape_id;
}
}
pos += total_len;
if !is_container && rec_len == 0 {
break;
}
}
Ok(max_id)
}
pub fn has_watermark_text(data: &[u8], ppd_offset: usize, watermark_utf16: &[u8]) -> bool {
let (_, _, _, ppd_len) = match parse_record_header(data, ppd_offset) {
Ok(v) => v,
Err(_) => return false,
};
let ppd_end = ppd_offset + 8 + ppd_len as usize;
if ppd_end > data.len() {
return false;
}
data[ppd_offset..ppd_end]
.windows(watermark_utf16.len())
.any(|w| w == watermark_utf16)
}
#[allow(dead_code)]
const _UNUSED_RT_MAIN_MASTER: u16 = RT_MAIN_MASTER;