{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} ------------------------------------------------------------------------------- -- | -- Module : Diagrams.Backend.Html5 -- Copyright : (c) 2015 Jeffrey Rosenbluth -- License : BSD-style (see LICENSE) -- Maintainer : diagrams-discuss@googlegroups.com -- -- A full-featured rendering backend for diagrams using HTML5 Canvas. -- Implemented using the static-canvas package. -- -- To invoke the Html5 backend, you have three options. -- -- * You can use the "Diagrams.Backend.Html5.CmdLine" module to create -- standalone executables which will display the diagram in a browser -- using a web service. -- -- * You can use the 'renderHtml5' function provided by this module, -- which gives you more programmatic control over when and -- how images are displayed (making it east to, for example, write a -- single program that displays multiple images, or one that diaplays -- images dynamically based on user input, and so on). -- -- * For the most flexiblity you can invoke the 'renderDia' method from -- 'Diagrams.Core.Types.Backend' instance for @Html5@. In particular, -- 'Diagrams.Core.Types.renderDia' has the generic type -- -- > renderDia :: b -> Options b v -> QDiagram b v m -> Result b v -- -- (omitting a few type class contraints). @b@ represents the -- backend type, @v@ the vector space, and @m@ the type of monoidal -- query annotations on the diagram. 'Options' and 'Result' are -- associated data and type families, respectively, which yield the -- type of option records and rendering results specific to any -- particular backend. For @b ~ Html5@ and @v ~ R2@, we have -- -- > data Options Html5 V2 Double = Html5Options -- > { _size :: SizeSpec V2 -- ^^ The requested size -- > } -- -- @ -- data family Render Html5 V2 Double = C (RenderM ()) -- @ -- -- @ -- type family Result Html5 V2 Double = Html5 () -- @ -- -- So the type of 'renderDia' resolves to -- -- @ -- renderDia :: Html5 -> Options Html5 V2 Double -> QDiagram Html5 V2 Double m -> -- Html5() -- @ -- -- which you could call like @renderDia Html5 (Html5Options (width 250)) -- myDiagram@ -- ------------------------------------------------------------------------------ module Diagrams.Backend.Html5 ( Html5(..) -- rendering token , B , Options(..) -- for rendering options specific to Html5 , renderHtml5 -- * Lenses , size , canvasId , standalone ) where import Control.Monad.State (when) import qualified Control.Monad.StateStack as SS import Control.Monad.Trans (lift) import Data.Default.Class import qualified Data.Foldable as F import Data.Maybe (catMaybes, isJust, fromJust, fromMaybe) import Data.NumInstances () import qualified Data.Text as T import Data.Text.Lazy.Builder (Builder, toLazyText) import qualified Data.Text.Lazy.IO as L import Data.Tree (Tree(Node)) import Data.Typeable (Typeable) import Diagrams.Attributes import Diagrams.Prelude hiding (fillTexture, moveTo, stroke, size) import Diagrams.TwoD.Adjust (adjustDia2D) import Diagrams.TwoD.Attributes (splitTextureFills) import Diagrams.TwoD.Path (Clip (Clip)) import Diagrams.TwoD.Text import Diagrams.Core.Compile import Diagrams.Core.Transform (matrixHomRep) import Diagrams.Core.Types (Annotation (..)) import qualified Graphics.Static as H -- | This data declaration is simply used as a token to distinguish -- this rendering engine. data Html5 = Html5 deriving (Eq, Ord, Read, Show, Typeable) type B = Html5 type instance V Html5 = V2 type instance N Html5 = Double data Html5State = Html5State { _accumStyle :: Style V2 Double , _csPos :: (Double, Double) } makeLenses ''Html5State instance Default Html5State where def = Html5State { _accumStyle = mempty , _csPos = (0,0) } type RenderM a = SS.StateStackT Html5State H.CanvasFree a liftC :: H.CanvasFree a -> RenderM a liftC = lift runRenderM :: RenderM a -> H.CanvasFree a runRenderM = flip SS.evalStateStackT def instance Monoid (Render Html5 V2 Double) where mempty = C $ return () (C c1) `mappend` (C c2) = C (c1 >> c2) instance Backend Html5 V2 Double where data Render Html5 V2 Double = C (RenderM ()) type Result Html5 V2 Double = Builder data Options Html5 V2 Double = Html5Options { _html5Size :: SizeSpec V2 Double -- ^ the requested size , _standalone :: Bool , _canvasId :: String } renderRTree :: Html5 -> Options Html5 V2 Double -> RTree Html5 V2 Double Annotation -> Result Html5 V2 Double renderRTree _ opts rt = buildF (round w) (round h) . runRenderM . runC . toRender $ rt where V2 w h = specToSize 100 (opts^.size) buildF | opts^.standalone = H.buildDoc | otherwise = \wd ht -> H.buildScript' wd ht (opts^.canvasId.to T.pack) adjustDia c opts d = adjustDia2D size c opts (d # reflectY) runC :: Render Html5 V2 Double -> RenderM () runC (C r) = r toRender :: RTree Html5 V2 Double Annotation -> Render Html5 V2 Double toRender = fromRTree . Node (RStyle (mempty # recommendFillColor transparent)) . (:[]) . splitTextureFills where fromRTree (Node (RPrim p) _) = render Html5 p fromRTree (Node (RStyle sty) rs) = C $ do save html5Style sty accumStyle %= (<> sty) runC $ F.foldMap fromRTree rs restore fromRTree (Node _ rs) = F.foldMap fromRTree rs -- lenses -------------------------------------------------------------- -- | Output size. size :: Lens' (Options Html5 V2 Double) (SizeSpec V2 Double) size = lens _html5Size $ \o i -> o { _html5Size = i } -- | \"id\" for the @@ element (prepended to \"StaticCanvas\"). -- Only applies to non-'standalone' diagrams. canvasId :: Lens' (Options Html5 V2 Double) String canvasId = lens _canvasId $ \o i -> o { _canvasId = i } -- | Should the output be a standalone html file. Otherwise the output -- is a @@ element followed by a @