packages feed

ihaskell-diagrams-0.4.0.0: IHaskell/Display/Diagrams.hs

{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
{-# LANGUAGE TypeFamilies                            #-}

module IHaskell.Display.Diagrams
         ( diagram, animation
         , ManuallySized, withSizeSpec, withImgWidth, withImgHeight
         , ManuallySampled, withAnimFps
         ) where

import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as Char
import qualified Data.Text.Encoding as T.Encoding
import           System.Directory
import           System.IO.Temp
import           System.FilePath ((</>))
import           Diagrams.Backend.Cairo
import           Diagrams.Prelude
import           IHaskell.Display
import           IHaskell.Display.Diagrams.Animation
import           IHaskell.Display.Diagrams.ImgSize

instance IHaskellDisplay (ManuallySized (QDiagram Cairo V2 Double Any)) where
  display renderable = do
    png <- diagramData renderable PNG
    svg <- diagramData renderable SVG
    return $ Display [png, svg]

diagramData :: ManuallySized (Diagram Cairo) -> OutputType -> IO DisplayData
diagramData (ManuallySized renderable imgWidth imgHeight) format = do
  -- We should not have to round-trip this ByteString to a temp file.
  -- https://github.com/IHaskell/IHaskell/issues/1248
  withSystemTempDirectory "ihaskell-diagram" $ \tmpdir -> do
    let path = case format of
          SVG -> tmpdir </> "ihaskell-diagram.svg"
          PNG -> tmpdir </> "ihaskell-diagram.png"
          _ -> error "Unreachable case"

    -- Write the image.
    renderCairo path (mkSizeSpec2D (Just imgWidth)
                                   (Just imgHeight)) renderable

    case format of
      PNG -> do
        -- Convert to base64.
        imgData <- Char.readFile path
        pure $ png (floor imgWidth) (floor imgHeight) $ base64 imgData
      SVG -> do
        imgData <- BS.readFile path
        pure $ svg (T.Encoding.decodeUtf8 imgData)
      _ -> error "Unreachable case"

-- Rendering hint.
diagram :: Diagram Cairo -> Diagram Cairo
diagram = id

instance (b ~ Cairo, v ~ V2, s ~ Double, m ~ Any)
              => ManuallySizeable (QDiagram a v s m) where
  withSizeSpec spec renderable = ManuallySized renderable imgWidth imgHeight
    where
       aspect = width renderable / height renderable
       V2 imgWidth imgHeight = case getSpec spec of
         V2 (Just w) (Just h) -> V2 w h
         V2 (Just w) Nothing  -> V2 w (w/aspect)
         V2 Nothing (Just h)  -> V2 (aspect*h) h
         V2 Nothing Nothing   -> (defaultDiagonal / sqrt (1 + aspect^2))
                                    *^ V2 aspect 1
                                 -- w^2 + h^2 = defaultDiagonal^2 / (1+aspect^2)
                                 --                * (aspect^2 + 1)
                                 --           = defaultDiagonal^2
                                 -- w/h = aspect/1 = aspect
       defaultDiagonal = 500

instance IHaskellDisplay (QDiagram Cairo V2 Double Any) where
  display = display . withSizeSpec (mkSizeSpec2D Nothing Nothing)