Source: ui/screenshotController.js

/**
 * @module ScreenshotController
 * @author Radim Brnka
 * @description Screenshot capturing logic.
 * @copyright Synaptory Fractal Traveler, 2025-2026
 * @license MIT
 */

import {JuliaRenderer} from "../renderers/juliaRenderer";
import {APP, CONSOLE_GROUP_STYLE, SCREENSHOT_JPEG_COMPRESSION_QUALITY} from "../global/constants";
import {expandComplexToString} from "../global/utils";
import {isJuliaMode} from "./ui";

/**
 * Generates filename based on current timestamp
 * @return {string}
 */
function getFilename() {
    const now = new Date();
    const pad = (n) => String(n).padStart(2, '0');
    return `fractal-${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}.jpg`;
}

/**
 * Returns watermark text lines for current fractal type and its properties.
 * @param {FractalRenderer} fractalApp
 * @return {{line1: string, line2: string}}
 */
function getWatermarkLines(fractalApp) {
    const fractalType = isJuliaMode() ? 'Julia' : 'Mandelbrot';
    const line1 = APP.defaultName;
    const line2 = `${fractalType}: p=${expandComplexToString(fractalApp.pan.slice(), 6)}, zoom=${fractalApp.zoom.toExponential(2)}` +
        `${(fractalApp instanceof JuliaRenderer) ? `, c=${expandComplexToString(fractalApp.c)}` : ``}`;
    return {line1, line2};
}

/**
 * Draws rectangle with rounded borders
 * @param {CanvasRenderingContext2D} ctx
 * @param {number} x
 * @param {number} y
 * @param {number} width
 * @param {number} height
 * @param {number} radius
 */
function drawRoundRect(ctx, x, y, width, height, radius) {
    ctx.beginPath();
    ctx.moveTo(x + radius, y);
    ctx.lineTo(x + width - radius, y);
    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
    ctx.lineTo(x + width, y + height - radius);
    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
    ctx.lineTo(x + radius, y + height);
    ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
    ctx.lineTo(x, y + radius);
    ctx.quadraticCurveTo(x, y, x + radius, y);
    ctx.closePath();
}

/**
 * Generates screenshot of the canvas, adds watermark and emulates link click to download the file.
 * @param {HTMLCanvasElement} canvas
 * @param {FractalRenderer} fractalApp
 * @param {string} accentColor
 */
export function takeScreenshot(canvas, fractalApp, accentColor) {
    console.groupCollapsed(`%c takeScreenshot`, CONSOLE_GROUP_STYLE);

    // Ensure the fractal is fully rendered before taking a screenshot
    fractalApp.draw();

    // Create an offscreen canvas for watermarking
    const offscreenCanvas = document.createElement('canvas');
    offscreenCanvas.width = canvas.width;
    offscreenCanvas.height = canvas.height;
    const ctx = offscreenCanvas.getContext('2d');

    if (!ctx) {
        console.error('Unable to get 2D context for the canvas.');
        return;
    }

    // Copy the fractal canvas content to the offscreen canvas
    ctx.drawImage(canvas, 0, 0);

    // Define the watermark text and style
    const {line1, line2} = getWatermarkLines(fractalApp);
    const fontSize = 12;
    const lineSpacing = 4;
    const padding = 6;
    const borderWidth = 1;

    // Font styles matching h1 for line1
    const line1Font = `italic ${fontSize}px "Bruno Ace SC", sans-serif`;
    const line2Font = `${fontSize}px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`;

    ctx.textAlign = 'center';
    ctx.letterSpacing = '1px';
    ctx.textBaseline = 'middle';

    // Measure text widths
    ctx.font = line1Font;
    const line1Width = ctx.measureText(line1).width;
    ctx.font = line2Font;
    const line2Width = ctx.measureText(line2).width;

    // Calculate rectangle size for two lines
    const maxTextWidth = Math.max(line1Width, line2Width);
    const rectWidth = maxTextWidth + padding * 2 + borderWidth * 2;
    const rectHeight = fontSize * 2 + lineSpacing + padding * 2 + borderWidth * 2;

    // Position the rectangle in the bottom-right corner
    const x = offscreenCanvas.width - rectWidth - padding;
    const y = offscreenCanvas.height - rectHeight - padding;

    // Draw the semi-transparent black background
    ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
    drawRoundRect(ctx, x, y, rectWidth, rectHeight, 8);
    ctx.fill();

    // Draw the text centered within the rectangle
    const textX = x + rectWidth / 2;
    const line1Y = y + padding + borderWidth + fontSize / 2;
    const line2Y = line1Y + fontSize + lineSpacing;

    // Draw line 1 (app name with h1 styling)
    ctx.font = line1Font;
    ctx.fillStyle = accentColor;
    ctx.fillText(line1, textX, line1Y);

    // Draw line 2 (coordinates)
    ctx.font = line2Font;
    ctx.fillStyle = accentColor;
    ctx.fillText(line2, textX, line2Y);

    // Create a temporary link for downloading the image
    const link = document.createElement('a');

    // Set the download attributes
    link.setAttribute('download', getFilename());
    link.setAttribute('href', offscreenCanvas.toDataURL("image/jpeg", SCREENSHOT_JPEG_COMPRESSION_QUALITY));
    link.click();

    console.log('Screenshot successfully taken.');
    console.groupEnd();
}