merge-jpg
Version:
A privacy-first client-side image merging library powered by TLDraw Canvas
506 lines (491 loc) • 14.3 kB
text/typescript
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 };