// PNG-001 — native PNG rasterization.
//
// `svg_to_png` consumes the SVG produced by the JS core and renders it via
// `resvg` + `tiny-skia`. Fonts come from `EMBEDDED_FONTS`, a static table
// generated by `build.rs` (PNG-001 build-script extension) directly from
// `node_modules/@excalidraw/excalidraw/dist/prod/fonts/*.woff2` — the same
// bytes the JS side base64-embeds, so fontdb's matching agrees with the
// `@font-face` declarations in the SVG `<defs>`.
//
// the implementation notes has the design sketch. Notes:
//   * No system-fonts path. Fonts are exclusively the embedded set; the
//     CLI must render identically across machines.
//   * `RasterOptions` mirrors the subset of `Args` that affects rasterization
//     (scale, max). Other rendering flags (dark, padding, frame, …) are
//     absorbed by the JS render step before the SVG ever reaches resvg.
//   * Determinism: same input bytes → same output bytes. resvg's
//     `tiny_skia::Pixmap::encode_png` writes a reproducible IDAT.
//
// PNG-002 (CLI wiring) is in main.rs. PNG-003 (golden snapshots) and
// PNG-004 (browser SSIM) are deferred.

use anyhow::{anyhow, Context};
use resvg::{tiny_skia, usvg};

/// Subset of CLI args that affect rasterization. `scale` mirrors `--scale`,
/// `max` mirrors `--max` (clamp on the larger of width/height after scaling).
#[derive(Debug, Clone, Default)]
pub struct RasterOptions {
    pub scale: Option<f32>,
    pub max: Option<u32>,
}

/// Iterate the embedded fonts as `(relative_path, &[u8])` tuples. The bytes
/// are decompressed TTF (the build script runs every shipped WOFF2 through
/// woofwoof). Unlike a `static &[(&str, &[u8])]` table — which would need
/// const-stable subslice indexing (not yet stable, see rust-lang/rust#143874)
/// — this view materializes the slices at first call from the offset/length
/// index baked at build time.
pub fn iter_embedded_fonts() -> impl Iterator<Item = (&'static str, &'static [u8])> {
    EMBEDDED_FONT_INDEX
        .iter()
        .map(|(name, start, len)| (*name, &EMBEDDED_FONTS_BLOB[*start..*start + *len]))
}

/// Rasterize an SVG string to PNG bytes using `resvg` and the embedded
/// font table. Honors `opts.scale` and `opts.max` (clamp on the larger of
/// width/height after scaling).
pub fn svg_to_png(svg: &str, opts: &RasterOptions) -> anyhow::Result<Vec<u8>> {
    // Build a fontdb from the embedded TTF bytes. usvg::Options takes an
    // Arc<fontdb::Database>; we own the database long enough to load all
    // fonts and then transfer ownership into the Arc.
    let mut fontdb = usvg::fontdb::Database::new();
    for (_rel_path, bytes) in iter_embedded_fonts() {
        fontdb.load_font_data(bytes.to_vec());
    }

    let usvg_opts = usvg::Options {
        fontdb: std::sync::Arc::new(fontdb),
        ..Default::default()
    };
    let tree = usvg::Tree::from_str(svg, &usvg_opts).context("usvg failed to parse SVG")?;

    let size = tree.size().to_int_size();
    let (mut w, mut h) = (size.width(), size.height());
    let s = opts.scale.unwrap_or(1.0);
    if s != 1.0 {
        w = ((w as f32) * s).round().max(1.0) as u32;
        h = ((h as f32) * s).round().max(1.0) as u32;
    }
    if let Some(max) = opts.max {
        let largest = w.max(h);
        if largest > max && largest > 0 {
            let factor = max as f32 / largest as f32;
            w = ((w as f32) * factor).round().max(1.0) as u32;
            h = ((h as f32) * factor).round().max(1.0) as u32;
        }
    }

    let mut pixmap = tiny_skia::Pixmap::new(w, h)
        .ok_or_else(|| anyhow!("pixmap alloc failed ({w}x{h})"))?;
    let transform = tiny_skia::Transform::from_scale(
        w as f32 / size.width() as f32,
        h as f32 / size.height() as f32,
    );
    resvg::render(&tree, transform, &mut pixmap.as_mut());
    pixmap.encode_png().context("PNG encoding failed")
}

include!(concat!(env!("OUT_DIR"), "/embedded_fonts.rs"));
