printwell-cli 0.1.11

Command-line tool for HTML to PDF conversion
Documentation
//! Watermark command implementation.

use anyhow::{Context, Result};

use crate::cli::args::WatermarkArgs;
use crate::cli::utils::{PageSelection, parse_color_hex, parse_page_selection};

pub fn watermark(args: WatermarkArgs) -> Result<()> {
    use printwell::watermark::{Color, Layer, Watermark, add_watermark};

    // Validate that either text or image is provided
    if args.text.is_none() && args.image.is_none() {
        anyhow::bail!("Either --text or --image must be specified");
    }
    if args.text.is_some() && args.image.is_some() {
        anyhow::bail!("Cannot specify both --text and --image");
    }

    let pdf_data = std::fs::read(&args.input)
        .with_context(|| format!("Failed to read input file: {}", args.input))?;

    // Parse position
    let position = parse_position(&args.position)
        .with_context(|| format!("Invalid position: {}", args.position))?;

    // Parse color using shared utility
    let (r, g, b) =
        parse_color_hex(&args.color).with_context(|| format!("Invalid color: {}", args.color))?;
    let color = Color::rgb(r, g, b);

    // Parse layer
    let layer = if args.foreground {
        Layer::Foreground
    } else {
        Layer::Background
    };

    // Parse page selection using shared utility and convert to watermark's type
    let page_selection =
        parse_page_selection(args.pages.as_deref()).context("Invalid page selection")?;
    let pages = convert_page_selection(page_selection);

    // Load image if specified
    let image_data = if let Some(ref image_path) = args.image {
        Some(
            std::fs::read(image_path)
                .with_context(|| format!("Failed to read image file: {image_path}"))?,
        )
    } else {
        None
    };

    // Build watermark
    let watermark = Watermark {
        text: args.text,
        image: image_data,
        position,
        rotation: args.rotation,
        opacity: args.opacity,
        font_size: args.font_size,
        font_name: String::from("Helvetica"),
        color,
        layer,
        pages,
        scale: args.scale,
    };

    // Add watermark
    let result = add_watermark(&pdf_data, &watermark).context("Failed to add watermark to PDF")?;

    std::fs::write(&args.output, &result)
        .with_context(|| format!("Failed to write watermarked PDF to: {}", args.output))?;

    eprintln!("Watermarked PDF written to: {}", args.output);

    Ok(())
}

/// Parse watermark position string.
/// Position-specific parsing stays local since it's only used by watermarks.
fn parse_position(s: &str) -> Result<printwell::watermark::Position> {
    use printwell::watermark::Position;

    match s.to_lowercase().as_str() {
        "center" => Ok(Position::Center),
        "top-left" | "topleft" => Ok(Position::TopLeft),
        "top-center" | "topcenter" => Ok(Position::TopCenter),
        "top-right" | "topright" => Ok(Position::TopRight),
        "middle-left" | "middleleft" => Ok(Position::MiddleLeft),
        "middle-right" | "middleright" => Ok(Position::MiddleRight),
        "bottom-left" | "bottomleft" => Ok(Position::BottomLeft),
        "bottom-center" | "bottomcenter" => Ok(Position::BottomCenter),
        "bottom-right" | "bottomright" => Ok(Position::BottomRight),
        custom => {
            // Try to parse as "x,y"
            if let Some((x_str, y_str)) = custom.split_once(',') {
                let x: f32 = x_str.trim().parse().context("Invalid X coordinate")?;
                let y: f32 = y_str.trim().parse().context("Invalid Y coordinate")?;
                Ok(Position::Custom(x, y))
            } else {
                anyhow::bail!(
                    "Unknown position. Use: center, top-left, top-center, top-right, \
                     middle-left, middle-right, bottom-left, bottom-center, bottom-right, or x,y"
                );
            }
        }
    }
}

/// Convert generic `PageSelection` to watermark's `PageSelection` type.
fn convert_page_selection(sel: PageSelection) -> printwell::watermark::PageSelection {
    use printwell::watermark::PageSelection as WmPageSelection;
    match sel {
        PageSelection::All => WmPageSelection::All,
        PageSelection::Pages(p) => WmPageSelection::Pages(p),
        PageSelection::Range(s, e) => WmPageSelection::Range(s, e),
        PageSelection::Odd => WmPageSelection::Odd,
        PageSelection::Even => WmPageSelection::Even,
        PageSelection::First => WmPageSelection::First,
        PageSelection::Last => WmPageSelection::Last,
    }
}