import { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType, Table, TableRow, TableCell, WidthType } from 'docx'; import PDFDocument from 'pdfkit'; import ExcelJS from 'exceljs'; // @ts-ignore import officegen from 'officegen'; import fs from 'fs'; import path from 'path'; export interface DocumentContent { title?: string; content: string | any[]; metadata?: Record; } export interface TableData { headers: string[]; rows: string[][]; } export interface SlideContent { title: string; content: string | string[]; bullets?: string[]; } export class DocumentGenerator { // Generate DOCX file static async generateDOCX(content: DocumentContent): Promise { const doc = new Document({ sections: [{ properties: {}, children: this.parseContentToParagraphs(content.content, content.title) }] }); return await Packer.toBuffer(doc); } private static parseContentToParagraphs(content: string | any, title?: string): Paragraph[] { const paragraphs: Paragraph[] = []; // Add title if provided if (title) { paragraphs.push(new Paragraph({ text: title, heading: HeadingLevel.HEADING_1, spacing: { after: 200 } })); } // Handle complex nested structures (like the letter example) if (typeof content === 'object' && content !== null) { // Check if it has sections array if (content.sections && Array.isArray(content.sections)) { for (const section of content.sections) { if (section.content) { // Add section content if (typeof section.content === 'string') { const lines = section.content.split('\n'); for (const line of lines) { if (line.trim()) { paragraphs.push(new Paragraph({ children: [new TextRun(line)], spacing: { after: 100 } })); } } } } // Handle paragraphs array in section if (section.paragraphs && Array.isArray(section.paragraphs)) { for (const para of section.paragraphs) { paragraphs.push(new Paragraph({ children: [new TextRun(para)], spacing: { after: 100 } })); } } } return paragraphs; } // Handle direct content field if (content.content) { return this.parseContentToParagraphs(content.content, title); } } if (typeof content === 'string') { const lines = content.split('\n'); for (const line of lines) { if (line.startsWith('# ')) { // H1 heading paragraphs.push(new Paragraph({ text: line.substring(2), heading: HeadingLevel.HEADING_1 })); } else if (line.startsWith('## ')) { // H2 heading paragraphs.push(new Paragraph({ text: line.substring(3), heading: HeadingLevel.HEADING_2 })); } else if (line.startsWith('### ')) { // H3 heading paragraphs.push(new Paragraph({ text: line.substring(4), heading: HeadingLevel.HEADING_3 })); } else if (line.startsWith('- ') || line.startsWith('* ')) { // Bullet point paragraphs.push(new Paragraph({ text: line.substring(2), bullet: { level: 0 } })); } else if (line.startsWith('1. ') || /^\d+\. /.test(line)) { // Numbered list paragraphs.push(new Paragraph({ text: line.replace(/^\d+\. /, ''), numbering: { reference: "default-numbering", level: 0 } })); } else { // Regular paragraph paragraphs.push(new Paragraph({ children: [new TextRun(line)] })); } } } else if (Array.isArray(content)) { for (const item of content) { if (typeof item === 'string') { paragraphs.push(new Paragraph({ children: [new TextRun(item)] })); } else if (item.type === 'heading') { paragraphs.push(new Paragraph({ text: item.text, heading: item.level || HeadingLevel.HEADING_1 })); } else if (item.type === 'paragraph') { paragraphs.push(new Paragraph({ children: [new TextRun(item.text)] })); } else if (item.type === 'bullet') { paragraphs.push(new Paragraph({ text: item.text, bullet: { level: item.level || 0 } })); } } } return paragraphs; } // Generate PDF file static async generatePDF(content: DocumentContent): Promise { return new Promise((resolve, reject) => { const doc = new PDFDocument(); const buffers: Buffer[] = []; doc.on('data', buffers.push.bind(buffers)); doc.on('end', () => { const pdfData = Buffer.concat(buffers); resolve(pdfData); }); doc.on('error', reject); // Add title if provided if (content.title) { doc.fontSize(20).text(content.title, { align: 'center' }); doc.moveDown(); } // Add content if (typeof content.content === 'string') { const lines = content.content.split('\n'); for (const line of lines) { if (line.startsWith('# ')) { doc.fontSize(18).text(line.substring(2)); } else if (line.startsWith('## ')) { doc.fontSize(16).text(line.substring(3)); } else if (line.startsWith('### ')) { doc.fontSize(14).text(line.substring(4)); } else if (line.startsWith('- ') || line.startsWith('* ')) { doc.fontSize(12).text(`• ${line.substring(2)}`, { indent: 20 }); } else { doc.fontSize(12).text(line); } doc.moveDown(0.5); } } doc.end(); }); } // Generate PowerPoint file static async generatePowerPoint(slides: SlideContent[]): Promise { return new Promise((resolve, reject) => { const pptx = officegen('pptx'); const buffers: Buffer[] = []; pptx.on('data', (data: Buffer) => buffers.push(data)); pptx.on('end', () => { const pptData = Buffer.concat(buffers); resolve(pptData); }); pptx.on('error', reject); // Add title slide if (slides.length > 0 && slides[0].title) { const titleSlide = pptx.makeNewSlide(); titleSlide.title = slides[0].title; if (slides[0].content) { titleSlide.addText(slides[0].content.toString(), { x: 'c', y: 'c', cx: '80%', cy: '30%', font_size: 18 }); } } // Add content slides for (let i = 1; i < slides.length; i++) { const slide = pptx.makeNewSlide(); slide.title = slides[i].title; if (slides[i].bullets && slides[i].bullets!.length > 0) { // Add bullet points const bullets = slides[i].bullets!.map(bullet => ({ text: bullet, options: { font_size: 14 } })); slide.addText(bullets, { x: 0.5, y: 1.5, cx: '90%', cy: '70%' }); } else if (slides[i].content) { // Add regular content slide.addText(slides[i].content.toString(), { x: 0.5, y: 1.5, cx: '90%', cy: '70%', font_size: 14 }); } } pptx.generate(); }); } // Generate Excel file static async generateExcel(data: { sheets: { name: string; data: TableData }[] }): Promise { const workbook = new ExcelJS.Workbook(); workbook.creator = 'ReubenOS'; workbook.created = new Date(); workbook.modified = new Date(); for (const sheetData of data.sheets) { const worksheet = workbook.addWorksheet(sheetData.name); // Add headers worksheet.addRow(sheetData.data.headers); // Style headers worksheet.getRow(1).font = { bold: true }; worksheet.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } }; // Add data rows for (const row of sheetData.data.rows) { worksheet.addRow(row); } // Auto-fit columns worksheet.columns.forEach((column) => { let maxLength = 0; if (column && column.eachCell) { column.eachCell({ includeEmpty: true }, (cell) => { const length = cell.value ? cell.value.toString().length : 10; if (length > maxLength) { maxLength = length; } }); column.width = maxLength + 2; } }); // Add borders to all cells with data const rowCount = worksheet.rowCount; const colCount = worksheet.columnCount; for (let row = 1; row <= rowCount; row++) { for (let col = 1; col <= colCount; col++) { const cell = worksheet.getCell(row, col); cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } }; } } } const buffer = await workbook.xlsx.writeBuffer(); return Buffer.from(buffer); } // Generate LaTeX and compile to PDF (requires latex installation) static async generateLatexPDF(latexContent: string): Promise { // For now, we'll use PDFKit as LaTeX compilation requires external tools // In production, you'd use node-latex or similar with a LaTeX installation return this.generatePDF({ content: this.convertLatexToPlainText(latexContent) }); } private static convertLatexToPlainText(latex: string): string { // Basic LaTeX to plain text conversion return latex .replace(/\\documentclass{.*?}/g, '') .replace(/\\usepackage{.*?}/g, '') .replace(/\\begin{document}/g, '') .replace(/\\end{document}/g, '') .replace(/\\section{(.*?)}/g, '# $1') .replace(/\\subsection{(.*?)}/g, '## $1') .replace(/\\subsubsection{(.*?)}/g, '### $1') .replace(/\\textbf{(.*?)}/g, '**$1**') .replace(/\\textit{(.*?)}/g, '*$1*') .replace(/\\item/g, '- ') .replace(/\\begin{itemize}/g, '') .replace(/\\end{itemize}/g, '') .replace(/\\begin{enumerate}/g, '') .replace(/\\end{enumerate}/g, '') .replace(/\$/g, '') .replace(/\\/g, '\n') .trim(); } // Convert markdown to structured content for document generation static parseMarkdown(markdown: string): any[] { const lines = markdown.split('\n'); const content = []; for (const line of lines) { if (line.startsWith('# ')) { content.push({ type: 'heading', text: line.substring(2), level: HeadingLevel.HEADING_1 }); } else if (line.startsWith('## ')) { content.push({ type: 'heading', text: line.substring(3), level: HeadingLevel.HEADING_2 }); } else if (line.startsWith('### ')) { content.push({ type: 'heading', text: line.substring(4), level: HeadingLevel.HEADING_3 }); } else if (line.startsWith('- ') || line.startsWith('* ')) { content.push({ type: 'bullet', text: line.substring(2) }); } else if (line.trim() !== '') { content.push({ type: 'paragraph', text: line }); } } return content; } }