`;
};
const downloadTxt = () => {
const data = getPosterData();
let content = `--- SCIENTIFIC POSTER CONTENT DRAFT ---\n`;
content += "========================================\n\n";
content += `TITLE: ${data.title.toUpperCase()}\n`;
content += `AUTHORS: ${data.authors}\n`;
content += `CONTEXT: ${data.conference} | Size: ${data.size}\n`;
content += "========================================\n\n";
content += "1. BACKGROUND/INTRODUCTION\n";
content += "----------------------------------------\n";
content += data.background.split('\n').map(p => p.trim()).join('\n') + '\n\n';
content += "2. METHODS\n";
content += "----------------------------------------\n";
content += data.methods.split('\n').map(p => p.trim()).join('\n') + '\n\n';
content += "3. RESULTS SUMMARY\n";
content += "----------------------------------------\n";
content += data.results.split('\n').map(p => p.trim()).join('\n') + '\n\n';
content += "4. CONCLUSION & FUTURE WORK\n";
content += "----------------------------------------\n";
content += data.conclusion.split('\n').map(p => p.trim()).join('\n') + '\n\n';
content += "5. FIGURES & VISUALS\n";
content += "----------------------------------------\n";
data.figures.forEach((fig, index) => {
content += `FIGURE ${index + 1} (${fig.size.toUpperCase()})\n`;
content += ` Caption: ${fig.caption}\n`;
content += ` Visual Placeholder: ${fig.url}\n\n`;
});
content += "6. REFERENCES\n";
content += "----------------------------------------\n";
content += data.references.split('\n').map(p => p.trim()).join('\n') + '\n';
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `${data.title.replace(/ /g, '_').substring(0, 30) || 'poster'}_content.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
};
const downloadPDF = () => {
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
alert('Error: jsPDF library not loaded.');
return;
}
const { jsPDF } = window.jspdf;
// Use A4 Landscape to represent the wide poster format
const doc = new jsPDF('l', 'mm', 'a4');
const data = getPosterData();
const margin = 10;
const pageWidth = doc.internal.pageSize.getWidth();
const columnWidth = (pageWidth - margin * 4) / 3; // 3 columns with 10mm margins
const columnGap = 10;
let yPos = 15;
let currentColumn = 1;
const addText = (text, size, style, xOffset, color = [52, 73, 94]) => {
doc.setFontSize(size);
doc.setFont(undefined, style);
doc.setTextColor(color[0], color[1], color[2]);
const lines = doc.splitTextToSize(text, columnWidth);
if (yPos + (lines.length * 4) > 195) { // Check for page break (A4 height - 10mm bottom margin)
doc.addPage('l');
yPos = 15;
}
doc.text(lines, margin + xOffset, yPos);
yPos += (lines.length * 4) + 2;
};
const addSection = (title, content) => {
const xOffset = (currentColumn - 1) * (columnWidth + columnGap);
// Add Section Title
yPos += 3;
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.setTextColor(231, 76, 60); // Red
doc.text(title.toUpperCase(), margin + xOffset, yPos);
doc.setDrawColor(231, 76, 60);
doc.setLineWidth(0.5);
doc.line(margin + xOffset, yPos + 1, margin + xOffset + columnWidth, yPos + 1);
yPos += 5;
// Add Content
addText(content, 10, 'normal', xOffset);
};
const resetColumns = () => {
currentColumn = 1;
yPos = 50; // Reset Y below header
};
// --- Build PDF Document ---
// 1. Title Header Area (Across all columns)
doc.setFontSize(22);
doc.setFont(undefined, 'bold');
doc.setTextColor(44, 62, 80);
doc.text(data.title.toUpperCase(), pageWidth / 2, yPos, { align: 'center' });
yPos += 8;
doc.setFontSize(12);
doc.setFont(undefined, 'normal');
doc.setTextColor(108, 117, 125);
doc.text(data.authors, pageWidth / 2, yPos, { align: 'center' });
yPos += 5;
doc.text(`[${data.conference} | Size: ${data.size}]`, pageWidth / 2, yPos, { align: 'center' });
yPos += 10;
// Define starting Y for 3 columns
let columnY = [yPos, yPos, yPos];
// --- Content Flow ---
// Helper function to add content to the shortest column
const addContentToShortest = (title, content, columnSpan = 1) => {
// Find shortest column
const minHeight = Math.min(...columnY);
let shortestIndex = columnY.findIndex(h => h === minHeight);
// Add Section Title
const xOffset = shortestIndex * (columnWidth + columnGap);
let currentY = columnY[shortestIndex];
if (currentY > 185) { // Start a new page if too close to the bottom
doc.addPage('l');
columnY = [15, 15, 15]; // Reset all Ys
shortestIndex = 0;
currentY = 15;
}
currentY += 3;
doc.setFontSize(14);
doc.setFont(undefined, 'bold');
doc.setTextColor(231, 76, 60); // Red
doc.text(title.toUpperCase(), margin + xOffset, currentY);
doc.setDrawColor(231, 76, 60);
doc.setLineWidth(0.5);
doc.line(margin + xOffset, currentY + 1, margin + xOffset + columnWidth * columnSpan, currentY + 1);
currentY += 5;
// Add Content
doc.setFontSize(10);
doc.setFont(undefined, 'normal');
doc.setTextColor(52, 73, 94);
let textToPrint = content.split('\n').filter(p => p.trim() !== '').join('\n');
const lines = doc.splitTextToSize(textToPrint, columnWidth * columnSpan);
doc.text(lines, margin + xOffset, currentY);
currentY += (lines.length * 4) + 5; // 4mm per line + buffer
// Update the column height(s)
if (columnSpan === 1) {
columnY[shortestIndex] = currentY;
} else if (columnSpan === 2) {
columnY[shortestIndex] = currentY;
columnY[shortestIndex + 1] = currentY; // Update the spanned column too
}
};
// Flow the content:
addContentToShortest("Background", data.background);
addContentToShortest("Methods", data.methods);
// Results/Figures section is large, attempt to span two columns
if (columnY[0] === columnY[1] && data.figures.length > 0) { // If first two columns are roughly equal, use a 2-column span
let figureContent = data.results.split('\n').filter(p => p.trim() !== '').join('\n') + '\n\n';
data.figures.forEach((fig, index) => {
figureContent += `\nFigure ${index + 1} (${fig.size.toUpperCase()}): ${fig.url}\nCaption: ${fig.caption}\n`;
});
addContentToShortest("Results & Figures", figureContent, 2);
} else {
addContentToShortest("Results", data.results);
addContentToShortest("Figures", data.figures.map((fig, index) => `Figure ${index+1} (${fig.size.toUpperCase()}): ${fig.caption} (${fig.url})`).join('\n'));
}
addContentToShortest("Conclusion", data.conclusion);
addContentToShortest("References", data.references);
// Footer
const finalY = Math.max(...columnY);
doc.setFontSize(8);
doc.setTextColor(108, 117, 125);
doc.text("Generated by Scientific Poster Generator | Content is for layout planning only.", pageWidth / 2, 200, { align: 'center' });
doc.save(`${data.title.replace(/ /g, '_').substring(0, 30) || 'scientific_poster'}.pdf`);
};
// --- Event Listeners ---
// Tab Buttons
tabButtons.forEach((btn, index) => {
btn.addEventListener('click', () => showTab(index + 1));
});
// Next/Prev Navigation
nextBtn.addEventListener('click', () => showTab(currentTab + 1));
prevBtn.addEventListener('click', () => showTab(currentTab - 1));
// Tab 3 Actions
figureInputs.addBtn.addEventListener('click', addFigure);
// Tab 4 Actions
refreshBtn.addEventListener('click', generatePreview);
downloadPdfBtn.addEventListener('click', downloadPDF);
downloadTxtBtn.addEventListener('click', downloadTxt);
// --- Initialization ---
// Pre-populate with sample data
inputs.title.value = "A Novel Approach to Deep-Sea Coral Taxonomy Using Machine Learning";
inputs.authors.value = "Jane Doe (MIT), John Smith (WHOI)";
inputs.conference.value = "Ocean Sciences Meeting, 2026";
inputs.background.value = "Deep-sea coral taxonomy relies heavily on visual inspection, leading to inconsistent species identification. Our study addresses the need for scalable, objective classification using computer vision techniques and a custom CNN model.";
inputs.methods.value = "1. A dataset of 5,000 images was collected from five deep-sea ROV missions. 2. A custom Convolutional Neural Network (CNN) model was trained on a high-performance GPU cluster. 3. Model performance was validated using k-fold cross-validation against human expert labels.";
inputs.results.value = "The CNN achieved a mean classification accuracy of 96.2% across 12 target species, exceeding the current human consensus rate of 88%. Training converged in 4.5 hours. See figures for confusion matrix and precision/recall data.";
inputs.conclusion.value = "We successfully demonstrated a high-accuracy ML solution for coral taxonomy. This method is faster and more reliable than manual inspection. Future work will focus on integrating real-time ROV video processing.";
inputs.references.value = "1. Smith et al., J. Marine Bio. (2020)\n2. Chen et al., Deep Sea Res. (2023)";
figureList.push({ id: figureIdCounter++, caption: "CNN architecture and training loss curve.", url: "Diagram: VGG-16 Architecture", size: "medium" });
figureList.push({ id: figureIdCounter++, caption: "Confusion matrix for the 12 target species.", url: "Table: Confusion Matrix PNG", size: "medium" });
figureList.push({ id: figureIdCounter++, caption: "Precision and Recall values vs. Depth.", url: "Bar Chart: Performance Metrics", size: "small" });
showTab(1); // Set initial state
});
