manga-nanobanana / services /pdfService.ts
Akhil-Theerthala's picture
Upload 25 files
2cdfc6e verified
raw
history blame
14.1 kB
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);
};