import { jsPDF } from 'jspdf'; import type { ComicSeries, ComicIteration } from '../types'; export const createMangaPdf = (images: string[], title: string): void => { console.log('createMangaPdf called with:', { imageCount: images.length, title }); if (images.length === 0) { console.error('No images provided for PDF creation'); return; } // Create PDF with first image dimensions to maintain aspect ratio let doc: jsPDF | null = null; images.forEach((imgData, index) => { if (index === 0) { // Use jsPDF's built-in method to get image properties try { const imgProps = (doc as any)?.getImageProperties ? (doc as any).getImageProperties(`data:image/jpeg;base64,${imgData}`) : null; // If we can't get properties, create a temporary jsPDF instance to get them if (!imgProps) { const tempDoc = new jsPDF(); const tempImgProps = (tempDoc as any).getImageProperties(`data:image/jpeg;base64,${imgData}`); var aspectRatio = tempImgProps.width / tempImgProps.height; console.log('Image dimensions from temp doc:', { width: tempImgProps.width, height: tempImgProps.height, aspectRatio }); } else { var aspectRatio = imgProps.width / imgProps.height; console.log('Image dimensions:', { width: imgProps.width, height: imgProps.height, aspectRatio }); } } catch (error) { // Fallback: assume standard comic book aspect ratio (3:4 portrait) console.warn('Could not determine image dimensions, using default comic aspect ratio:', error); var aspectRatio = 3 / 4; // Standard comic book ratio } // Scale to reasonable print size while maintaining aspect ratio // Use a base size and scale according to aspect ratio const baseSize = 200; // mm - base dimension for the larger side let pageWidth, pageHeight; if (aspectRatio > 1) { // Landscape/Wide format pageWidth = baseSize; pageHeight = baseSize / aspectRatio; } else { // Portrait/Tall format pageHeight = baseSize; pageWidth = baseSize * aspectRatio; } console.log('PDF page dimensions:', { pageWidth, pageHeight, aspectRatio }); doc = new jsPDF({ orientation: aspectRatio > 1 ? 'landscape' : 'portrait', unit: 'mm', format: [pageWidth, pageHeight] }); // Add first image filling entire page with no margins doc.addImage(`data:image/jpeg;base64,${imgData}`, 'JPEG', 0, 0, pageWidth, pageHeight); } else { // For subsequent pages, maintain the same format as the first page if (doc) { doc.addPage(); const pageSize = doc.internal.pageSize; const currentWidth = pageSize.getWidth(); const currentHeight = pageSize.getHeight(); doc.addImage(`data:image/jpeg;base64,${imgData}`, 'JPEG', 0, 0, currentWidth, currentHeight); } } }); const safeTitle = title.replace(/[^a-z0-9]/gi, '_').toLowerCase(); const filename = `${safeTitle}_manga.pdf`; console.log('Saving PDF with filename:', filename); try { doc.save(filename); console.log('PDF save command executed successfully'); } catch (error) { console.error('Error saving PDF:', error); } }; /** * Creates a comprehensive PDF from a complete comic series with multiple iterations * Uses the aspect ratio of the first image in the series */ export const createComicSeriesPdf = (comicSeries: ComicSeries): void => { console.log('createComicSeriesPdf called with:', { iterations: comicSeries.iterations.length, seriesTitle: comicSeries.seriesTitle }); // Get dimensions from the first image in the first iteration const firstIteration = comicSeries.iterations[0]; if (!firstIteration || firstIteration.images.length === 0) { console.error('No images found in comic series'); return; } const firstImageData = firstIteration.images[0]; // Use jsPDF's built-in method to get image properties let aspectRatio; try { const tempDoc = new jsPDF(); const imgProps = (tempDoc as any).getImageProperties(`data:image/jpeg;base64,${firstImageData}`); aspectRatio = imgProps.width / imgProps.height; console.log('Series image dimensions:', { width: imgProps.width, height: imgProps.height, aspectRatio }); } catch (error) { console.warn('Could not determine series image dimensions, using default comic aspect ratio:', error); aspectRatio = 3 / 4; // Standard comic book ratio } // Use same sizing logic as single comic PDF const baseSize = 200; let pdfWidth, pdfHeight; if (aspectRatio > 1) { pdfWidth = baseSize; pdfHeight = baseSize / aspectRatio; } else { pdfHeight = baseSize; pdfWidth = baseSize * aspectRatio; } console.log('Series PDF dimensions:', { pdfWidth, pdfHeight, aspectRatio }); const doc = new jsPDF({ orientation: aspectRatio > 1 ? 'landscape' : 'portrait', unit: 'mm', format: [pdfWidth, pdfHeight] }); let isFirstPage = true; let totalPages = 0; // Add title page for the series if (isFirstPage) { // Create a series title page doc.setFillColor(20, 20, 30); doc.rect(0, 0, pdfWidth, pdfHeight, 'F'); // Title doc.setTextColor(255, 255, 255); doc.setFontSize(24); doc.setFont('helvetica', 'bold'); const titleLines = doc.splitTextToSize(comicSeries.seriesTitle, pdfWidth - 20); const titleY = pdfHeight / 2 - 30; doc.text(titleLines, pdfWidth / 2, titleY, { align: 'center' }); // Author doc.setFontSize(16); doc.setFont('helvetica', 'normal'); doc.text(`By ${comicSeries.author}`, pdfWidth / 2, titleY + 20, { align: 'center' }); // Series info doc.setFontSize(12); doc.text(`Complete Series • ${comicSeries.iterations.length} Chapters • ${comicSeries.totalPages} Pages`, pdfWidth / 2, titleY + 35, { align: 'center' }); // Created date doc.setFontSize(10); doc.setTextColor(180, 180, 180); doc.text(`Created: ${comicSeries.createdAt.toLocaleDateString()}`, pdfWidth / 2, titleY + 50, { align: 'center' }); // "Comic Genesis AI" attribution doc.text('Generated by Comic Genesis AI', pdfWidth / 2, pdfHeight - 20, { align: 'center' }); isFirstPage = false; totalPages = 1; } // Add each iteration with separator pages comicSeries.iterations.forEach((iteration, iterationIndex) => { // Add chapter divider page (except for the first iteration) if (iterationIndex > 0) { doc.addPage(); totalPages++; // Chapter divider page doc.setFillColor(30, 30, 40); doc.rect(0, 0, pdfWidth, pdfHeight, 'F'); doc.setTextColor(255, 255, 255); doc.setFontSize(20); doc.setFont('helvetica', 'bold'); const chapterTitle = `Chapter ${iterationIndex + 1}`; doc.text(chapterTitle, pdfWidth / 2, pdfHeight / 2 - 20, { align: 'center' }); doc.setFontSize(16); doc.setFont('helvetica', 'normal'); const iterationTitleLines = doc.splitTextToSize(iteration.title, pdfWidth - 20); doc.text(iterationTitleLines, pdfWidth / 2, pdfHeight / 2, { align: 'center' }); doc.setFontSize(10); doc.setTextColor(180, 180, 180); doc.text(`${iteration.images.length} pages`, pdfWidth / 2, pdfHeight / 2 + 20, { align: 'center' }); } // Add all images from this iteration iteration.images.forEach((imgData, imageIndex) => { if (!isFirstPage || imageIndex > 0) { doc.addPage(); totalPages++; } try { // Fill entire page with image doc.addImage(`data:image/jpeg;base64,${imgData}`, 'JPEG', 0, 0, pdfWidth, pdfHeight); // Add page number footer doc.setFontSize(8); doc.setTextColor(120, 120, 120); doc.text(`${totalPages}`, pdfWidth - 10, pdfHeight - 5, { align: 'right' }); } catch (error) { console.warn(`Failed to add image ${imageIndex} from iteration ${iterationIndex}:`, error); // Add error page doc.setFillColor(50, 50, 60); doc.rect(0, 0, pdfWidth, pdfHeight, 'F'); doc.setTextColor(200, 200, 200); doc.setFontSize(12); doc.text('Image could not be loaded', pdfWidth / 2, pdfHeight / 2, { align: 'center' }); } if (isFirstPage) { isFirstPage = false; } }); }); // Add final credits/summary page doc.addPage(); totalPages++; doc.setFillColor(15, 15, 25); doc.rect(0, 0, pdfWidth, pdfHeight, 'F'); doc.setTextColor(255, 255, 255); doc.setFontSize(18); doc.setFont('helvetica', 'bold'); doc.text('The End', pdfWidth / 2, pdfHeight / 2 - 30, { align: 'center' }); doc.setFontSize(12); doc.setFont('helvetica', 'normal'); doc.text(`Complete Series: ${comicSeries.seriesTitle}`, pdfWidth / 2, pdfHeight / 2 - 10, { align: 'center' }); doc.text(`By ${comicSeries.author}`, pdfWidth / 2, pdfHeight / 2 + 5, { align: 'center' }); doc.setFontSize(10); doc.setTextColor(180, 180, 180); doc.text(`${comicSeries.iterations.length} chapters • ${totalPages} total pages`, pdfWidth / 2, pdfHeight / 2 + 25, { align: 'center' }); doc.text(`Generated: ${new Date().toLocaleDateString()}`, pdfWidth / 2, pdfHeight / 2 + 40, { align: 'center' }); doc.text('Powered by Comic Genesis AI & Google Gemini', pdfWidth / 2, pdfHeight - 15, { align: 'center' }); // Save the PDF const safeTitle = comicSeries.seriesTitle.replace(/[^a-z0-9]/gi, '_').toLowerCase(); doc.save(`${safeTitle}_complete_series.pdf`); }; /** * Creates a PDF from a single comic iteration with chapter formatting * Uses the aspect ratio of the first image in the iteration */ export const createIterationPdf = (iteration: ComicIteration, chapterNumber?: number): void => { if (iteration.images.length === 0) { console.error('No images found in iteration'); return; } // Get dimensions from the first image using jsPDF const firstImageData = iteration.images[0]; let aspectRatio; try { const tempDoc = new jsPDF(); const imgProps = (tempDoc as any).getImageProperties(`data:image/jpeg;base64,${firstImageData}`); aspectRatio = imgProps.width / imgProps.height; console.log('Iteration image dimensions:', { width: imgProps.width, height: imgProps.height, aspectRatio }); } catch (error) { console.warn('Could not determine iteration image dimensions, using default comic aspect ratio:', error); aspectRatio = 3 / 4; // Standard comic book ratio } // Use same sizing logic as other PDF functions const baseSize = 200; let pdfWidth, pdfHeight; if (aspectRatio > 1) { pdfWidth = baseSize; pdfHeight = baseSize / aspectRatio; } else { pdfHeight = baseSize; pdfWidth = baseSize * aspectRatio; } const doc = new jsPDF({ orientation: aspectRatio > 1 ? 'landscape' : 'portrait', unit: 'mm', format: [pdfWidth, pdfHeight] }); let isFirstPage = true; // Add chapter title page if chapter number is provided if (chapterNumber) { doc.setFillColor(20, 20, 30); doc.rect(0, 0, pdfWidth, pdfHeight, 'F'); doc.setTextColor(255, 255, 255); doc.setFontSize(20); doc.setFont('helvetica', 'bold'); doc.text(`Chapter ${chapterNumber}`, pdfWidth / 2, pdfHeight / 2 - 20, { align: 'center' }); doc.setFontSize(16); doc.setFont('helvetica', 'normal'); const titleLines = doc.splitTextToSize(iteration.title, pdfWidth - 20); doc.text(titleLines, pdfWidth / 2, pdfHeight / 2, { align: 'center' }); doc.setFontSize(10); doc.text(`By ${iteration.author}`, pdfWidth / 2, pdfHeight / 2 + 20, { align: 'center' }); isFirstPage = false; } // Add all images iteration.images.forEach((imgData, index) => { if (!isFirstPage || index > 0) { doc.addPage(); } try { // Fill entire page with image doc.addImage(`data:image/jpeg;base64,${imgData}`, 'JPEG', 0, 0, pdfWidth, pdfHeight); } catch (error) { console.warn(`Failed to add image ${index}:`, error); } if (isFirstPage) { isFirstPage = false; } }); const safeTitle = iteration.title.replace(/[^a-z0-9]/gi, '_').toLowerCase(); const fileName = chapterNumber ? `${safeTitle}_chapter_${chapterNumber}.pdf` : `${safeTitle}.pdf`; doc.save(fileName); };