module Text.BlogLiterately.Diagrams
( diagramsXF, diagramsInlineXF
) where
import Safe (readMay)
import System.Directory (createDirectoryIfMissing)
import System.FilePath
import System.IO (hPutStrLn, stderr)
import qualified Codec.Picture as J
import Diagrams.Backend.Rasterific
import qualified Diagrams.Builder as DB
import Diagrams.Prelude (SizeSpec, V2, centerXY, pad, zero,
(&), (.~))
import Diagrams.TwoD.Size (mkSizeSpec2D)
import Text.BlogLiterately
import Text.Pandoc
diagramsXF :: Transform
diagramsXF = ioTransform renderBlockDiagrams (const True)
renderBlockDiagrams :: BlogLiterately -> Pandoc -> IO Pandoc
renderBlockDiagrams _ p = bottomUpM (renderBlockDiagram defs) p
where
defs = queryWith extractDiaDef p
diagramsInlineXF :: Transform
diagramsInlineXF = ioTransform renderInlineDiagrams (const True)
renderInlineDiagrams :: BlogLiterately -> Pandoc -> IO Pandoc
renderInlineDiagrams _ p = bottomUpM (renderInlineDiagram defs) p
where
defs = queryWith extractDiaDef p
extractDiaDef :: Block -> [String]
extractDiaDef (CodeBlock (_, as, _) s)
= [src | "dia-def" `elem` (maybe id (:) tag) as]
where
(tag, src) = unTag s
extractDiaDef _ = []
diaDir :: FilePath
diaDir = "diagrams"
renderDiagram :: Bool
-> [String]
-> String
-> Attr
-> IO (Either String FilePath)
renderDiagram shouldPad decls expr (_ident, _cls, fields) = do
createDirectoryIfMissing True diaDir
let bopts = DB.mkBuildOpts Rasterific zero (RasterificOptions sz)
& DB.snippets .~ decls
& DB.imports .~ ["Diagrams.Backend.Rasterific"]
& DB.diaExpr .~ expr
& DB.postProcess .~ (if shouldPad then pad 1.1 . centerXY else id)
& DB.decideRegen .~
(DB.hashedRegenerate
(\_ opts -> opts)
diaDir
)
res <- DB.buildDiagram bopts
case res of
DB.ParseErr err -> do
let errStr = "\nParse error:\n" ++ err
putErrLn errStr
return (Left errStr)
DB.InterpErr ierr -> do
let errStr = "\nInterpreter error:\n" ++ DB.ppInterpError ierr
putErrLn errStr
return (Left errStr)
DB.Skipped hash -> return (Right $ mkFile (DB.hashToHexStr hash))
DB.OK hash img -> do
let imgFile = mkFile (DB.hashToHexStr hash)
J.savePngImage imgFile (J.ImageRGBA8 img)
return (Right imgFile)
where
sz :: SizeSpec V2 Double
sz = mkSizeSpec2D
(lookup "width" fields >>= readMay)
(lookup "height" fields >>= readMay)
mkFile base = diaDir </> base <.> "png"
renderBlockDiagram :: [String] -> Block -> IO Block
renderBlockDiagram defs c@(CodeBlock attr@(_, cls, _) s)
| "dia-def" `elem` classTags = return Null
| "dia" `elem` classTags = do
res <- renderDiagram True (src : defs) "dia" attr
case res of
Left err -> return (CodeBlock attr (s ++ err))
Right fileName -> return $ Para [Image nullAttr [] (fileName, "")]
| otherwise = return c
where
(tag, src) = unTag s
classTags = (maybe id (:) tag) cls
renderBlockDiagram _ b = return b
renderInlineDiagram :: [String] -> Inline -> IO Inline
renderInlineDiagram defs c@(Code attr@(_, cls, _) expr)
| "dia" `elem` cls = do
res <- renderDiagram False defs expr attr
case res of
Left err -> return (Code attr (expr ++ err))
Right fileName -> return $ Image nullAttr [] (fileName, "")
| otherwise = return c
renderInlineDiagram _ i = return i
putErrLn :: String -> IO ()
putErrLn = hPutStrLn stderr