Skip to content

Instantly share code, notes, and snippets.

@Hona
Created May 29, 2025 00:10
Show Gist options
  • Select an option

  • Save Hona/530a3261f142859f003d19a7b278a9a4 to your computer and use it in GitHub Desktop.

Select an option

Save Hona/530a3261f142859f003d19a7b278a9a4 to your computer and use it in GitHub Desktop.
Fix mildly corrupt PDFs by doing a PDF to IMG to PDF conversion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
// Top-level statements for the console application
if (args.Length < 2)
{
Console.Error.WriteLine("Usage: <program> <inputPdfPath> <outputPdfPath>");
return 1;
}
var inputPath = args[0];
var outputPath = args[1];
if (string.IsNullOrWhiteSpace(inputPath) || string.IsNullOrWhiteSpace(outputPath))
{
Console.Error.WriteLine("Error: Input and output paths must be provided.");
return 1;
}
if (!File.Exists(inputPath))
{
Console.Error.WriteLine($"Error: Input file not found: {inputPath}");
return 1;
}
// Optional: Define paths to your CLI tools if not in PATH
string? pdftoppmPath = @"C:\Users\LukeParker\Downloads\Release-24.08.0-0\poppler-24.08.0\Library\bin\pdftoppm.exe"; // Or full path e.g., @"C:\poppler\bin\pdftoppm.exe"
string? img2pdfPath = @"C:\Users\LukeParker\Downloads\img2pdf-v1.8-windows10-bin\img2pdf.exe"; // Or full path e.g., @"C:\Python39\Scripts\img2pdf.exe"
byte[]? outputBytes;
try
{
outputBytes = await PdfCliSanitizer.SanitizePdfViaCliToolsAsync(
inputPath,
pdftoppmPath,
img2pdfPath
);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Sanitization Error with CLI tools: {ex.Message}");
if (ex.InnerException != null)
{
Console.Error.WriteLine($" Inner Exception: {ex.InnerException.Message}");
}
Console.Error.WriteLine(ex.ToString());
return 1;
}
if (outputBytes == null || outputBytes.Length == 0)
{
Console.Error.WriteLine(
"Error: CLI sanitization resulted in null or empty output. No file written."
);
return 1;
}
try
{
await File.WriteAllBytesAsync(outputPath, outputBytes);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error writing output file {outputPath}: {ex.Message}");
return 1;
}
Console.WriteLine(
$"CLI tool sanitization complete. Output saved to: {outputPath}"
);
return 0;
public static class PdfCliSanitizer
{
private static async Task<(int ExitCode, string Output, string Error)>
RunProcessAsync(
string fileName,
string arguments,
string? workingDirectory = null
)
{
using (var process = new Process())
{
process.StartInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = workingDirectory ?? string.Empty
};
var outputBuilder = new StringBuilder();
var errorBuilder = new StringBuilder();
process.OutputDataReceived += (_, e) =>
{
if (e.Data != null)
outputBuilder.AppendLine(e.Data);
};
process.ErrorDataReceived += (_, e) =>
{
if (e.Data != null)
errorBuilder.AppendLine(e.Data);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync(); // Use async version
return (process.ExitCode, outputBuilder.ToString(), errorBuilder.ToString());
}
}
public static async Task<byte[]?> SanitizePdfViaCliToolsAsync(
string inputPdfPath,
string pdftoppmExecutable = "pdftoppm.exe",
string img2pdfExecutable = "img2pdf.exe"
)
{
string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDir);
string imageFilePrefix = Path.Combine(tempDir, "page");
try
{
// Step 1: Convert PDF to images (PNG) using pdftoppm
// -png: output PNG files
// -r 150: set DPI to 150 (adjust as needed for quality/size)
string pdftoppmArgs =
$"-png -r 300 \"{inputPdfPath}\" \"{imageFilePrefix}\"";
var pdftoppmResult =
await RunProcessAsync(pdftoppmExecutable, pdftoppmArgs, tempDir);
if (pdftoppmResult.ExitCode != 0)
{
throw new InvalidOperationException(
$"pdftoppm failed (Exit Code: {pdftoppmResult.ExitCode}). " +
$"Error: {pdftoppmResult.Error}"
);
}
var imageFiles = Directory.GetFiles(tempDir, "page-*.png")
.OrderBy(f => // Natural sort for page numbers (page-1, page-2, ..., page-10)
int.Parse(
Path.GetFileNameWithoutExtension(f).Substring("page-".Length)
)
)
.ToList();
if (!imageFiles.Any())
{
throw new InvalidOperationException(
"pdftoppm ran successfully but produced no image files."
);
}
// Step 2: Convert images back to PDF using img2pdf
// img2pdf is good at preserving image quality without re-encoding if possible.
string tempOutputPdf = Path.Combine(tempDir, "output_from_images.pdf");
string img2pdfArgs =
$"--output \"{tempOutputPdf}\" {string.Join(" ", imageFiles.Select(f => $"\"{f}\""))}";
var img2pdfResult =
await RunProcessAsync(img2pdfExecutable, img2pdfArgs, tempDir);
if (img2pdfResult.ExitCode != 0)
{
throw new InvalidOperationException(
$"img2pdf failed (Exit Code: {img2pdfResult.ExitCode}). " +
$"Error: {img2pdfResult.Error}"
);
}
if (!File.Exists(tempOutputPdf))
{
throw new InvalidOperationException(
"img2pdf ran successfully but the output PDF was not created."
);
}
return await File.ReadAllBytesAsync(tempOutputPdf);
}
finally
{
if (Directory.Exists(tempDir))
{
try
{
Directory.Delete(tempDir, true);
}
catch (Exception ex)
{
Console.Error.WriteLine(
$"Warning: Failed to delete temporary directory {tempDir}: {ex.Message}"
);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment