extract-figure.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /**
  2. * extract-figure.ts
  3. * Extract a specific page from a PDF and save as PNG image.
  4. *
  5. * Usage:
  6. * npx -y bun extract-figure.ts --pdf paper.pdf --page 4 --output figure.png
  7. *
  8. * Options:
  9. * --pdf Path to source PDF file (required)
  10. * --page Page number to extract, 1-indexed (required)
  11. * --output Output PNG file path (required)
  12. * --scale Render scale factor, default 2.0 for high quality (optional)
  13. */
  14. import { existsSync, writeFileSync, mkdirSync } from "fs";
  15. import { dirname, resolve } from "path";
  16. // Use legacy build for Node.js compatibility
  17. import * as pdfjsLib from "pdfjs-dist/legacy/build/pdf.mjs";
  18. import { createCanvas } from "canvas";
  19. interface Args {
  20. pdf: string;
  21. page: number;
  22. output: string;
  23. scale: number;
  24. }
  25. function parseArgs(): Args {
  26. const args = process.argv.slice(2);
  27. let pdf = "";
  28. let page = 0;
  29. let output = "";
  30. let scale = 2.0;
  31. for (let i = 0; i < args.length; i++) {
  32. switch (args[i]) {
  33. case "--pdf":
  34. pdf = args[++i];
  35. break;
  36. case "--page":
  37. page = parseInt(args[++i], 10);
  38. break;
  39. case "--output":
  40. output = args[++i];
  41. break;
  42. case "--scale":
  43. scale = parseFloat(args[++i]);
  44. break;
  45. }
  46. }
  47. if (!pdf || !page || !output) {
  48. console.error("Usage: bun extract-figure.ts --pdf <pdf-path> --page <number> --output <output.png>");
  49. console.error("\nOptions:");
  50. console.error(" --pdf Path to source PDF file (required)");
  51. console.error(" --page Page number to extract, 1-indexed (required)");
  52. console.error(" --output Output PNG file path (required)");
  53. console.error(" --scale Render scale factor, default 2.0 (optional)");
  54. process.exit(1);
  55. }
  56. return { pdf, page, output, scale };
  57. }
  58. async function extractPage(pdfPath: string, pageNum: number, outputPath: string, scale: number) {
  59. // Validate PDF exists
  60. const absolutePdfPath = resolve(pdfPath);
  61. if (!existsSync(absolutePdfPath)) {
  62. throw new Error(`PDF file not found: ${absolutePdfPath}`);
  63. }
  64. console.log(`Loading PDF: ${absolutePdfPath}`);
  65. // Load the PDF document
  66. const loadingTask = pdfjsLib.getDocument({
  67. url: absolutePdfPath,
  68. useSystemFonts: true,
  69. standardFontDataUrl: "node_modules/pdfjs-dist/standard_fonts/",
  70. });
  71. const pdfDoc = await loadingTask.promise;
  72. const totalPages = pdfDoc.numPages;
  73. console.log(`PDF loaded: ${totalPages} pages`);
  74. // Validate page number
  75. if (pageNum < 1 || pageNum > totalPages) {
  76. throw new Error(`Page ${pageNum} out of range (1-${totalPages})`);
  77. }
  78. // Get the page
  79. const page = await pdfDoc.getPage(pageNum);
  80. const viewport = page.getViewport({ scale });
  81. console.log(`Page ${pageNum}: ${Math.round(viewport.width)}x${Math.round(viewport.height)} px (scale: ${scale})`);
  82. // Create canvas
  83. const canvas = createCanvas(viewport.width, viewport.height);
  84. const ctx = canvas.getContext("2d");
  85. // Fill white background
  86. ctx.fillStyle = "#FFFFFF";
  87. ctx.fillRect(0, 0, viewport.width, viewport.height);
  88. // Render page to canvas
  89. const renderContext = {
  90. canvasContext: ctx as any,
  91. viewport: viewport,
  92. };
  93. console.log("Rendering page...");
  94. await page.render(renderContext).promise;
  95. // Ensure output directory exists
  96. const outputDir = dirname(outputPath);
  97. if (outputDir && !existsSync(outputDir)) {
  98. mkdirSync(outputDir, { recursive: true });
  99. }
  100. // Save as PNG
  101. const buffer = canvas.toBuffer("image/png");
  102. writeFileSync(outputPath, buffer);
  103. console.log(`\nSaved: ${outputPath}`);
  104. console.log(`Size: ${Math.round(buffer.length / 1024)} KB`);
  105. }
  106. async function main() {
  107. const { pdf, page, output, scale } = parseArgs();
  108. try {
  109. await extractPage(pdf, page, output, scale);
  110. console.log("\nExtraction complete!");
  111. } catch (error) {
  112. console.error("\nError:", error instanceof Error ? error.message : error);
  113. process.exit(1);
  114. }
  115. }
  116. main();