{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Render(retreiveStyles, renderDoc, c_renderDoc) where import qualified Data.ByteString.Lazy as B import qualified Text.XML as XML import Data.Text as Txt (pack, unpack, Text(..), intercalate) import qualified Data.Map as M import System.Directory as Dir import Data.FileEmbed import Data.Maybe (fromMaybe, maybeToList) import Debug.Trace (trace) --- External Rhapsode subcomponents import qualified Data.CSS.Syntax.StyleSheet as CSS import qualified Data.CSS.Style as Style import Data.CSS.StyleTree import qualified Data.CSS.Syntax.Tokens as CSSTok import qualified Data.CSS.Preprocessor.Conditions as CSSCond import Data.CSS.Preprocessor.Assets import qualified Data.CSS.Preprocessor.PsuedoClasses as CSSPseudo import qualified Data.CSS.Preprocessor.Text as CSSTxt import qualified Data.HTML2CSS as H2C import Network.URI import Network.URI.Fetch import Network.URI.Charset --- For CSS assets import Data.List (nub, elem) import Control.Concurrent.Async (forConcurrently) import System.IO.Temp import Control.Exception (catch) -- Internal Rhapsode Subcomponents import SpeechStyle import SSML import Input (applyCSScharset) -- C API import Types import Foreign.StablePtr import Foreign.C.String import Data.ByteString (useAsCString) renderDoc :: Style.QueryableStyleSheet (Style.VarParser (CSSTxt.TextStyle SpeechStyle)) -> XML.Element -> B.ByteString renderDoc style html = renderElLBS $ styleToSSML $ CSSTxt.resolve $ inlinePseudos $ H2C.stylizeEl style html inlinePseudos :: Style.PropertyParser s => StyleTree [(Text, Style.VarParser s)] -> StyleTree s inlinePseudos (StyleTree self childs) = StyleTree { style = fromMaybe Style.temp $ Style.innerParser <$> lookup "" self, children = pseudo "before" ++ map inlinePseudos childs ++ pseudo "after" } where pseudo n | Just style <- Style.innerParser <$> lookup n self, Just style' <- Style.longhand style style "::" [CSSTok.Ident n] = [StyleTree style' []] | Just style <- Style.innerParser <$> lookup n self = [StyleTree style []] | otherwise = [] renderElLBS el = XML.renderLBS XML.def $ XML.Document { XML.documentPrologue = XML.Prologue [] Nothing [], XML.documentRoot = el, XML.documentEpilogue = [] } retreiveStyles :: Session -> CSSCond.ConditionalStyles (CSSTxt.TextStyle SpeechStyle) -> IO (CSSCond.ConditionalStyles (CSSTxt.TextStyle SpeechStyle)) retreiveStyles manager authorStyle = do let agentStyle = H2C.cssPriorityAgent authorStyle `CSS.parse` $(embedStringFile "useragent.css") userStyle <- loadUserStyles agentStyle CSSCond.loadImports loadURL lowerVars lowerToks userStyle [] where loadURL url = do response <- fetchURL manager ["text/css"] url let charsets' = map unpack charsets return $ case response of ("text/css", Left text) -> text ("text/css", Right bytes) -> applyCSScharset charsets' $ B.toStrict bytes (_, _) -> "" resolve' :: CSS.StyleSheet s => s -> CSSCond.ConditionalStyles (CSSTxt.TextStyle SpeechStyle) -> s resolve' = CSSCond.resolve lowerVars lowerToks lowerVars "speech" = CSSCond.B True lowerVars "-rhapsode" = CSSCond.B True lowerVars _ = CSSCond.B False lowerToks _ = CSSCond.B False loadUserStyles styles = do dir <- Dir.getXdgDirectory Dir.XdgConfig "rhapsode" exists <- Dir.doesDirectoryExist dir loadDirectory dir exists where loadDirectory _ False = return styles loadDirectory dir True = do files <- Dir.listDirectory dir loadFiles (H2C.cssPriorityUser styles) files loadFiles style (file:files) = do source <- readFile file CSS.parse style (Txt.pack source) `loadFiles` files loadFiles style [] = return style targetSel "" = [CSSTok.Ident "main"] targetSel ('#':id) = [CSSTok.Hash CSSTok.HUnrestricted $ Txt.pack id] targetSel _ = [] rhapsodePseudoFilter url = CSSPseudo.addRewrite "link" "[src], [href]" $ CSSPseudo.addRewrite' "target" (targetSel $ uriFragment url) $ CSSPseudo.htmlPsuedoFilter Style.queryableStyleSheet -------- ---- Download assets -------- downloadAssets session mimes (StyleAssets _ assets) = do dir <- Dir.getXdgDirectory Dir.XdgCache "rhapsode" Dir.removeDirectoryRecursive dir `catch` ignoreError -- Clear cache. Dir.createDirectoryIfMissing True dir fetchURLs session mimes assets $ filterMIMEs mimes $ saveDownload nullURI dir where ignoreError :: IOError -> IO () ignoreError _ = return () filterMIMEs mimes cb download@(_, mime, _) | mime `elem` mimes = cb download | otherwise = return nullURI -------- ---- C API -------- foreign export ccall c_renderDoc :: StablePtr Session -> StablePtr Page -> Bool -> IO CString -- Hard to C bindings without IO c_renderDoc c_session c_page rewriteURLs = do session <- deRefStablePtr c_session page <- deRefStablePtr c_page css' <- retreiveStyles session $ css page let pseudoFilter = rhapsodePseudoFilter $ url page qCSS <- if rewriteURLs then do assets <- downloadAssets session [ "audio/vnd.wav" ] $ resolve' (StyleAssets ["cue-before", "cue-after", "cue"] []) css' let URIRewriter _ qCSS' = resolve' (URIRewriter assets pseudoFilter) css' return $ CSSPseudo.inner qCSS' else return $ CSSPseudo.inner $ resolve' pseudoFilter css' let ssml = renderDoc qCSS $ XML.documentRoot $ html page B.toStrict ssml `useAsCString` \cstr -> do str <- peekCString cstr newCString str