import { Download } from "lucide-react"; import { useEffect, useState } from "react"; import { ProgressCard } from "@/components/common/progress-card"; import { useTranslation } from "@/contexts/i18n-context"; import { useToolProcessor } from "@/hooks/use-tool-processor"; import { useFileStore } from "@/stores/file-store"; type OutputFormat = "png" | "jpg" | "webp" | "avif" | "tiff" | "gif" | "heif" | "jxl"; type SizingMode = "scale" | "custom"; type BgMode = "transparent" | "color"; const FORMATS: OutputFormat[] = ["png", "jpg", "webp", "avif", "tiff", "gif", "heif", "jxl"]; const LOSSY_FORMATS: OutputFormat[] = ["jpg", "webp", "avif", "heif", "jxl"]; const NO_TRANSPARENCY_FORMATS: OutputFormat[] = ["jpg", "tiff"]; const SCALE_PRESETS = [0.5, 1, 2, 3, 4]; const DPI_PRESETS = [72, 96, 150, 300]; interface SvgDims { width: number; height: number; } function parseSvgDimensions(file: File): Promise { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = () => { const text = reader.result as string; const vbMatch = text.match( /viewBox=["'][\s]*([0-9.]+)[\s,]+([0-9.]+)[\s,]+([0-9.]+)[\s,]+([0-9.]+)[\s]*["']/, ); if (vbMatch) { const w = parseFloat(vbMatch[3]); const h = parseFloat(vbMatch[4]); if (w > 0 && h > 0) return resolve({ width: Math.round(w), height: Math.round(h) }); } const wMatch = text.match(/\bwidth=["']([0-9.]+)/); const hMatch = text.match(/\bheight=["']([0-9.]+)/); if (wMatch && hMatch) { const w = parseFloat(wMatch[1]); const h = parseFloat(hMatch[1]); if (w > 0 && h > 0) return resolve({ width: Math.round(w), height: Math.round(h) }); } resolve(null); }; reader.onerror = () => resolve(null); reader.readAsText(file); }); } export function SvgToRasterSettings() { const { t } = useTranslation(); const { files } = useFileStore(); const { processFiles, processAllFiles, processing, error, downloadUrl, progress } = useToolProcessor("svg-to-raster"); // SVG intrinsic dimensions const [svgDims, setSvgDims] = useState(null); // Sizing const [sizingMode, setSizingMode] = useState("scale"); const [scale, setScale] = useState(1); const [customWidth, setCustomWidth] = useState(1024); const [customHeight, setCustomHeight] = useState(""); // Render const [dpi, setDpi] = useState(300); const [format, setFormat] = useState("png"); const [quality, setQuality] = useState(90); // Background const [bgMode, setBgMode] = useState("transparent"); const [bgColor, setBgColor] = useState("#ffffff"); const isLossy = LOSSY_FORMATS.includes(format); const hasFile = files.length > 0; // Parse SVG dimensions when file changes useEffect(() => { if (files.length === 0) { setSvgDims(null); return; } parseSvgDimensions(files[0]).then((dims) => { setSvgDims(dims); if (!dims) setSizingMode("custom"); }); }, [files]); // When format changes to one that does not support transparency, switch bgMode useEffect(() => { if (NO_TRANSPARENCY_FORMATS.includes(format) && bgMode === "transparent") { setBgMode("color"); } }, [format, bgMode]); const computedWidth = svgDims ? Math.round(svgDims.width * scale) : null; const computedHeight = svgDims ? Math.round(svgDims.height * scale) : null; const handleProcess = () => { const settings: Record = { dpi, quality, outputFormat: format, backgroundColor: bgMode === "transparent" ? "#00000000" : bgColor, }; if (sizingMode === "scale" && computedWidth && computedHeight) { settings.width = computedWidth; settings.height = computedHeight; } else if (sizingMode === "custom") { settings.width = customWidth; if (customHeight) settings.height = Number(customHeight); } if (files.length > 1) { processAllFiles(files, settings); } else { processFiles(files, settings); } }; const btnClass = (active: boolean) => `text-xs py-1.5 rounded ${active ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"}`; return (
{/* SVG Info */} {hasFile && svgDims && (

SVG Dimensions

{svgDims.width} x {svgDims.height} {sizingMode === "scale" && computedWidth && computedHeight && ( {" "} → {computedWidth} x {computedHeight} )}
)} {hasFile && !svgDims && (

SVG Dimensions

Could not detect dimensions. Using custom size.
)}
{/* Sizing Mode */}

Sizing

{/* Scale controls */} {sizingMode === "scale" && svgDims && (
{SCALE_PRESETS.map((s) => ( ))}
Scale {scale}x
setScale(Number(e.target.value))} className="w-full mt-1" />
)} {/* Custom size controls */} {sizingMode === "custom" && (
setCustomWidth(Number(e.target.value))} min={1} max={16384} className="w-full mt-0.5 px-2 py-1.5 rounded border border-border bg-background text-sm text-foreground" />
setCustomHeight(e.target.value)} placeholder="Auto" className="w-full mt-0.5 px-2 py-1.5 rounded border border-border bg-background text-sm text-foreground" />
)}
{/* Render DPI */}

Render DPI

{DPI_PRESETS.map((d) => ( ))}
{/* Format */}

Format

{FORMATS.map((f) => ( ))}
{/* Quality (lossy only) */} {isLossy && ( <>
Quality {quality}
setQuality(Number(e.target.value))} className="w-full mt-1" />
)}
{/* Background */}

Background

{bgMode === "color" && (
)} {/* Error */} {error &&

{error}

} {/* Process / Progress */} {processing ? ( 1 ? `Converting ${files.length} files` : "Converting SVG"} stage={progress.stage} percent={progress.percent} elapsed={progress.elapsed} /> ) : ( )} {/* Download */} {downloadUrl && ( Download )}
); }