Created
August 8, 2025 09:32
-
-
Save IRus/4d4299e1b401086847b75440ab4d5638 to your computer and use it in GitHub Desktop.
Using pandoc from Kotlin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class PandocConverter { | |
/** | |
* Convert HTML to PDF using Pandoc | |
* @param inputHtmlPath Path to the input HTML file | |
* @param outputPdfPath Path to the output PDF file | |
* @param options Additional Pandoc options | |
* @return ConversionResult containing success status and any output/error messages | |
*/ | |
fun convertHtmlToPdf( | |
inputHtmlPath: String, | |
outputPdfPath: String, | |
options: PandocOptions = PandocOptions(), | |
): ConversionResult { | |
// Build the command | |
val command = buildPandocCommand(inputHtmlPath, outputPdfPath, options) | |
println(command.joinToString(" ")) | |
return try { | |
// Create ProcessBuilder | |
val processBuilder = ProcessBuilder(command) | |
// Set working directory if specified | |
options.workingDirectory?.let { | |
processBuilder.directory(File(it)) | |
} | |
// Redirect error stream to output stream for easier handling | |
processBuilder.redirectErrorStream(true) | |
// Start the process | |
val process = processBuilder.start() | |
// Read the output | |
val output = process.inputStream.bufferedReader().use { it.readText() } | |
// Wait for the process to complete with timeout | |
val completed = process.waitFor(options.timeoutSeconds, TimeUnit.SECONDS) | |
if (!completed) { | |
process.destroyForcibly() | |
return ConversionResult( | |
success = false, | |
errorMessage = "Process timed out after ${options.timeoutSeconds} seconds", | |
) | |
} | |
val exitCode = process.exitValue() | |
ConversionResult( | |
success = exitCode == 0, | |
output = output, | |
errorMessage = if (exitCode != 0) "Pandoc exited with code $exitCode" else null, | |
exitCode = exitCode, | |
) | |
} catch (e: IOException) { | |
ConversionResult( | |
success = false, | |
errorMessage = "Failed to execute Pandoc: ${e.message}", | |
) | |
} catch (e: InterruptedException) { | |
ConversionResult( | |
success = false, | |
errorMessage = "Process was interrupted: ${e.message}", | |
) | |
} | |
} | |
/** | |
* Build the Pandoc command with all options | |
*/ | |
private fun buildPandocCommand( | |
inputPath: String, | |
outputPath: String, | |
options: PandocOptions, | |
): List<String> { | |
val command = mutableListOf<String>() | |
// Base command | |
command.add(options.pandocBinary) | |
// Input file | |
command.add(inputPath) | |
// Output file | |
command.add("-o") | |
command.add(outputPath) | |
// PDF engine (wkhtmltopdf, weasyprint, prince, etc.) | |
options.pdfEngine?.let { | |
command.add("--pdf-engine=$it") | |
} | |
// CSS file for styling | |
options.cssFile?.let { | |
command.add("--css=$it") | |
} | |
// Page size | |
options.pageSize?.let { | |
command.add("-V") | |
command.add("papersize=$it") | |
} | |
// Margins | |
options.margins?.let { margins -> | |
margins.top?.let { | |
command.add("-V") | |
command.add("margin-top=$it") | |
} | |
margins.bottom?.let { | |
command.add("-V") | |
command.add("margin-bottom=$it") | |
} | |
margins.left?.let { | |
command.add("-V") | |
command.add("margin-left=$it") | |
} | |
margins.right?.let { | |
command.add("-V") | |
command.add("margin-right=$it") | |
} | |
} | |
// Table of contents | |
if (options.tableOfContents) { | |
command.add("--toc") | |
} | |
// Standalone document | |
if (options.standalone) { | |
command.add("--standalone") | |
} | |
// Additional custom options | |
options.customOptions.forEach { command.add(it) } | |
return command | |
} | |
} | |
/** | |
* Data class for Pandoc options | |
*/ | |
data class PandocOptions( | |
val pandocBinary: String = "/opt/homebrew/bin/pandoc", | |
val pdfEngine: String? = "/Library/TeX/texbin/pdflatex", | |
val cssFile: String? = null, | |
val pageSize: String? = "a4", | |
val margins: Margins? = Margins(), | |
val tableOfContents: Boolean = false, | |
val standalone: Boolean = true, | |
val workingDirectory: String? = null, | |
val timeoutSeconds: Long = 60, | |
val customOptions: List<String> = emptyList(), | |
) | |
/** | |
* Data class for page margins | |
*/ | |
data class Margins( | |
val top: String? = "2cm", | |
val bottom: String? = "2cm", | |
val left: String? = "2cm", | |
val right: String? = "2cm", | |
) | |
/** | |
* Data class for conversion result | |
*/ | |
data class ConversionResult( | |
val success: Boolean, | |
val output: String = "", | |
val errorMessage: String? = null, | |
val exitCode: Int? = null, | |
) | |
/** | |
* Data class for batch conversion task | |
*/ | |
data class ConversionTask( | |
val inputPath: String, | |
val outputPath: String, | |
val options: PandocOptions = PandocOptions(), | |
) | |
/** | |
* Example usage and utility functions | |
*/ | |
fun main() { | |
val converter = PandocConverter() | |
// Example 1: Simple conversion with default options | |
val result1 = converter.convertHtmlToPdf( | |
inputHtmlPath = "/Users/yoda/Downloads/index.html", | |
outputPdfPath = "/Users/yoda/Downloads/index-output-1.pdf", | |
) | |
if (result1.success) { | |
println("Conversion successful!") | |
} else { | |
println("Conversion failed: ${result1.output}") | |
} | |
// Example 2: Conversion with custom options | |
val customOptions = PandocOptions( | |
cssFile = "/app/styles/custom.css", | |
pageSize = "letter", | |
margins = Margins( | |
top = "1in", | |
bottom = "1in", | |
left = "1.25in", | |
right = "1.25in", | |
), | |
tableOfContents = true, | |
timeoutSeconds = 120, | |
) | |
val result2 = converter.convertHtmlToPdf( | |
inputHtmlPath = "/Users/yoda/Downloads/index.html", | |
outputPdfPath = "/Users/yoda/Downloads/index-output-2.pdf", | |
options = customOptions, | |
) | |
if (result2.success) { | |
println("Conversion successful!") | |
} else { | |
println("Conversion failed: ${result2.output}") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment