latex-svg-pandoc-0.2: src/Image/LaTeX/Render/Pandoc.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
module Image.LaTeX.Render.Pandoc (
-- * SVG
convertFormulaSvgInline,
convertFormulaSvgBlock,
convertFormulaSvgPandoc,
-- * Separate Files
convertFormulaFilesInline,
convertFormulaFilesBlock,
convertFormulaFilesPandoc,
-- ** Name Supplies
NameSupply,
newNameSupply,
-- * Options
PandocFormulaOptions(..),
ShrinkSize,
defaultPandocFormulaOptions,
-- ** Error display functions
hideError,
displayError,
-- * Generalised versions
-- ** SVG
convertFormulaSvgInlineWith,
convertFormulaSvgBlockWith,
convertFormulaSvgPandocWith,
-- ** Files
convertFormulaFilesInlineWith,
convertFormulaFilesBlockWith,
convertFormulaFilesPandocWith,
) where
import Data.IORef (IORef, modifyIORef, newIORef, readIORef)
import Data.String (IsString (..))
import Numeric (showFFloat)
import System.FilePath ((<.>), (</>))
import Text.Pandoc.Definition (Block (..), Format (..), Inline (..), MathType (..), Pandoc, nullAttr)
import Text.Pandoc.Walk (walkM)
import qualified Data.Text as T
import Image.LaTeX.Render
-- | All options pertaining to the actual display of formulae.
data PandocFormulaOptions = PandocFormulaOptions
{ errorDisplay :: RenderError -> Inline
-- ^ How to display various errors (such as LaTeX errors). Usually this can just be @displayError@ but you may wish @hideError@
-- to avoid putting potentially secure information into the output page.
, formulaOptions :: Maybe MathType -> FormulaOptions
-- ^ LaTeX environment settings, including the preamble, for each equation type (display and inline)
}
-- | A set of sensible defaults for formula options.
defaultPandocFormulaOptions :: PandocFormulaOptions
defaultPandocFormulaOptions = PandocFormulaOptions
{ errorDisplay = displayError
, formulaOptions = fopts
}
where
fopts (Just DisplayMath) = displaymath
fopts (Just InlineMath) = math
fopts Nothing = defaultFormulaOptions
-- | Denominator for various dimensions. For high DPI displays, it can be useful to use values of 2 or 4, so that the dimensions
-- of the image are a fraction of the actual image size, and the image appears more crisp. Otherwise, a value of 1 will always
-- produce sensible, if somewhat pixelated results.
type ShrinkSize = Int
-- | Convert a formula in a pandoc document to an image, embedding the image into the HTML using Data URIs.
convertFormulaSvgInline
:: EnvironmentOptions -- ^ System environment settings
-> PandocFormulaOptions -- ^ Formula display settings
-> Inline -> IO Inline
convertFormulaSvgInline = convertFormulaSvgInlineWith . imageForFormula
--
-- | Convert all formulae in a pandoc block to images, embedding the images into the HTML using Data URIs.
convertFormulaSvgBlock
:: EnvironmentOptions -- ^ System environment settings
-> PandocFormulaOptions -- ^ Formula display settings
-> Block -> IO Block
convertFormulaSvgBlock = convertFormulaSvgBlockWith . imageForFormula
-- | Convert all formulae in a pandoc document to images, embedding the images into the HTML using Data URIs.
convertFormulaSvgPandoc
:: EnvironmentOptions -- ^ System environment settings
-> PandocFormulaOptions -- ^ Formula display settings
-> Pandoc -> IO Pandoc
convertFormulaSvgPandoc e = walkM . convertFormulaSvgBlock e
-- | A generalisation of 'convertFormulaSvgBlock' which allows the actual image rendering
-- function to be customised, so that (e.g) caching can be added or other image processing.
convertFormulaSvgBlockWith
:: (FormulaOptions -> Formula -> IO (Either RenderError SVG))
-- ^ Function that renders a formula, such as @imageForFormula defaultEnv@
-> PandocFormulaOptions -- ^ Formula display settings
-> Block -> IO Block
convertFormulaSvgBlockWith f o (RawBlock format s)
| format == Format "tex"
= do
res <- f (formulaOptions o Nothing) (toString s)
return $ Para $ singleton $ case res of
Left e -> errorDisplay o e
Right svg -> RawInline (Format "html") $ fromString $ alterForHTML svg
convertFormulaSvgBlockWith f o b = walkM (convertFormulaSvgInlineWith f o) b
-- | A generalisation of 'convertFormulaSvgInline' which allows the actual image rendering
-- function to be customised, so that (e.g) caching can be added or other image processing.
convertFormulaSvgInlineWith
:: (FormulaOptions -> Formula -> IO (Either RenderError SVG))
-- ^ Function that renders a formula, such as @imageForFormula defaultEnv@
-> PandocFormulaOptions -- ^ Formula display settings
-> Inline -> IO Inline
convertFormulaSvgInlineWith f o (Math t s) = do
res <- f (formulaOptions o (Just t)) (toString s)
return $ case res of
Left e -> errorDisplay o e
Right svg -> RawInline (Format "html") $ fromString $ alterForHTML svg
convertFormulaSvgInlineWith _ _ x = return x
-- | A generalisation of 'convertFormulaSvgPandoc' which allows the actual image rendering
-- function to be customised, so that (e.g) caching can be added or other image processing.
convertFormulaSvgPandocWith
:: (FormulaOptions -> Formula -> IO (Either RenderError SVG))
-- ^ Function that renders a formula, such as @imageForFormula defaultEnv@
-> PandocFormulaOptions -- ^ Formula display settings
-> Pandoc -> IO Pandoc
convertFormulaSvgPandocWith f = walkM . convertFormulaSvgBlockWith f
-- | If we use files for the images, we need some way of naming the image files we produce
-- A NameSupply provides us with a source of unique names via an ever-increasing integer.
-- It's important that any invocation of 'convertFormulaFiles' or 'convertFormulaFiles'
-- that shares the same image storage directory will also use the same name supply, or they
-- will overwrite each others images.
--
-- TODO: remove
type NameSupply = IORef Int
-- | Create a new name supply.
newNameSupply :: IO NameSupply
newNameSupply = newIORef 0
-- | A generalisation of 'convertFormulaFilesInline' which allows the actual image rendering
-- function to be customised, so that (e.g) caching can be added or other image processing.
convertFormulaFilesInlineWith
:: (FormulaOptions -> Formula -> IO (Either RenderError SVG))
-- ^ Function that renders a formula, such as @imageForFormula defaultEnv@
-> NameSupply -- ^ Unique file name supply. Reuse this for every invocation that shares the same image directory.
-> FilePath -- ^ Name of image directory where images will be stored
-> PandocFormulaOptions -- ^ Formula display settings
-> Inline -> IO Inline
convertFormulaFilesInlineWith f ns bn o (Math t s) = f (formulaOptions o (Just t)) (toString s) >>= \res -> case res of
Left e -> return $ errorDisplay o e
Right svg -> makeSvgFile ns bn (Just t) svg
convertFormulaFilesInlineWith _ _ _ _ x = return x
makeSvgFile :: NameSupply -> FilePath -> Maybe MathType -> SVG -> IO Inline
makeSvgFile ns bn t svg = do
let baseline = getBaseline svg
fn <- readIORef ns
modifyIORef ns (+1)
let uri = bn </> show fn <.> "svg"
writeFile uri svg
let classArg = case t of
Nothing -> ""
Just InlineMath -> " class='inline-math'"
Just DisplayMath -> " class='display-math'"
return $ RawInline (Format "html") $ fromString $
"<img src=\"" ++ uri ++ "\"" ++ classArg ++
" style=\"margin:0; vertical-align:-" ++ showFFloat (Just 6) baseline "" ++ "pt;\"/>"
-- | A generalisation of 'convertFormulaFilesBlock' which allows the actual image rendering
-- function to be customised, so that (e.g) caching can be added or other image processing.
convertFormulaFilesBlockWith
:: (FormulaOptions -> Formula -> IO (Either RenderError SVG))
-- ^ Function that renders a formula, such as @imageForFormula defaultEnv@
-> NameSupply -- ^ Unique file name supply. Reuse this for every invocation that shares the same image directory.
-> FilePath -- ^ Name of image directory where images will be stored
-> PandocFormulaOptions -- ^ Formula display settings
-> Block -> IO Block
convertFormulaFilesBlockWith f ns bn o (RawBlock format s)
| format == Format "tex"
= do
res <- f (formulaOptions o Nothing) (toString s)
case res of
Left e -> return $ Para $ singleton $ errorDisplay o e
Right svg -> fmap (Para . singleton) $ makeSvgFile ns bn Nothing svg
convertFormulaFilesBlockWith f ns bn o b = walkM (convertFormulaFilesInlineWith f ns bn o) b
-- | Convert a formula in a pandoc document to an image, storing the images in a separate directory.
convertFormulaFilesInline
:: EnvironmentOptions -- ^ System environment settings
-> NameSupply -- ^ Unique file name supply. Reuse this for every invocation that shares the same image directory.
-> FilePath -- ^ Name of image directory where images will be stored
-> PandocFormulaOptions -- ^ Formula display settings
-> Inline -> IO Inline
convertFormulaFilesInline = convertFormulaFilesInlineWith . imageForFormula
--
-- | Convert every formula in a pandoc block to an image, storing the images in a separate directory.
convertFormulaFilesBlock
:: EnvironmentOptions -- ^ System environment settings
-> NameSupply -- ^ Unique file name supply. Reuse this for every invocation that shares the same image directory.
-> FilePath -- ^ Name of image directory where images will be stored
-> PandocFormulaOptions -- ^ Formula display settings
-> Block -> IO Block
convertFormulaFilesBlock = convertFormulaFilesBlockWith . imageForFormula
-- | Convert every formula in a pandoc document to an image, storing the images in a separate directory.
convertFormulaFilesPandoc
:: EnvironmentOptions -- ^ System environment settings
-> NameSupply -- ^ Unique file name supply. Reuse this for every invocation that shares the same image directory.
-> FilePath -- ^ Name of image directory where images will be stored
-> PandocFormulaOptions -- ^ Formula display settings
-> Pandoc -> IO Pandoc
convertFormulaFilesPandoc eo ns fp = walkM . convertFormulaFilesInline eo ns fp
-- | A generalisation of 'convertFormulaFilesPandoc' which allows the actual image rendering
-- function to be customised, so that (e.g) caching can be added or other image processing.
convertFormulaFilesPandocWith
:: (FormulaOptions -> Formula -> IO (Either RenderError SVG))
-- ^ Function that renders a formula, such as @imageForFormula defaultEnv@
-> NameSupply -- ^ Unique file name supply. Reuse this for every invocation that shares the same image directory.
-> FilePath -- ^ Name of image directory where images will be stored
-> PandocFormulaOptions -- ^ Formula display settings
-> Pandoc -> IO Pandoc
convertFormulaFilesPandocWith x y a = walkM . convertFormulaFilesInlineWith x y a
-- | Render all errors simply as "Error"
hideError :: RenderError -> Inline
hideError = const $ Str blank
where
blank = "Error"
-- | Render errors nicely, in order to show any problems clearly, with all information intact.
displayError :: RenderError -> Inline
displayError (LaTeXFailure str) = pandocError [Str "LaTeX failed:", LineBreak, Code nullAttr $ fromString str]
displayError (DVISVGMFailure str) = pandocError [Str "DVIPS failed:", LineBreak, Code nullAttr $ fromString str]
displayError (IOException e) = pandocError [Str "IO Exception:", LineBreak, Code nullAttr $ fromString $ show e]
pandocError :: [Inline] -> Inline
pandocError = Strong . (Emph [Str "Error:"] :)
-------------------------------------------------------------------------------
-- compat
-------------------------------------------------------------------------------
class IsString s => ToString s where
toString :: s -> String
instance Char ~ c => ToString [c] where
toString = id
instance ToString T.Text where
toString = T.unpack
singleton :: a -> [a]
singleton = return