UNPKG

merge-jpg

Version:

A privacy-first client-side image merging library powered by TLDraw Canvas

506 lines (491 loc) 14.3 kB
import { Editor } from '@tldraw/editor'; /** * Core types for @tree/merge-jpg package */ /** * Represents an image file with metadata */ interface ImageFile { /** Unique identifier for the image */ id: string; /** The actual file object */ file: File; /** Blob URL for preview/processing */ url: string; /** Original filename */ name: string; /** File size in bytes */ size: number; /** MIME type (image/jpeg, image/png, etc.) */ type: string; /** Image width in pixels */ width?: number; /** Image height in pixels */ height?: number; } /** * Merge direction for image layout */ type MergeDirection = "horizontal" | "vertical"; /** * Supported output formats */ type OutputFormat = "jpeg" | "png" | "pdf"; /** * PDF page size options */ type PDFPageSize = "a4" | "letter" | "a3"; /** * Configuration settings for image merging */ interface MergeSettings { /** Direction to merge images (horizontal = left-to-right, vertical = top-to-bottom) */ direction: MergeDirection; /** Output format */ format: OutputFormat; /** Spacing between images in pixels */ spacing: number; /** Background color (hex format, e.g., "#ffffff") */ backgroundColor: string; /** JPEG quality (10-100, only used for JPEG format) */ quality: number; /** PDF page size (only used for PDF format) */ pdfPageSize?: PDFPageSize; } /** * Result of image merging operation */ interface MergeResult { /** Blob URL of the merged image */ url: string; /** Generated filename */ filename: string; /** File size in bytes */ size: number; /** Output format */ format?: OutputFormat; } /** * Progress callback function */ type ProgressCallback = (progress: number) => void; /** * Error types that can occur during processing */ type ErrorType = "file_count" | "file_size" | "file_type" | "network" | "processing" | "initialization" | "unknown"; /** * Error information */ interface MergeError { type: ErrorType; message: string; fileName?: string; originalError?: Error; } /** * Image position and dimensions for layout calculation */ interface ImagePosition { /** X coordinate */ x: number; /** Y coordinate */ y: number; /** Width */ width: number; /** Height */ height: number; } /** * Layout calculation result */ interface LayoutResult { /** Total canvas width */ canvasWidth: number; /** Total canvas height */ canvasHeight: number; /** Position of each image */ positions: ImagePosition[]; } /** * Options for TLDraw merger initialization */ interface MergerOptions { /** Container element for the hidden TLDraw instance (optional) */ container?: HTMLElement; /** Custom canvas size limits */ maxCanvasSize?: { width: number; height: number; }; /** Debug mode - keeps TLDraw instance visible for debugging */ debug?: boolean; } /** * Default merge settings */ declare const DEFAULT_MERGE_SETTINGS: MergeSettings; /** * Validation constraints */ declare const VALIDATION_CONSTRAINTS: { /** Maximum file size in bytes (100MB) */ readonly MAX_FILE_SIZE: number; /** Maximum number of images that can be merged at once */ readonly MAX_FILE_COUNT: 50; /** Allowed MIME types */ readonly ALLOWED_TYPES: readonly ["image/jpeg", "image/jpg", "image/png"]; /** Allowed file extensions */ readonly ALLOWED_EXTENSIONS: readonly [".jpg", ".jpeg", ".png"]; /** Quality range for JPEG */ readonly QUALITY_RANGE: { readonly min: 10; readonly max: 100; }; /** Spacing range in pixels */ readonly SPACING_RANGE: { readonly min: 0; readonly max: 200; }; }; /** * Core TLDraw-based image merger * Extracted and refactored from the original merge-jpg codebase */ /** * Core TLDraw-based image merger class * * This class creates a hidden TLDraw editor instance and uses it to merge images * entirely on the client-side, preserving user privacy. */ declare class TldrawMerger { private editor; private container; private root; private isInitialized; private options; constructor(options?: MergerOptions); /** * Initializes the TLDraw editor instance */ initialize(): Promise<void>; /** * Merges images using TLDraw canvas rendering */ merge(images: ImageFile[], settings: MergeSettings, onProgress?: ProgressCallback): Promise<MergeResult>; /** * Clears all shapes from the canvas */ private clearCanvas; /** * Validates that canvas size is within acceptable limits */ private validateCanvasSize; /** * Creates TLDraw assets from image files */ private createImageAssets; /** * Positions image shapes on the canvas */ private positionImages; /** * Exports the canvas as an image */ private exportImage; /** * Cleanup resources */ destroy(): void; /** * Gets the current initialization status */ get initialized(): boolean; /** * Gets the TLDraw editor instance (for advanced use cases) */ get editorInstance(): Editor | null; } /** * Main ImageMerger class providing a high-level API for image merging * * @example * ```typescript * const merger = new ImageMerger(); * await merger.initialize(); * * const result = await merger.mergeFiles(files, { * direction: 'vertical', * format: 'jpeg', * spacing: 10 * }); * * // Download the result * const link = document.createElement('a'); * link.href = result.url; * link.download = result.filename; * link.click(); * * merger.destroy(); * ``` */ declare class ImageMerger { private tldrawMerger; private isInitialized; private options; constructor(options?: MergerOptions); /** * Initializes the image merger * Must be called before using any merge methods */ initialize(): Promise<void>; /** * Merges an array of File objects * This is the main entry point for most use cases */ mergeFiles(files: File[], settings?: Partial<MergeSettings>, onProgress?: ProgressCallback): Promise<MergeResult>; /** * Merges an array of ImageFile objects * Use this if you have already processed files or need more control */ mergeImages(images: ImageFile[], settings?: Partial<MergeSettings>, onProgress?: ProgressCallback): Promise<MergeResult>; /** * Generates a PDF from images (internal method) */ private generatePDF; /** * Calculates the layout for given images and settings without merging * Useful for previewing the result or validating settings */ calculateLayout(images: ImageFile[], settings?: Partial<MergeSettings>): { layout: LayoutResult; warnings: string[]; settings: MergeSettings; }; /** * Validates files before processing * Returns validation errors without processing the files */ validateFiles(files: File[]): Promise<{ valid: boolean; errors: MergeError[]; warnings: string[]; }>; /** * Gets information about supported formats and constraints */ getCapabilities(): { supportedInputFormats: string[]; supportedOutputFormats: string[]; maxFileSize: number; maxFileCount: number; pdfPageSizes: string[]; qualityRange: { min: number; max: number; }; spacingRange: { min: number; max: number; }; }; /** * Cleanup all resources * Should be called when done using the merger to prevent memory leaks */ destroy(): void; /** * Gets current initialization status */ get initialized(): boolean; /** * Gets the underlying TLDraw merger instance for advanced usage */ get tldrawInstance(): TldrawMerger | null; } /** * PDF generation utilities for @tree/merge-jpg * Extracted and refactored from the original PDF generator */ /** * Options for PDF generation progress tracking */ interface PDFGenerationOptions { onProgress?: ProgressCallback; } /** * PDF Generator class for creating multi-page PDFs from images */ declare class PDFGenerator { /** * Generates a PDF document from an array of images * Each image becomes a separate page in the PDF */ static generatePDF(images: ImageFile[], settings: MergeSettings, options?: PDFGenerationOptions): Promise<Uint8Array>; /** * Converts a data URL to byte array */ private static dataUrlToBytes; /** * Calculates image dimensions to fit within page bounds while maintaining aspect ratio */ private static calculateImageDimensions; /** * Converts hex color to RGB values */ private static hexToRgb; /** * Creates a PDF result object from generated bytes */ static createPDFResult(pdfBytes: Uint8Array): MergeResult; /** * Gets available PDF page sizes */ static getAvailablePageSizes(): Record<PDFPageSize, { width: number; height: number; }>; /** * Validates if a page size is supported */ static isValidPageSize(pageSize: string): pageSize is PDFPageSize; } /** * Validation utilities for @tree/merge-jpg */ /** * Validates an array of image files */ declare function validateImages(images: ImageFile[]): MergeError[]; /** * Validates merge settings */ declare function validateSettings(settings: MergeSettings): MergeError[]; /** * Validates if a string is a valid hex color */ declare function isValidHexColor(color: string): boolean; /** * Formats file size in human-readable format */ declare function formatFileSize(bytes: number): string; /** * Checks if the current environment supports the required APIs */ declare function validateEnvironment(): MergeError[]; /** * Creates a MergeError from a generic Error */ declare function createMergeError(type: MergeError['type'], message: string, originalError?: Error, fileName?: string): MergeError; /** * Layout calculation utilities for @tree/merge-jpg * Replicates the exact logic from the original tldraw-merger.ts */ /** * Calculates the layout for images based on merge settings * This function replicates the exact layout algorithm from the original implementation */ declare function calculateLayout(images: ImageFile[], settings: MergeSettings): LayoutResult; /** * Validates the calculated layout for potential issues */ declare function validateLayout(layout: LayoutResult): string[]; /** * Calculates the estimated memory usage for a given layout */ declare function estimateMemoryUsage(layout: LayoutResult, format: string): { bytes: number; megabytes: number; description: string; }; /** * File processing utilities for @tree/merge-jpg */ /** * Converts a File object to a DataURL string */ declare function fileToDataUrl(file: File): Promise<string>; /** * Gets image dimensions from a File object */ declare function getImageDimensions(file: File): Promise<{ width: number; height: number; }>; /** * Processes raw files into ImageFile objects with metadata */ declare function processFiles(files: File[]): Promise<{ images: ImageFile[]; errors: MergeError[]; }>; /** * Cleans up blob URLs to prevent memory leaks */ declare function cleanupImageFiles(images: ImageFile[]): void; /** * Validates if a file is a valid image by attempting to load it */ declare function validateImageFile(file: File): Promise<boolean>; /** * Generates a unique ID for images */ declare function generateImageId(): string; /** * Converts a blob to a File object */ declare function blobToFile(blob: Blob, filename: string): File; /** * Downloads a blob as a file */ declare function downloadBlob(blob: Blob, filename: string): void; /** * Quick function to merge files with minimal setup * Automatically initializes and cleans up the merger * * @param files - Array of image files to merge * @param settings - Merge settings (optional) * @param onProgress - Progress callback (optional) * @returns Promise resolving to merge result */ declare function mergeFiles(files: File[], settings?: Partial<MergeSettings>, onProgress?: ProgressCallback): Promise<MergeResult>; /** * Quick function to merge ImageFile objects with minimal setup * Automatically initializes and cleans up the merger * * @param images - Array of ImageFile objects to merge * @param settings - Merge settings (optional) * @param onProgress - Progress callback (optional) * @returns Promise resolving to merge result */ declare function mergeImages(images: ImageFile[], settings?: Partial<MergeSettings>, onProgress?: ProgressCallback): Promise<MergeResult>; /** * Quick function to validate files before processing * * @param files - Array of files to validate * @returns Promise resolving to validation result */ declare function validateFiles(files: File[]): Promise<{ valid: boolean; errors: MergeError[]; warnings: string[]; }>; /** * Gets library capabilities and constraints */ declare function getCapabilities(): { supportedInputFormats: string[]; supportedOutputFormats: string[]; maxFileSize: number; maxFileCount: number; pdfPageSizes: string[]; qualityRange: { min: number; max: number; }; spacingRange: { min: number; max: number; }; }; declare const version = "1.0.0"; export { DEFAULT_MERGE_SETTINGS, type ErrorType, type ImageFile, ImageMerger, type ImagePosition, type LayoutResult, type MergeDirection, type MergeError, type MergeResult, type MergeSettings, type MergerOptions, type OutputFormat, PDFGenerator, type PDFPageSize, type ProgressCallback, TldrawMerger, VALIDATION_CONSTRAINTS, blobToFile, calculateLayout, cleanupImageFiles, createMergeError, downloadBlob, estimateMemoryUsage, fileToDataUrl, formatFileSize, generateImageId, getCapabilities, getImageDimensions, isValidHexColor, mergeFiles, mergeImages, processFiles, validateEnvironment, validateFiles, validateImageFile, validateImages, validateLayout, validateSettings, version };