packages feed

latex-formulae-image (empty) → 0.1.0.0

raw patch · 4 files changed

+253/−0 lines, 4 filesdep +JuicyPixelsdep +basedep +directorysetup-changed

Dependencies added: JuicyPixels, base, directory, errors, filepath, process, temporary, transformers, transformers-compat

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Liam O'Connor (c) 2015++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Liam O'Connor nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ latex-formulae-image.cabal view
@@ -0,0 +1,52 @@+name:                latex-formulae-image+version:             0.1.0.0+synopsis:            A library for rendering LaTeX formulae as images using an actual LaTeX installation+description:         This library provides the basic infrastructure necessary to convert LaTeX formulae into images, using a real+                     LaTeX installation. This is useful in particular for showing formulae on websites, where using alternatives+                     like MathJax is not an option (e.g, when you want to use various LaTeX packages that MathJax doesn't support).+                     .+                     This library requires @latex@, @dvips@ and ImageMagick's @convert@ to be present in the system. @convert@+                     needs to understand PostScript files.+                     .+                     We also+                     use a few tricks to compute where the baseline is in the image, so these images can be effectively typeset inline+                     with other text.+                     .+                     The companion library to this, @latex-formulae-pandoc@, provides useful tools to integrate this library with pandoc,+                     when generating HTML documents.+homepage:            http://github.com/liamoc/latex-formulae#readme+license:             BSD3+license-file:        LICENSE+author:              Liam O'Connor+maintainer:          liamoc@cse.unsw.edu.au+copyright:           Liam O'Connor, 2015+category:            Image+build-type:          Simple+-- extra-source-files:+cabal-version:       >=1.10+source-repository head+  type:     git+  location: https://github.com/liamoc/latex-formulae+flag old-base+  description: if building with older base versions+  default: False++library+  hs-source-dirs:      src+  exposed-modules:     Image.LaTeX.Render+  if flag(old-base)+     build-depends: base >= 4.7 && < 4.8+                  , transformers-compat >= 0.4 && < 0.5+  else+     build-depends: base >= 4.8 && < 4.9+  build-depends:       JuicyPixels >= 3.2 && < 3.3, errors >= 2.0 && < 2.1+                     , temporary >= 1.2 && < 1.3+                     , transformers >= 0.3 && < 0.5+                     , directory >= 1.2 && < 1.3+                     , process >= 1.2 && < 1.3+                     , filepath >= 1.3 && < 1.5+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/liamoc/latex-formulae-image
+ src/Image/LaTeX/Render.hs view
@@ -0,0 +1,169 @@+{-# LANGUAGE FlexibleContexts, RecordWildCards #-}+module Image.LaTeX.Render+       ( -- * Rendering Formulas+         imageForFormula+       , Formula, Baseline+         -- * Errors+       , RenderError (..)+         -- * Options+         -- ** Environment Options+       , EnvironmentOptions (..)+       , defaultEnv+       , TempDirectoryHandling (..)+         -- ** Formula Options+       , FormulaOptions (..)+       , displaymath+       , math+       )+       where++import Codec.Picture+import Data.Maybe+import Control.Error.Util+import Data.List+import System.IO.Temp+import System.FilePath+import System.Process+import System.Directory+import Control.Monad.Trans.Except+import Control.Monad+import System.Exit+import Control.Exception+import Control.Arrow(second)+import Control.Applicative+import Data.Monoid+import Prelude++-- | This type contains all possible errors than can happen while rendering an equation.+--   It includes all IO errors that can happen as well as more specific errors.+data RenderError = ImageIsEmpty -- ^ The equation produced an empty image+                 | CannotDetectBaseline -- ^ The baseline marker could not be found+                 | LaTeXFailure String -- ^ @latex@ returned a nonzero error code+                 | DVIPSFailure String -- ^ @dvips@ returned a nonzero error code+                 | IMConvertFailure String -- ^ @convert@ returned a nonzero error code+                 | IOException IOException -- ^ An 'IOException' occurred while managing the temporary files used to convert the equation+                 | ImageReadError String -- ^ The PNG image from ImageMagick could not be read by JuicyPixels.+                 deriving (Show)+++data TempDirectoryHandling = UseSystemTempDir { nameTemplate :: String }+                           -- ^ A temporary directory with a name based on the given template will be created in the system temporary files location+                           | UseCurrentDir    { nameTemplate :: String }+                           -- ^ A temporary directory with a name based on the given template will be created in the current directory++data EnvironmentOptions+     = EnvironmentOptions { latexCommand :: String -- ^ Command to use for @latex@, default is @latex@+                          , dvipsCommand :: String -- ^ Command to use for @dvips@, default is @dvips@+                          , imageMagickCommand :: String -- ^ Command to use for ImageMagick's @convert@, default is @convert@+                          , latexArgs :: [String] -- ^ Any additional arguments for @latex@+                          , dvipsArgs :: [String] -- ^ Any additional arguments for @dvips@+                          , imageMagickArgs :: [String] -- ^ Any additional arguments for @convert@+                          , tempDir :: TempDirectoryHandling -- ^ How to handle temporary files+                          , tempFileBaseName :: String -- ^ The base name to use for the temporary files.+                          }++data FormulaOptions+     = FormulaOptions { preamble :: String -- ^ LaTeX preamble to use. Put your @\usepackage@ commands here.@ commands here.+                       , environment :: String -- ^ LaTeX environment in which the equation will be typeset, usually @math@ or @displaymath@+                       , dpi :: Int -- ^ DPI for the image to be rendered at. ~200 is good for retina displays, ~100 works OK for non-retina displays.+                       }++-- | Use the @amsmath@ package, the @displaymath@ environment, and 200dpi.+displaymath :: FormulaOptions+displaymath = FormulaOptions "\\usepackage{amsmath}" "displaymath" 200++-- | Use the @amsmath@ package, the @math@ environment, and 200dpi.+math :: FormulaOptions+math = FormulaOptions "\\usepackage{amsmath}\\usepackage{amsfonts}\\usepackage{stmaryrd}" "math" 200++-- | Sensible defaults for system environments. Works if @dvips@, @convert@, and @latex@ are recent enough and in your @$PATH@.+defaultEnv :: EnvironmentOptions+defaultEnv = EnvironmentOptions "latex" "dvips" "convert" [] [] [] (UseSystemTempDir "latex-eqn-temp") "working"++-- | A LaTeX formula, e.g @x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}@ for the quadratic formula. Do not include any @$@s to denote the environment, just+--   specify the environment in the 'FormulaOptions' instead.+type Formula = String++-- | Number of pixels from the bottom of the image to the typesetting baseline. Useful for setting your formulae inline with text.+type Baseline = Int++++-- | Convert a formula into a JuicyPixels 'DynamicImage', also detecting where the typesetting baseline of the image is.+imageForFormula :: EnvironmentOptions -> FormulaOptions -> Formula -> IO (Either RenderError (Baseline, DynamicImage))+imageForFormula (EnvironmentOptions {..}) (FormulaOptions {..}) eqn =+    bracket getCurrentDirectory setCurrentDirectory $ const $ withTemp $ \temp -> runExceptT $ do+      let doc = mconcat ["\\nonstopmode\n",+                 "\\documentclass[12pt]{article}\n",+                 "\\pagestyle{empty}\n", preamble,+                 "\\begin{document}\n",+                 "\\begin{", environment, "}\n",+                 ".",eqn,+                 "\\end{", environment, "}\n",+                 "\\end{document}\n"]+      io $ writeFile (temp </> tempFileBaseName <.> "tex") doc+      io $ setCurrentDirectory temp+      (c,o,e) <- io $ flip (readProcessWithExitCode latexCommand) "" $ latexArgs ++ [tempFileBaseName <.> "tex"]+      io $ removeFile (tempFileBaseName <.> "tex") +      io $ removeFile (tempFileBaseName <.> "aux")+      when (c /= ExitSuccess) $ do+        io $ removeFile (tempFileBaseName <.> "dvi")+        throwE $ LaTeXFailure (o ++ "\n" ++ e)+      (c',o',e') <- io $ flip (readProcessWithExitCode dvipsCommand) "" $ dvipsArgs ++ ["-q", "-E", "-o", tempFileBaseName <.> "ps", tempFileBaseName <.> "dvi"]+      io $ removeFile (tempFileBaseName <.> "dvi")+      when (c' /= ExitSuccess) $ throwE $ DVIPSFailure (o' ++ "\n" ++ e')+      (c'', o'', e'') <- io $ flip (readProcessWithExitCode imageMagickCommand) "" $+                                [ "-density", show dpi+                                , "-bordercolor", "none"+                                , "-border", "1x1"+                                , "-trim"+                                , "-background", "none"+                                , "-splice","1x0"+                                ] ++ imageMagickArgs +++                                [ tempFileBaseName <.> "ps", tempFileBaseName <.> "png" ]+      io $ removeFile (tempFileBaseName <.> "ps")+      when (c'' /= ExitSuccess) $ throwE $ IMConvertFailure (o'' ++ "\n" ++ e'')+      imgM <- io $ readImage (tempFileBaseName <.> "png")+      img <- withExceptT ImageReadError $ hoistEither imgM+      io $ removeFile $ tempFileBaseName <.> "png"+      hoistEither $ postprocess img+  where+    io = withExceptT IOException . tryIO+    withTemp a = case tempDir of+      UseSystemTempDir f -> withSystemTempDirectory f a+      UseCurrentDir f -> withTempDirectory "." f a++postprocess :: DynamicImage -> Either RenderError (Int, DynamicImage)+postprocess (ImageY8 i)     = second ImageY8     <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageY16 i)    = second ImageY16    <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageYF i)     = second ImageYF     <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageYA8 i)    = second ImageYA8    <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageYA16 i)   = second ImageYA16   <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageRGB8 i)   = second ImageRGB8   <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageRGB16 i)  = second ImageRGB16  <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageRGBF i)   = second ImageRGBF   <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageRGBA8 i)  = second ImageRGBA8  <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageRGBA16 i) = second ImageRGBA16 <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageYCbCr8 i) = second ImageYCbCr8 <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageCMYK8 i)  = second ImageCMYK8  <$> postprocess' i (pixelAt i 0 0)+postprocess (ImageCMYK16 i) = second ImageCMYK16 <$> postprocess' i (pixelAt i 0 0)+++postprocess' :: (Eq a, Pixel a) => Image a -> a -> Either RenderError (Int, Image a)+postprocess' img bg+  = do startX <- note ImageIsEmpty $ listToMaybe $ dropWhile isEmptyCol [0.. imageWidth img - 1]+       let (dotXs, postXs) = break isEmptyCol [startX .. imageWidth img]+       postX <- note CannotDetectBaseline $ listToMaybe postXs+       let postY = (+ 2) $ average $ dotXs >>= (\x -> takeWhile (not . isEmpty x) (dropWhile (isEmpty x) [0..imageHeight img - 1]))+           average = uncurry div . foldl' (\(s,c) e -> (e+s,c+1)) (0,0)+           newHeight = imageHeight img+           newWidth  = imageWidth img - postX + 3+           baseline  = imageHeight img - postY+       let image = generateImage (pixelAt' . (+ postX)) newWidth newHeight+       return (baseline, image)+  where+    isEmptyCol x = all (isEmpty x) [0.. imageHeight img - 1]+    isEmpty x = (== bg) . pixelAt img x+    pixelAt' x y | x < imageWidth img && y < imageHeight img = pixelAt img x y+                 | otherwise = bg+