Fix LaTeX PDF generation by adding comprehensive package support

- Add fancyvrb package and Shaded environment for syntax highlighting
- Include amsmath and amssymb packages for mathematical symbols
- Switch from pdflatex to xelatex with fontspec for Unicode support
- Add complete set of syntax highlighting commands for code blocks
- Fix directory-relative Mermaid file generation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Willem van den Ende 2025-07-29 18:26:15 +02:00
parent 0bbff27b44
commit 90891d3797

View File

@ -9,7 +9,7 @@ import Text.Pandoc.PDF (makePDF)
import Text.Pandoc.Walk (walkM) import Text.Pandoc.Walk (walkM)
import Text.Pandoc.Extensions (Extension(..), enableExtension, getDefaultExtensions) import Text.Pandoc.Extensions (Extension(..), enableExtension, getDefaultExtensions)
import System.Environment (getArgs) import System.Environment (getArgs)
import System.FilePath (replaceExtension) import System.FilePath (replaceExtension, takeDirectory, (</>))
import System.Process (callProcess) import System.Process (callProcess)
import System.Directory (doesFileExist) import System.Directory (doesFileExist)
import Data.Text (Text) import Data.Text (Text)
@ -20,30 +20,24 @@ import Control.Monad (when, void)
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
-- Transform Mermaid code blocks into image embeds -- Transform Mermaid code blocks into image embeds
processMermaid :: Block -> IO Block processMermaidInDir :: FilePath -> Block -> IO Block
processMermaid block@(CodeBlock (id', classes, _) contents) processMermaidInDir sourceDir block@(CodeBlock (id', classes, _) contents)
| "mermaid" `elem` classes = do | "mermaid" `elem` classes = do
putStrLn $ "🎯 Found Mermaid block with classes: " ++ show classes
let baseName = if T.null id' then "diagram-" ++ take 6 (show (abs (hash (T.unpack contents)))) else T.unpack id' let baseName = if T.null id' then "diagram-" ++ take 6 (show (abs (hash (T.unpack contents)))) else T.unpack id'
mmdFile = baseName ++ ".mmd" mmdFile = sourceDir </> baseName ++ ".mmd"
pngFile = baseName ++ ".png" pngFile = sourceDir </> baseName ++ ".png"
putStrLn $ "📝 Writing to " ++ mmdFile ++ " and generating " ++ pngFile
writeFile mmdFile (T.unpack contents) writeFile mmdFile (T.unpack contents)
void $ callProcess "mmdc" ["-i", mmdFile, "-o", pngFile] void $ callProcess "mmdc" ["-i", mmdFile, "-o", pngFile]
putStrLn $ "✅ Generated " ++ pngFile putStrLn $ "✅ Generated " ++ pngFile
return $ Para [Image nullAttr [] (T.pack pngFile, "Mermaid diagram")] return $ Para [Image nullAttr [] (T.pack pngFile, "Mermaid diagram")]
processMermaid x = do processMermaidInDir _ x = return x
-- Debug: show what blocks we're processing
case x of
CodeBlock (_, classes, _) _ -> putStrLn $ "📄 Found code block with classes: " ++ show classes
_ -> return ()
return x
-- Walk the Pandoc AST and process blocks using walkM -- Walk the Pandoc AST and process blocks using walkM
transformDoc :: Pandoc -> IO Pandoc transformDoc :: FilePath -> Pandoc -> IO Pandoc
transformDoc = walkM processMermaid transformDoc sourceDir = walkM (processMermaidInDir sourceDir)
main :: IO () main :: IO ()
main = do main = do
@ -77,15 +71,65 @@ compileToPDF :: FilePath -> IO ()
compileToPDF path = do compileToPDF path = do
content <- TIO.readFile path content <- TIO.readFile path
let readerOptions = def { readerExtensions = getDefaultExtensions "markdown" } let readerOptions = def { readerExtensions = getDefaultExtensions "markdown" }
sourceDir = takeDirectory path
pandoc <- runIOorExplode $ readMarkdown readerOptions content pandoc <- runIOorExplode $ readMarkdown readerOptions content
transformed <- transformDoc pandoc transformed <- transformDoc sourceDir pandoc
let outputPath = replaceExtension path "pdf" let outputPath = replaceExtension path "pdf"
writerOptions = def writerOptions = def
-- First generate LaTeX with proper document structure -- Generate LaTeX and add proper header with tightlist definition
latexOutput <- runIOorExplode $ writeLaTeX writerOptions transformed latexOutput <- runIOorExplode $ writeLaTeX writerOptions transformed
let latexWithHeader = "\\documentclass{article}\n\\usepackage[utf8]{inputenc}\n\\usepackage{graphicx}\n\\usepackage{geometry}\n\\geometry{margin=1in}\n\\begin{document}\n" <> latexOutput <> "\n\\end{document}" let latexWithProperHeader = T.unlines [
result <- runIOorExplode $ makePDF "pdflatex" [] (\_ _ -> return latexWithHeader) def transformed "\\documentclass{article}",
"\\usepackage[utf8]{inputenc}",
"\\usepackage{fontspec}",
"\\usepackage{graphicx}",
"\\usepackage{geometry}",
"\\geometry{margin=1in}",
"\\usepackage{hyperref}",
"\\usepackage{enumitem}",
"\\usepackage{amsmath}",
"\\usepackage{amssymb}",
"\\usepackage{fancyvrb}",
"\\usepackage{color}",
"\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\}}",
"\\newenvironment{Shaded}{}{}",
"\\newcommand{\\AlertTok}[1]{\\textcolor[rgb]{1.00,0.00,0.00}{\\textbf{#1}}}",
"\\newcommand{\\AnnotationTok}[1]{\\textcolor[rgb]{0.38,0.63,0.69}{\\textbf{\\textit{#1}}}}",
"\\newcommand{\\AttributeTok}[1]{\\textcolor[rgb]{0.49,0.56,0.16}{#1}}",
"\\newcommand{\\BaseNTok}[1]{\\textcolor[rgb]{0.25,0.63,0.44}{#1}}",
"\\newcommand{\\BuiltInTok}[1]{#1}",
"\\newcommand{\\CharTok}[1]{\\textcolor[rgb]{0.25,0.44,0.63}{#1}}",
"\\newcommand{\\CommentTok}[1]{\\textcolor[rgb]{0.38,0.63,0.69}{\\textit{#1}}}",
"\\newcommand{\\CommentVarTok}[1]{\\textcolor[rgb]{0.38,0.63,0.69}{\\textbf{\\textit{#1}}}}",
"\\newcommand{\\ConstantTok}[1]{\\textcolor[rgb]{0.53,0.00,0.00}{#1}}",
"\\newcommand{\\ControlFlowTok}[1]{\\textcolor[rgb]{0.00,0.44,0.13}{\\textbf{#1}}}",
"\\newcommand{\\DataTypeTok}[1]{\\textcolor[rgb]{0.56,0.13,0.00}{#1}}",
"\\newcommand{\\DecValTok}[1]{\\textcolor[rgb]{0.25,0.63,0.44}{#1}}",
"\\newcommand{\\DocumentationTok}[1]{\\textcolor[rgb]{0.73,0.13,0.13}{\\textit{#1}}}",
"\\newcommand{\\ErrorTok}[1]{\\textcolor[rgb]{1.00,0.00,0.00}{\\textbf{#1}}}",
"\\newcommand{\\ExtensionTok}[1]{#1}",
"\\newcommand{\\FloatTok}[1]{\\textcolor[rgb]{0.25,0.63,0.44}{#1}}",
"\\newcommand{\\FunctionTok}[1]{\\textcolor[rgb]{0.02,0.16,0.49}{#1}}",
"\\newcommand{\\ImportTok}[1]{#1}",
"\\newcommand{\\InformationTok}[1]{\\textcolor[rgb]{0.38,0.63,0.69}{\\textbf{\\textit{#1}}}}",
"\\newcommand{\\KeywordTok}[1]{\\textcolor[rgb]{0.00,0.44,0.13}{\\textbf{#1}}}",
"\\newcommand{\\NormalTok}[1]{#1}",
"\\newcommand{\\OperatorTok}[1]{\\textcolor[rgb]{0.40,0.40,0.40}{#1}}",
"\\newcommand{\\OtherTok}[1]{\\textcolor[rgb]{0.00,0.44,0.13}{#1}}",
"\\newcommand{\\PreprocessorTok}[1]{\\textcolor[rgb]{0.74,0.48,0.00}{#1}}",
"\\newcommand{\\RegionMarkerTok}[1]{#1}",
"\\newcommand{\\SpecialCharTok}[1]{\\textcolor[rgb]{0.25,0.44,0.63}{#1}}",
"\\newcommand{\\SpecialStringTok}[1]{\\textcolor[rgb]{0.73,0.40,0.53}{#1}}",
"\\newcommand{\\StringTok}[1]{\\textcolor[rgb]{0.25,0.44,0.63}{#1}}",
"\\newcommand{\\VariableTok}[1]{\\textcolor[rgb]{0.10,0.09,0.49}{#1}}",
"\\newcommand{\\VerbatimStringTok}[1]{\\textcolor[rgb]{0.25,0.44,0.63}{#1}}",
"\\newcommand{\\WarningTok}[1]{\\textcolor[rgb]{0.38,0.63,0.69}{\\textbf{\\textit{#1}}}}",
"\\providecommand{\\tightlist}{%",
" \\setlength{\\itemsep}{0pt}\\setlength{\\parskip}{0pt}}",
"\\begin{document}"
] <> latexOutput <> "\n\\end{document}"
result <- runIOorExplode $ makePDF "xelatex" [] (\_ _ -> return latexWithProperHeader) def transformed
case result of case result of
Left err -> error $ "PDF error: " ++ show err Left err -> error $ "PDF error: " ++ show err
Right bs -> BL.writeFile outputPath bs >> putStrLn ("✅ PDF written to " ++ outputPath) Right bs -> BL.writeFile outputPath bs >> putStrLn ("✅ PDF written to " ++ outputPath)
@ -93,12 +137,10 @@ compileToPDF path = do
compileToHTML :: FilePath -> IO () compileToHTML :: FilePath -> IO ()
compileToHTML path = do compileToHTML path = do
content <- TIO.readFile path content <- TIO.readFile path
putStrLn $ "📖 Reading: " ++ path
putStrLn $ "📝 Content: " ++ T.unpack content
let readerOptions = def { readerExtensions = getDefaultExtensions "markdown" } let readerOptions = def { readerExtensions = getDefaultExtensions "markdown" }
sourceDir = takeDirectory path
pandoc <- runIOorExplode $ readMarkdown readerOptions content pandoc <- runIOorExplode $ readMarkdown readerOptions content
putStrLn $ "🔍 Parsed AST: " ++ show pandoc transformed <- transformDoc sourceDir pandoc
transformed <- transformDoc pandoc
let outputPath = replaceExtension path "html" let outputPath = replaceExtension path "html"
html <- runIOorExplode $ writeHtml5String def transformed html <- runIOorExplode $ writeHtml5String def transformed