Skip to main content
Glama

MCP PDF

compare-approaches.ts10.5 kB
import { createWriteStream } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import PDFDocument from 'pdfkit'; import { registerEmojiFont } from '../src/lib/emoji-renderer.ts'; import { hasEmoji, setupFonts } from '../src/lib/fonts.ts'; import { renderTextWithEmoji } from '../src/lib/pdf-helpers.ts'; const outputDir = tmpdir(); /** * Approach 1: Algorithmic (custom code) - What we built in space-resume-full.ts */ async function generateWithAlgorithm() { const outputPath = join(outputDir, 'space-resume-ALGORITHM.pdf'); const doc = new PDFDocument({ size: [612, 792], margins: { top: 50, bottom: 50, left: 60, right: 60 }, }); const stream = createWriteStream(outputPath); doc.pipe(stream); const fonts = await setupFonts(doc); const { regular: regularFont, bold: boldFont } = fonts; const pageWidth = 612; // Black background doc.rect(0, 0, 612, 792).fill('black'); doc.on('pageAdded', () => { doc.rect(0, 0, 612, 792).fill('black'); }); doc.moveDown(2); doc.fontSize(11).font(regularFont).fillColor('#4DA6FF'); doc.text('IN THE VASTNESS OF SPACE AND TIME...', { align: 'center' }); doc.moveDown(1.5); const resumeLines = [ 'THE ODYSSEY OF ALEX QUANTUM', 'Chapter I: The Engineer Awakens', '', 'Home World: San Francisco, CA, USA', 'Holotransmission: +1 (555) 123-4567 | DataNet: alex@example.com', '', 'In an era when organizations needed visionaries, one engineer discovered they possessed', 'a rare gift - the ability to unite teams, modernize legacy systems,', 'and lead transformations through impossible challenges. This is their story...', '', "THE HERO'S JOURNEY", '', 'Chapter VII: The Platform Awakening (2024-2025)', 'Alliance: TechCorp Industries | Title: Senior Engineering Manager', '', 'Our hero discovered a critical challenge - users across the organization', 'needed better tools for collaboration. Armed with modern cloud technologies', 'and AI-driven insights, Alex led an expedition to modernize the platform.', ]; for (let i = 0; i < resumeLines.length; i++) { const line = resumeLines[i]; if (!line) continue; if (line === '') { doc.moveDown(0.3); continue; } const progress = i / resumeLines.length; const fontSize = 7 + progress * 13; const width = 250 + progress * 250; const x = (pageWidth - width) / 2; const isHeading = line === line.toUpperCase() || line.startsWith('Chapter'); const font = isHeading ? boldFont : regularFont; doc.fillColor('#FFD700'); doc.fontSize(isHeading ? Math.max(fontSize, 12) : fontSize).font(font); renderTextWithEmoji(doc, line, isHeading ? Math.max(fontSize, 12) : fontSize, font, false, { x, width, align: 'center', oblique: 15, characterSpacing: 0.3, }); doc.moveDown(isHeading ? 0.4 : 0.2); } doc.end(); await new Promise<void>((resolve, reject) => { stream.on('finish', () => resolve()); stream.on('error', reject); }); console.log(`\n✅ ALGORITHM approach generated: ${outputPath}`); return outputPath; } /** * Approach 2: Enhanced Tool API (agent workflow) * Agent calculates all values, then calls tool once */ async function generateWithToolAPI() { const outputPath = join(outputDir, 'space-resume-TOOL-API.pdf'); // Step 1: Agent prepares resume content const resumeLines = [ 'THE ODYSSEY OF ALEX QUANTUM', 'Chapter I: The Engineer Awakens', '', 'Home World: San Francisco, CA, USA', 'Holotransmission: +1 (555) 123-4567 | DataNet: alex@example.com', '', 'In an era when organizations needed visionaries, one engineer discovered they possessed', 'a rare gift - the ability to unite teams, modernize legacy systems,', 'and lead transformations through impossible challenges. This is their story...', '', "THE HERO'S JOURNEY", '', 'Chapter VII: The Platform Awakening (2024-2025)', 'Alliance: TechCorp Industries | Title: Senior Engineering Manager', '', 'Our hero discovered a critical challenge - users across the organization', 'needed better tools for collaboration. Armed with modern cloud technologies', 'and AI-driven insights, Alex led an expedition to modernize the platform.', ]; const pageWidth = 612; const content: any[] = []; // Step 2: Agent calculates and builds content array // Add opening line (no tapering) content.push({ type: 'text', text: 'IN THE VASTNESS OF SPACE AND TIME...', fontSize: 11, color: '#4DA6FF', align: 'center', moveDown: 1.5, }); // Add all lines with calculated tapering for (let i = 0; i < resumeLines.length; i++) { const line = resumeLines[i]; if (!line) continue; if (line === '') { content.push({ type: 'text', text: '', fontSize: 1, moveDown: 0.3 }); continue; } // Agent does these calculations const progress = i / resumeLines.length; // 0.0 to 1.0 const fontSize = 7 + progress * 13; // 7pt → 20pt const width = 250 + progress * 250; // 250px → 500px const x = (pageWidth - width) / 2; // Center it // Determine if heading const isHeading = line === line.toUpperCase() || line.startsWith('Chapter'); // Add to content array content.push({ type: isHeading ? 'heading' : 'text', text: line, fontSize: isHeading ? Math.max(fontSize, 12) : fontSize, bold: isHeading, color: '#FFD700', width, x, align: 'center', oblique: 15, characterSpacing: 0.3, moveDown: isHeading ? 0.4 : 0.2, }); } // Step 3: Agent calls the tool with all calculated values await createPdfWithToolAPI({ outputPath, pageSetup: { backgroundColor: 'black', margins: { top: 50, bottom: 50, left: 60, right: 60 }, }, content, }); console.log(`✅ TOOL API approach generated: ${outputPath}`); console.log(` 📊 Agent calculated ${resumeLines.length} progressive values`); console.log(` 📦 Content array size: ${content.length} items`); return outputPath; } /** * Helper that simulates the enhanced create-pdf tool */ async function createPdfWithToolAPI(options: { outputPath: string; pageSetup?: { size?: [number, number]; margins?: { top: number; bottom: number; left: number; right: number }; backgroundColor?: string; }; content: Array<any>; }) { const { outputPath, pageSetup, content } = options; const docOptions: any = {}; if (pageSetup?.size) docOptions.size = pageSetup.size; if (pageSetup?.margins) docOptions.margins = pageSetup.margins; const doc = new PDFDocument(docOptions); const stream = createWriteStream(outputPath); doc.pipe(stream); // Draw background if (pageSetup?.backgroundColor) { const pageSize = pageSetup?.size || [612, 792]; doc.rect(0, 0, pageSize[0], pageSize[1]).fill(pageSetup.backgroundColor); } const contentText = JSON.stringify(content); const containsEmoji = hasEmoji(contentText); const emojiAvailable = containsEmoji ? registerEmojiFont() : false; const fonts = await setupFonts(doc); const { regular: regularFont, bold: boldFont } = fonts; // Background on new pages const drawBackgroundOnPage = () => { if (pageSetup?.backgroundColor) { const currentY = doc.y; const currentX = doc.x; const pageSize = pageSetup?.size || [612, 792]; doc.rect(0, 0, pageSize[0], pageSize[1]).fill(pageSetup.backgroundColor); doc.x = currentX; doc.y = currentY; } }; doc.on('pageAdded', drawBackgroundOnPage); // Process content for (const item of content) { switch (item.type) { case 'text': case 'heading': { const fontSize = item.fontSize ?? (item.type === 'text' ? 12 : 24); const font = item.bold !== false && item.type === 'heading' ? boldFont : item.bold ? boldFont : regularFont; if (item.color) doc.fillColor(item.color); const options: any = {}; if (item.x !== undefined) options.x = item.x; if (item.y !== undefined) options.y = item.y; if (item.align !== undefined) options.align = item.align; if (item.width !== undefined) options.width = item.width; if (item.oblique !== undefined) options.oblique = item.oblique; if (item.characterSpacing !== undefined) options.characterSpacing = item.characterSpacing; renderTextWithEmoji(doc, item.text, fontSize, font, emojiAvailable, options); if (item.color) doc.fillColor('black'); if (item.moveDown !== undefined) doc.moveDown(item.moveDown); break; } case 'rect': { doc.rect(item.x, item.y, item.width, item.height); if (item.fillColor && item.strokeColor) { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc.fillAndStroke(item.fillColor, item.strokeColor); } else if (item.fillColor) { doc.fill(item.fillColor); } else if (item.strokeColor) { if (item.lineWidth) doc.lineWidth(item.lineWidth); doc.stroke(item.strokeColor); } doc.fillColor('black'); break; } case 'pageBreak': { doc.addPage(); break; } } } doc.end(); await new Promise<void>((resolve, reject) => { stream.on('finish', () => resolve()); stream.on('error', reject); }); } async function compareApproaches() { console.log('\n🔬 COMPARISON: Algorithm vs Tool API\n'); console.log('Generating Space Resume using both approaches...\n'); const algoPath = await generateWithAlgorithm(); const toolPath = await generateWithToolAPI(); console.log('\n📊 COMPARISON RESULTS:\n'); console.log('ALGORITHM approach:'); console.log(' • Custom code with imperative loops'); console.log(' • Calculates and renders in one pass'); console.log(' • Direct PDFKit API access'); console.log(` • File: ${algoPath}`); console.log(''); console.log('TOOL API approach:'); console.log(' • Agent calculates all values first'); console.log(' • Builds declarative content array'); console.log(' • Calls tool once with full structure'); console.log(` • File: ${toolPath}`); console.log(''); console.log(`📁 Both files generated in: ${outputDir}`); console.log(' Open both PDFs to visually compare!\n'); console.log('Expected: Should be IDENTICAL in appearance\n'); } compareApproaches().catch(console.error);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mcp-z/mcp-pdf'

If you have feedback or need assistance with the MCP directory API, please join our Discord server