{-# LANGUAGE TypeFamilies #-} ----------------------------------------------------------------------------- -- | -- Module : Diagrams.Backend.PGF -- Copyright : (c) 2015 Christopher Chalmers -- License : BSD-style (see LICENSE) -- Maintainer : diagrams-discuss@googlegroups.com -- -- A full-featured PGF backend for diagrams producing PGF code -- suitable for LaTeX, ConTeXt or plain TeX consumption. -- -- To invoke the PGF backend, you have a number of options. -- -- * You can use 'renderPGF' or 'renderPGF'' to render a 'Diagram' to a -- ".tex" or ".pdf" file; -- -- * You can use the most flexible 'renderDia' function to access the -- resulting PGF code directly in memory as a bytestring 'Builder'. -- -- * Use 'Diagrams.Backend.PGF.CmdLine.mainWith' or -- 'Diagrams.Backend.PGF.CmdLine.defaultMain' to make a command line -- application. See 'Diagrams.Backend.PGF.CmdLine' for more info. -- -- The 'Surface' provides the necessary information for rendering PGF code and -- building a PDF using "texrunner". See 'Diagrams.Backend.PGF.Surface' -- for more info. -- module Diagrams.Backend.PGF ( -- * Rendering token & options PGF (..) , B -- * Rendering functions , renderPGF , renderPGF' , renderPGFSurf -- * Options -- | Options for changing how the diagram is rendered. 'Options' -- 'PGF' is an instance of 'Default': -- -- @ -- def = PGFOptions { -- _surface = 'latexSurface' -- _sizeSpec = 'absolute' -- _readable = 'True' -- _standalone = 'False' -- } -- @ -- -- You can edit the default options using lenses. , readable , sizeSpec , surface , standalone -- * Surfaces -- | These surfaces should be suitable for basic diagrams. For more -- complicated options see 'Diagrams.Backend.PGF.Surface'. , Surface , surfOnlineTex -- ** Predefined surfaces , latexSurface , contextSurface , plaintexSurface -- ** Lenses , command , arguments , preamble -- * Online TeX -- | By using 'OnlineTex', diagrams is able to query tex for sizes -- of hboxs and give them the corresponding envelopes. These can -- then be used as any other diagram with the correct size. -- -- Online diagrams use the 'Surface' to run tex in online mode and -- get feedback for hbox sizes. To run it you can use -- 'renderOnlinePGF', 'renderOnlinePGF'' or 'onlineMain' from -- 'Diagrams.Backend.PGF.CmdLine'. -- -- See -- <https://github.com/diagrams/diagrams-pgf/tree/master/examples> -- for examples. , OnlineTex , renderOnlinePGF , renderOnlinePGF' -- ** Hbox , Hbox , hboxOnline , hboxPoint , hboxSurf ) where import Data.ByteString.Builder import System.Directory hiding (readable) import System.FilePath import System.IO import System.Texrunner import System.Texrunner.Online hiding (hbox) import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB import Diagrams.Backend.PGF.Hbox import Diagrams.Backend.PGF.Render import Diagrams.Backend.PGF.Surface import Diagrams.Size import Diagrams.Prelude hiding (r2) type B = PGF -- | Render a pgf diagram and write it to the given filepath. Same as -- 'renderPGF'' but uses the default options. renderPGF :: (TypeableFloat n, Monoid' m) => FilePath -- ^ path to output -> SizeSpec V2 n -- ^ size of output -> QDiagram PGF V2 n m -- ^ 'Diagram' to render -> IO () renderPGF outFile sizeSp = renderPGFSurf outFile sizeSp def -- | Render a pgf diagram and write it to the given filepath. Same as -- 'renderPGF' but takes a 'Surface'. renderPGFSurf :: (TypeableFloat n, Monoid' m) => FilePath -- ^ path to output -> SizeSpec V2 n -- ^ size of output -> Surface -- ^ surface to render with -> QDiagram PGF V2 n m -- ^ diagram to render -> IO () renderPGFSurf outFile sizeSp surf = renderPGF' outFile $ def & sizeSpec .~ sizeSp & surface .~ surf -- | Render a pgf diagram and write it to the given filepath. If the file has -- the extension @.pdf@, a PDF is generated in a temporary directory using -- options from the given surface, otherwise, the tex output is saved -- using the surface's 'TexFormat'. renderPGF' :: (TypeableFloat n, Monoid' m) => FilePath -> Options PGF V2 n -> QDiagram PGF V2 n m -> IO () renderPGF' outFile opts d = case takeExtension outFile of ".pdf" -> do let rendered = renderDia PGF (opts & standalone .~ True & readable .~ False) d currentDir <- getCurrentDirectory targetDir <- canonicalizePath (takeDirectory outFile) let source = toLazyByteString rendered (_, texLog, mPDF) <- runTex (opts^.surface.command) (opts^.surface.arguments) [currentDir, targetDir] source case mPDF of Nothing -> putStrLn "Error, no PDF found:" >> B.putStrLn (prettyPrintLog texLog) Just pdf -> LB.writeFile outFile pdf -- tex output _ -> writeTexFile outFile opts d -- | Render an online 'PGF' diagram and save it. Same as -- 'renderOnlinePGF'' using default options. renderOnlinePGF :: (TypeableFloat n, Monoid' m) => FilePath -> SizeSpec V2 n -> OnlineTex (QDiagram PGF V2 n m) -> IO () renderOnlinePGF outFile sizeSp = renderOnlinePGF' outFile (def & sizeSpec .~ sizeSp) -- | Same as 'renderOnlinePDF' but takes 'Options' 'PGF'. renderOnlinePGF' :: (TypeableFloat n, Monoid' m) => FilePath -> Options PGF V2 n -> OnlineTex (QDiagram PGF V2 n m) -> IO () renderOnlinePGF' outFile opts dOL = case takeExtension outFile of ".pdf" -> do ((), texLog, mPDF) <- runOnlineTex' (surf^.command) (surf^.arguments) (toByteString . stringUtf8 $ surf ^. (preamble <> beginDoc)) $ do d <- dOL -- we've already output the preamble so don't do it again let opts' = opts & surface %~ set beginDoc "" . set preamble "" & readable .~ False & standalone .~ True rendered = renderDia PGF opts' d texPutStrLn $ toByteString rendered case mPDF of Nothing -> putStrLn "Error, no PDF found:" >> print texLog Just pdf -> LB.writeFile outFile pdf -- tex output _ -> surfOnlineTexIO surf dOL >>= writeTexFile outFile opts where surf = opts ^. surface toByteString = LB.toStrict . toLazyByteString -- | Write the rendered diagram to a text file, ignoring the extension. writeTexFile :: (TypeableFloat n, Monoid' m) => FilePath -> Options PGF V2 n -> QDiagram PGF V2 n m -> IO () writeTexFile outFile opts d = do h <- openFile outFile WriteMode hPutBuilder h $ renderDia PGF opts d hClose h