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.Extensions (Extension(..), enableExtension, getDefaultExtensions)
import System.Environment (getArgs)
import System.FilePath (replaceExtension)
import System.FilePath (replaceExtension, takeDirectory, (</>))
import System.Process (callProcess)
import System.Directory (doesFileExist)
import Data.Text (Text)
@ -20,30 +20,24 @@ import Control.Monad (when, void)
import qualified Data.ByteString.Lazy as BL
-- Transform Mermaid code blocks into image embeds
processMermaid :: Block -> IO Block
processMermaid block@(CodeBlock (id', classes, _) contents)
processMermaidInDir :: FilePath -> Block -> IO Block
processMermaidInDir sourceDir block@(CodeBlock (id', classes, _) contents)
| "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'
mmdFile = baseName ++ ".mmd"
pngFile = baseName ++ ".png"
mmdFile = sourceDir </> baseName ++ ".mmd"
pngFile = sourceDir </> baseName ++ ".png"
putStrLn $ "📝 Writing to " ++ mmdFile ++ " and generating " ++ pngFile
writeFile mmdFile (T.unpack contents)
void $ callProcess "mmdc" ["-i", mmdFile, "-o", pngFile]
putStrLn $ "✅ Generated " ++ pngFile
return $ Para [Image nullAttr [] (T.pack pngFile, "Mermaid diagram")]
processMermaid x = do
-- Debug: show what blocks we're processing
case x of
CodeBlock (_, classes, _) _ -> putStrLn $ "📄 Found code block with classes: " ++ show classes
_ -> return ()
return x
processMermaidInDir _ x = return x
-- Walk the Pandoc AST and process blocks using walkM
transformDoc :: Pandoc -> IO Pandoc
transformDoc = walkM processMermaid
transformDoc :: FilePath -> Pandoc -> IO Pandoc
transformDoc sourceDir = walkM (processMermaidInDir sourceDir)
main :: IO ()
main = do
@ -77,15 +71,65 @@ compileToPDF :: FilePath -> IO ()
compileToPDF path = do
content <- TIO.readFile path
let readerOptions = def { readerExtensions = getDefaultExtensions "markdown" }
sourceDir = takeDirectory path
pandoc <- runIOorExplode $ readMarkdown readerOptions content
transformed <- transformDoc pandoc
transformed <- transformDoc sourceDir pandoc
let outputPath = replaceExtension path "pdf"
writerOptions = def
-- First generate LaTeX with proper document structure
-- Generate LaTeX and add proper header with tightlist definition
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}"
result <- runIOorExplode $ makePDF "pdflatex" [] (\_ _ -> return latexWithHeader) def transformed
let latexWithProperHeader = T.unlines [
"\\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
Left err -> error $ "PDF error: " ++ show err
Right bs -> BL.writeFile outputPath bs >> putStrLn ("✅ PDF written to " ++ outputPath)
@ -93,12 +137,10 @@ compileToPDF path = do
compileToHTML :: FilePath -> IO ()
compileToHTML path = do
content <- TIO.readFile path
putStrLn $ "📖 Reading: " ++ path
putStrLn $ "📝 Content: " ++ T.unpack content
let readerOptions = def { readerExtensions = getDefaultExtensions "markdown" }
sourceDir = takeDirectory path
pandoc <- runIOorExplode $ readMarkdown readerOptions content
putStrLn $ "🔍 Parsed AST: " ++ show pandoc
transformed <- transformDoc pandoc
transformed <- transformDoc sourceDir pandoc
let outputPath = replaceExtension path "html"
html <- runIOorExplode $ writeHtml5String def transformed