1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
module Main where
import System.Environment (getArgs)
import qualified Data.Map as M
import qualified Data.Set as S
import Data.Maybe (fromJust, fromMaybe)
import qualified Data.Text as Txt
import qualified Data.ByteString as BS
import System.Directory (getCurrentDirectory)
import qualified System.Directory as Dir
import Graphics.Layout.CSS (CSSBox(..), finalizeCSS')
import Graphics.Layout.CSS.Font (placeholderFont)
import Graphics.Layout (LayoutItem, boxLayout, glyphsPerFont,
layoutGetBox, layoutGetChilds, layoutGetInner)
import Graphics.Layout.Box (zeroBox)
import qualified Graphics.Layout.Box as B
import Network.URI.Fetch.XML (Page(..), fetchDocument, applyCSScharset)
import Network.URI.Fetch (newSession, fetchURL)
import Network.URI.Charset (charsets)
import Network.URI (URI(..), nullURI, parseURIReference)
import Data.FileEmbed (makeRelativeToProject, embedStringFile)
import Data.HTML2CSS (el2stylist)
import Text.XML as X (Document(..), Element(..), Node(..), Prologue(..))
import Stylist.Tree (StyleTree(..), preorder, treeMap)
import Stylist (PropertyParser(..), cssPriorityAgent, cssPriorityUser)
import qualified Data.CSS.Style as Style
import qualified Data.CSS.Syntax.StyleSheet as CSS
import qualified Data.CSS.Preprocessor.Text as CSSTxt
import Data.CSS.Preprocessor.Conditions as CSSCond
(ConditionalStyles, conditionalStyles, loadImports, Datum(..), resolve)
import qualified Data.CSS.Preprocessor.PsuedoClasses as CSSPseudo
import Control.Concurrent.MVar (putMVar, newEmptyMVar, tryReadMVar)
import Control.Concurrent (forkIO)
import Control.DeepSeq (NFData(..), ($!!))
import SDL hiding (rotate)
import Foreign.C.Types (CInt)
import Data.Function (fix)
import Control.Monad (unless)
import Control.Exception (evaluate)
import qualified Graphics.Text.Font.Choose as FC
initReferer :: IO (Page (CSSCond.ConditionalStyles (CSSBox Nil)))
initReferer = do
cwd <- getCurrentDirectory
return $ Page {
-- Default to URIs being relative to CWD.
pageURL = URI {uriScheme = "file:", uriPath = cwd,
uriAuthority = Nothing, uriQuery = "", uriFragment = ""},
-- Blank values:
css = conditionalStyles nullURI "temp",
domain = "temp",
html = Document {
documentPrologue = Prologue [] Nothing [],
documentRoot = Element "temp" M.empty [],
documentEpilogue = []
},
pageTitle = "", pageMIME = "", apps = [],
backStack = [], forwardStack = [], visitedURLs = S.empty,
initCSS = conditionalStyles,
appName = "cattrap"
}
stylize' style = preorder inner
where
inner parent _ el = Style.cascade style el [] $
Style.inherit $ fromMaybe Style.temp parent
resolveCSS manager page = do
let agentStyle = cssPriorityAgent (css page) `CSS.parse`
$(makeRelativeToProject "app/useragent.css" >>= embedStringFile)
userStyle <- loadUserStyles agentStyle
CSSCond.loadImports loadURL lowerVars lowerToks userStyle []
where
loadURL url = do
response <- fetchURL manager ["text/css"] url
let charsets' = map Txt.unpack charsets
return $ case response of
("text/css", Left text) -> text
("text/css", Right bytes) -> applyCSScharset charsets' $ BS.toStrict bytes
(_, _) -> ""
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 (cssPriorityUser styles) files
loadFiles style (file:files) = do
source <- readFile file
CSS.parse style (Txt.pack source) `loadFiles` files
loadFiles style [] = return style
-- FIXME: Support more media queries!
resolve' = CSSCond.resolve lowerVars lowerToks
lowerVars _ = CSSCond.B False
lowerToks _ = CSSCond.B False
main :: IO ()
main = do
FC.init
SDL.initializeAll
let wcfg = defaultWindow {
windowInitialSize = V2 1280 480,
-- Simplify moving layout/download out-of-thread
windowResizable = False
}
w <- createWindow "CatTrap" wcfg
renderer <- createRenderer w (-1) defaultRenderer
args <- getArgs
let url = case args of
(url:_) -> url
[] -> "https://git.argonaut-constellation.org/~alcinnz/CatTrap"
sess <- newSession
ref <- initReferer
xml <- fetchDocument sess ref $ fromMaybe nullURI $ parseURIReference url
let pseudoFilter = CSSPseudo.htmlPsuedoFilter Style.queryableStyleSheet
css' <- resolveCSS sess xml
let css = CSSPseudo.inner $ resolve' pseudoFilter css'
let styles = CSSTxt.resolve $ treeMap Style.innerParser $
stylize' css $ el2stylist $ X.documentRoot $ html xml
let layout = finalizeCSS' placeholderFont styles
V2 x y <- get $ windowSize w
pages' <- forkCompute $ boxLayout zeroBox {
B.size = B.Size (fromIntegral x) (fromIntegral y)
} layout False
fix $ \loop -> do
events <- fmap eventPayload <$> pollEvents
rendererDrawColor renderer $= V4 255 255 255 255
clear renderer
pages <- tryReadMVar pages'
case pages of
Just (display:_) -> do
evaluate $ glyphsPerFont display
renderDisplay renderer display
_ -> return ()
present renderer
unless (QuitEvent `elem` events) loop
SDL.quit
-- FC.fini -- FIXME: Need to free all Haskell data before freeing FontConfig's
data Nil = Nil deriving Eq
instance PropertyParser Nil where
temp = Nil
inherit _ = Nil
longhand _ _ _ _ = Nothing
instance NFData Nil where rnf Nil = ()
renderDisplay :: Renderer -> LayoutItem Double Double ((Double, Double), Nil)
-> IO ()
renderDisplay renderer display = do
let ((x, y), _) = layoutGetInner display
let box = layoutGetBox display
rendererDrawColor renderer $= V4 255 0 0 255
drawBox renderer x y (B.width box) (B.height box)
rendererDrawColor renderer $= V4 0 255 0 255
drawBox renderer
(x + B.left (B.margin box)) (y + B.top (B.margin box))
(B.width box - B.left (B.margin box) - B.right (B.margin box))
(B.height box - B.top (B.margin box) - B.bottom (B.margin box))
rendererDrawColor renderer $= V4 0 0 255 255
drawBox renderer
(x + B.left (B.margin box) + B.left (B.border box))
(y + B.top (B.margin box) + B.top (B.border box))
(B.inline (B.size box) + B.left (B.padding box) + B.right (B.padding box))
(B.block (B.size box) + B.top (B.padding box) + B.bottom (B.padding box))
rendererDrawColor renderer $= V4 255 255 0 255
drawBox renderer
(x + B.left (B.margin box) + B.left (B.border box) + B.left (B.padding box))
(y + B.top (B.margin box) + B.top (B.border box) + B.top (B.padding box))
(B.inline $ B.size box) (B.block $ B.size box)
mapM (renderDisplay renderer) $ layoutGetChilds display
return ()
drawBox :: Renderer -> Double -> Double -> Double -> Double -> IO ()
drawBox renderer x y width height = do
fillRect renderer $ Just $ Rectangle
(P $ V2 (c x) (c y)) (V2 (c width) (c height))
c :: (Enum a, Enum b) => a -> b
c = toEnum . fromEnum
forkCompute dat = do
ret <- newEmptyMVar
forkIO $ putMVar ret $!! dat
return ret