From c6df4e60f08a746215290c15fce72b2c738198ed Mon Sep 17 00:00:00 2001 From: Alan Su Date: Wed, 18 Mar 2026 20:53:03 -0400 Subject: [PATCH] Refactor tex and svg temporary files to use stdin --- CHANGELOG.md | 1 + app/Controllers/Graph.hs | 17 +++++------- app/Controllers/Timetable.hs | 19 +++++-------- app/Export/ImageConversion.hs | 16 +++++++++-- app/Export/LatexGenerator.hs | 9 ++++--- app/Export/PdfGenerator.hs | 50 +++++++++++++++++++---------------- 6 files changed, 61 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index daa2af344..eb4de0d68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Remove unused `getTimetableImage` function in `Export/GetImages.hs` - Refactored various backend text functions and tests to avoid `String` data in favour of `Text` when feasible - Removed unused files +- Refactored `returnPDF`, `exportTimetablePDFResponse`, and `graphImageResponse` to use stdin instead of temporary `.tex` and `.svg` files ## [0.7.2] - 2025-12-10 diff --git a/app/Controllers/Graph.hs b/app/Controllers/Graph.hs index cc2c34171..9f125d1af 100644 --- a/app/Controllers/Graph.hs +++ b/app/Controllers/Graph.hs @@ -3,12 +3,12 @@ module Controllers.Graph (graphResponse, index, getGraphJSON, graphImageResponse import Control.Monad.IO.Class (liftIO) import Data.Aeson (decode, object, (.=)) import Data.Maybe (fromMaybe) -import Export.ImageConversion (createImageFile) +import Export.ImageConversion (withImageFile) import Happstack.Server (Response, ServerPart, look, lookBS, lookText', ok, toResponse) import MasterTemplate (masterTemplate) import Scripts (graphScripts) -import System.IO (hClose) -import System.IO.Temp (withSystemTempFile) +import System.FilePath (()) +import System.IO.Temp (withSystemTempDirectory) import Text.Blaze ((!)) import qualified Text.Blaze.Html5 as H import qualified Text.Blaze.Html5.Attributes as A @@ -54,13 +54,10 @@ getGraphJSON = do graphImageResponse :: ServerPart Response graphImageResponse = do graphInfo <- look "JsonLocalStorageObj" - liftIO $ withSystemTempFile "graph.svg" $ \svgPath svgHandle -> do - withSystemTempFile "graph.png" $ \pngPath pngHandle -> do - hClose pngHandle - writeActiveGraphImage graphInfo svgHandle - hClose svgHandle - createImageFile svgPath pngPath - readImageData pngPath + liftIO $ withSystemTempDirectory "graph-image" $ \tempDir -> do + let pngPath = tempDir "graph.png" + withImageFile pngPath (writeActiveGraphImage graphInfo) + readImageData pngPath -- | Inserts SVG graph data into Texts, Shapes, and Paths tables saveGraphJSON :: ServerPart Response diff --git a/app/Controllers/Timetable.hs b/app/Controllers/Timetable.hs index 22b99efb9..47a9ce9f9 100644 --- a/app/Controllers/Timetable.hs +++ b/app/Controllers/Timetable.hs @@ -15,7 +15,7 @@ import Data.Time.Calendar.OrdinalDate (fromMondayStartWeek, mondayStartWeek) import Database.Persist.Sqlite (entityKey, entityVal, selectList, (==.)) import Database.Tables import Export.GetImages (getActiveTimetable, writeActiveGraphImage) -import Export.ImageConversion (createImageFile) +import Export.ImageConversion (withImageFile) import Export.LatexGenerator import Export.PdfGenerator import Happstack.Server @@ -23,7 +23,6 @@ import MasterTemplate import Models.Meeting (returnMeeting) import Scripts import System.FilePath (()) -import System.IO (IOMode (WriteMode), withFile) import System.IO.Temp (withSystemTempDirectory) import Text.Blaze ((!)) import qualified Text.Blaze.Html5 as H @@ -60,12 +59,8 @@ exportTimetablePDFResponse = do graphInfo <- look "JsonLocalStorageObj" liftIO $ withSystemTempDirectory "timetable-pdf" $ \tempDir -> do - let graphSvgPath = tempDir "graph.svg" - graphPngPath = tempDir "graph.png" - - withFile graphSvgPath WriteMode $ \graphSvgHandle -> - writeActiveGraphImage graphInfo graphSvgHandle - createImageFile graphSvgPath graphPngPath + let graphPngPath = tempDir "graph.png" + withImageFile graphPngPath (writeActiveGraphImage graphInfo) fallPngPath <- getActiveTimetable selectedCourses "Fall" tempDir springPngPath <- getActiveTimetable selectedCourses "Spring" tempDir pdfName <- returnPDF graphPngPath fallPngPath springPngPath tempDir @@ -81,10 +76,10 @@ returnPdfBS pdfFilename = do -- and springTimetableImg generated in tempDir. returnPDF :: String -> String -> String -> FilePath -> IO String returnPDF graphImg fallTimetableImg springTimetableImg tempDir = do - let texName = tempDir "timetable.tex" - pdfName = tempDir "timetable.pdf" - generateTex [graphImg, fallTimetableImg, springTimetableImg] texName -- generate a temporary TEX file - createPDF texName -- create PDF using TEX + let timetableName = "timetable" + pdfName = tempDir (timetableName ++ ".pdf") + texText <- generateTex [graphImg, fallTimetableImg, springTimetableImg] + createPDF texText tempDir timetableName -- create PDF using TEX return pdfName diff --git a/app/Export/ImageConversion.hs b/app/Export/ImageConversion.hs index 580b82209..80cf21122 100644 --- a/app/Export/ImageConversion.hs +++ b/app/Export/ImageConversion.hs @@ -3,10 +3,22 @@ Description : Includes functions for converting SVG files to PNG. -} module Export.ImageConversion - (createImageFile) where + (createImageFile, withImageFile) where import GHC.IO.Handle.Types -import System.Process (ProcessHandle, createProcess, shell, waitForProcess) +import System.IO (hClose) +import System.Process (ProcessHandle, StdStream (CreatePipe), createProcess, proc, shell, std_in, + waitForProcess) + +-- | Opens a new process to convert an SVG read from stdin to a PNG (outName) +withImageFile :: String -> (Handle -> IO ()) -> IO () +withImageFile outName writeAction = do + (Just hin, _, _, pid) <- createProcess (proc "magick" ["svg:-", outName]) { std_in = CreatePipe } + writeAction hin + hClose hin + putStrLn "Waiting for process..." + _ <- waitForProcess pid + putStrLn "Process Complete" -- | Opens a new process to convert an SVG (inName) to a PNG (outName) -- Note: hGetContents can be used to read Handles. Useful when trying to read from diff --git a/app/Export/LatexGenerator.hs b/app/Export/LatexGenerator.hs index 80f573e0e..73014e5b4 100644 --- a/app/Export/LatexGenerator.hs +++ b/app/Export/LatexGenerator.hs @@ -10,10 +10,11 @@ import Text.LaTeX.Packages.Fancyhdr import Text.LaTeX.Packages.Geometry import Text.LaTeX.Packages.Graphicx --- | Create a TEX file named texName that includes all of the images in --- imageNames -generateTex :: [String] -> String -> IO () -generateTex imageNames texName = execLaTeXT (buildTex imageNames) >>= renderFile texName +-- | Generates a TEX text that includes all of the images in imageNames +generateTex :: [String] -> IO Text +generateTex imageNames = do + texText <- execLaTeXT (buildTex imageNames) + return $ render texText -- | Combine the preamble and the document text into a single block of latex -- code. The document text contains code to insert all of the images in diff --git a/app/Export/PdfGenerator.hs b/app/Export/PdfGenerator.hs index e1741fcfb..79d74554d 100644 --- a/app/Export/PdfGenerator.hs +++ b/app/Export/PdfGenerator.hs @@ -5,30 +5,34 @@ module Export.PdfGenerator (createPDF) where -import Data.List.Utils (replace) +import Data.Text (Text) +import qualified Data.Text.IO as TIO import GHC.IO.Handle.Types -import System.Directory (removeFile) -import System.Process (ProcessHandle, createProcess, shell, waitForProcess) +import System.IO (hClose) +import System.Process (ProcessHandle, StdStream (CreatePipe), createProcess, proc, std_in, + waitForProcess) --- | Opens a new process to create a PDF from a TEX (texName) and deletes --- the tex file and extra files created by pdflatex -createPDF :: String -> IO () -createPDF texName = do - (_, _, _, pid) <- convertTexToPDF texName - putStrLn "Waiting for a process..." - _ <- waitForProcess pid - let auxFile = replace ".tex" ".aux" texName - logFile = replace ".tex" ".log" texName - mapM_ removeFile [auxFile, logFile, texName] - putStrLn "Process Complete" +-- | Opens a new process to create a PDF from a TEX +createPDF :: Text -> FilePath -> String -> IO () +createPDF texText outDir jobName = do + (Just hin, _, _, pid) <- convertTexToPDF outDir jobName + TIO.hPutStr hin texText + hClose hin + putStrLn "Waiting for a process..." + _ <- waitForProcess pid + putStrLn "Process Complete" -- | Create a process to use the pdflatex program to create a PDF from a TEX --- file (texName). The process is run in nonstop mode and so it will not block --- if an error occurs. The resulting PDF will have the same filename as texName. -convertTexToPDF :: String -> IO - (Maybe Handle, - Maybe Handle, - Maybe Handle, - ProcessHandle) -convertTexToPDF texName = - createProcess $ shell $ "pdflatex -interaction=nonstopmode " ++ texName +-- read from stdin. The process is run in nonstop mode and so it will not block +-- if an error occurs. The resulting PDF will have the same filename as jobName. +convertTexToPDF :: FilePath -> String -> IO + (Maybe Handle, + Maybe Handle, + Maybe Handle, + ProcessHandle) +convertTexToPDF outDir jobName = + createProcess (proc "pdflatex" + [ "-interaction=nonstopmode" + , "-output-directory=" ++ outDir + , "-jobname=" ++ jobName + ]) { std_in = CreatePipe }