|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
let doc: jsPDF | null = null; |
|
|
|
|
|
images.forEach((imgData, index) => { |
|
|
if (index === 0) { |
|
|
|
|
|
try { |
|
|
const imgProps = (doc as any)?.getImageProperties ? |
|
|
(doc as any).getImageProperties(`data:image/jpeg;base64,${imgData}`) : |
|
|
null; |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
console.warn('Could not determine image dimensions, using default comic aspect ratio:', error); |
|
|
var aspectRatio = 3 / 4; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const baseSize = 200; |
|
|
|
|
|
let pageWidth, pageHeight; |
|
|
if (aspectRatio > 1) { |
|
|
|
|
|
pageWidth = baseSize; |
|
|
pageHeight = baseSize / aspectRatio; |
|
|
} else { |
|
|
|
|
|
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] |
|
|
}); |
|
|
|
|
|
|
|
|
doc.addImage(`data:image/jpeg;base64,${imgData}`, 'JPEG', 0, 0, pageWidth, pageHeight); |
|
|
} else { |
|
|
|
|
|
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); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const createComicSeriesPdf = (comicSeries: ComicSeries): void => { |
|
|
console.log('createComicSeriesPdf called with:', { iterations: comicSeries.iterations.length, seriesTitle: comicSeries.seriesTitle }); |
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
if (isFirstPage) { |
|
|
|
|
|
doc.setFillColor(20, 20, 30); |
|
|
doc.rect(0, 0, pdfWidth, pdfHeight, 'F'); |
|
|
|
|
|
|
|
|
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' }); |
|
|
|
|
|
|
|
|
doc.setFontSize(16); |
|
|
doc.setFont('helvetica', 'normal'); |
|
|
doc.text(`By ${comicSeries.author}`, pdfWidth / 2, titleY + 20, { align: 'center' }); |
|
|
|
|
|
|
|
|
doc.setFontSize(12); |
|
|
doc.text(`Complete Series • ${comicSeries.iterations.length} Chapters • ${comicSeries.totalPages} Pages`, |
|
|
pdfWidth / 2, titleY + 35, { align: 'center' }); |
|
|
|
|
|
|
|
|
doc.setFontSize(10); |
|
|
doc.setTextColor(180, 180, 180); |
|
|
doc.text(`Created: ${comicSeries.createdAt.toLocaleDateString()}`, |
|
|
pdfWidth / 2, titleY + 50, { align: 'center' }); |
|
|
|
|
|
|
|
|
doc.text('Generated by Comic Genesis AI', |
|
|
pdfWidth / 2, pdfHeight - 20, { align: 'center' }); |
|
|
|
|
|
isFirstPage = false; |
|
|
totalPages = 1; |
|
|
} |
|
|
|
|
|
|
|
|
comicSeries.iterations.forEach((iteration, iterationIndex) => { |
|
|
|
|
|
if (iterationIndex > 0) { |
|
|
doc.addPage(); |
|
|
totalPages++; |
|
|
|
|
|
|
|
|
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' }); |
|
|
} |
|
|
|
|
|
|
|
|
iteration.images.forEach((imgData, imageIndex) => { |
|
|
if (!isFirstPage || imageIndex > 0) { |
|
|
doc.addPage(); |
|
|
totalPages++; |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
doc.addImage(`data:image/jpeg;base64,${imgData}`, 'JPEG', 0, 0, pdfWidth, pdfHeight); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
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' }); |
|
|
|
|
|
|
|
|
const safeTitle = comicSeries.seriesTitle.replace(/[^a-z0-9]/gi, '_').toLowerCase(); |
|
|
doc.save(`${safeTitle}_complete_series.pdf`); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const createIterationPdf = (iteration: ComicIteration, chapterNumber?: number): void => { |
|
|
if (iteration.images.length === 0) { |
|
|
console.error('No images found in iteration'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
iteration.images.forEach((imgData, index) => { |
|
|
if (!isFirstPage || index > 0) { |
|
|
doc.addPage(); |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
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); |
|
|
}; |
|
|
|