1 {-# LANGUAGE OverloadedLists #-}
2 {-# LANGUAGE UndecidableInstances #-}
3 {-# OPTIONS_GHC -Wno-orphans #-}
5 module Literate.Document.HTML (
6 module Literate.Document.HTML,
7 module Text.Blaze.Html5,
8 module Text.Blaze.Renderer.Utf8,
12 import Data.Char qualified as Char
13 import Data.List qualified as List
14 import Data.Map.Strict qualified as Map
15 import Data.String qualified as String
16 import Data.Text qualified as Text
17 import Data.Text.Short qualified as ShortText
18 import Data.Time.Format.ISO8601 qualified as Time
19 import Data.Time.LocalTime qualified as Time
20 import Literate.Document qualified as Doc
21 import Literate.Document.Table
22 import Literate.Document.Type hiding (classes)
23 import Literate.Prelude
24 import System.FilePath.Posix ((</>))
26 import Text.Blaze.Html5
27 import Text.Blaze.Html5.Attributes qualified as HA
28 import Text.Blaze.Internal (MarkupM (Parent))
29 import Text.Blaze.Renderer.Utf8
30 import Text.Printf qualified as Printf
32 instance IsString AttributeValue
33 instance IsString Html
35 classes :: [Class] -> (Bool, Attribute)
37 ( not $ null clsFiltered
38 , HA.class_ $ clsFiltered <&> toValue & List.intersperse " " & mconcat
41 clsFiltered = cls & List.filter (not . Text.null . Doc.unClass)
43 className :: Show a => a -> String
45 x & show & List.map \c ->
50 type CSSBlock = Map String String
52 styles :: CSSBlock -> (Bool, Attribute)
54 ( styFiltered & null & not
57 [ toValue k <> ":" <> toValue v <> ";"
58 | (k, v) <- styFiltered
62 styFiltered = kvs & Map.toList & List.filter \(_k, v) -> v & null & not
64 type CSS = Map [String] CSSBlock
65 styleCSS :: CSS -> Markup
69 $ [ mconcat [n <> " {" | n <- ns]
72 [ k <> ":" <> v <> ";"
73 | (k, v) <- kvs & Map.toList
76 <> mconcat [" }" | _n <- ns]
78 | (ns, kvs) <- m & Map.toList
87 instance ToMarkup ShortText where
88 toMarkup = ShortText.toText >>> toMarkup
89 preEscapedToMarkup = ShortText.toText >>> preEscapedToMarkup
90 instance ToCSS a => ToCSS (Maybe a) where
91 toCSS = maybe "" toCSS
92 instance ToCSS Text where
94 instance ToCSS Length where
96 LengthAbsolute x -> x & toCSS
97 LengthRelative x -> x & toCSS
98 instance ToCSS LengthAbsolute where
100 LengthAbsoluteMillimeters x -> show x <> "mm"
101 instance ToCSS LengthRelative where
103 LengthRelativeFractionalRatio x -> show x <> "fr"
104 LengthRelativeMaxContent -> "max-content"
105 LengthRelativeMinContent -> "min-content"
106 instance ToCSS PageSize where
110 PageSizeA4Plus -> "A4plus"
112 instance ToCSS PageOrientation where
114 PageOrientationPortrait -> "portrait"
115 PageOrientationLandscape -> "landscape"
116 instance ToCSS Justify where
118 JustifyCenter -> "center"
120 JustifySpaceAround -> "space-around"
121 JustifySpaceBetween -> "space-between"
122 JustifySpaceEvenly -> "space-evenly"
123 JustifyStart -> "start"
124 instance ToCSS Align where
126 AlignBaseline -> "baseline"
127 AlignBaselineFirst -> "first-baseline"
128 AlignBaselineLast -> "last-baseline"
129 AlignCenter -> "center"
131 AlignSpaceAround -> "space-around"
132 AlignSpaceBetween -> "space-between"
133 AlignSpaceEvenly -> "space-evenly"
134 AlignStart -> "start"
135 AlignStretch -> "stretch"
136 instance ToCSS FlexDirection where
138 FlexDirectionColumn -> "column"
139 FlexDirectionRow -> "row"
142 PageOrientationLandscape -> 29.7 & cm
143 PageOrientationPortrait -> 21.0 & cm
145 cssPageHeight = \case
146 PageOrientationLandscape -> 21.0 & cm
147 PageOrientationPortrait -> 29.7 & cm
149 cssPrintPage :: PageOrientation -> PageSize -> CSS
150 cssPrintPage pageOrient pageSize =
163 cssBlockObjectFitCover :: CSSBlock
164 cssBlockObjectFitCover = ["object-fit" := "cover"]
166 instance ToMarkup (FilePath, Document) where
167 toMarkup (dataPath, doc) =
171 doc & documentTitle & toMarkup
172 forM_ (doc & documentAttachments & Map.findWithDefault [] "css") \cssFile -> do
174 ! HA.rel "stylesheet"
175 ! HA.type_ "text/css"
176 ! HA.href (dataPath </> (cssFile & Text.unpack) & toValue)
178 forM_ (doc & documentPages) \pag -> do
180 instance ToMarkup Page where
185 , pag & pageSize & toCSS & Text.pack & Class
186 , pag & pageOrientation & toCSS & Text.pack & Class
191 [ pag & pageSize & toCSS
192 , pag & pageOrientation & toCSS
194 , "display" := "flex"
196 $ (!? styles ["width" := "100%"])
200 { Doc.flexDirection = Doc.FlexDirectionColumn
201 , Doc.flexGap = 0.5 & Doc.cm
202 , Doc.flexJustifyContent = Just JustifySpaceBetween
203 , Doc.flexAlignContent = Just AlignStretch
205 [ [Doc.flexItem{Doc.flexItemContent = pag & Doc.pageContent & Doc.toBlock}]
208 { Doc.flexItemContent =
210 { Doc.flexDirection = Doc.FlexDirectionRow
211 , Doc.flexGap = 0.5 & Doc.cm
212 , Doc.flexJustifyContent = Just JustifySpaceBetween
215 { Doc.flexItemContent =
216 [ ( [ pag & Doc.pageNumber & maybe [] \n -> [n & Printf.printf "%d" & Text.pack]
217 , pag & Doc.pageNumberTotal & maybe [] \n -> [n & Printf.printf "%d" & Text.pack]
221 & Text.intersperse '/'
224 , pag & Doc.pageSection & maybe [] Doc.toBlock
226 & case pag & Doc.pageSide of
227 Just Doc.PageSideLeft -> Doc.toBlock
228 Just Doc.PageSideRight -> List.reverse >>> Doc.toBlock
229 Nothing -> Doc.toBlock
239 instance ToMarkup Block where
241 BlockDiv x -> x & toMarkup
242 BlockFlex x -> x & toMarkup
243 BlockList x -> x & toMarkup
244 BlockPara x -> p $ x & toMarkup
245 BlockTable x -> x & toMarkup
246 BlockDict x -> x & toMarkup
247 Blocks xs -> xs & foldMap toMarkup
248 instance ToMarkup Inline where
250 InlineSpan x -> x & toMarkup
251 InlineCode x -> code $ x & toMarkup
252 InlineLink{inlineLinkText, inlineLinkTarget} ->
253 ( if (inlineLinkTarget & unTarget & Doc.toInline) == inlineLinkText
254 then a !? styles ["word-break" := "break-all"]
257 ! HA.href (inlineLinkTarget & unTarget & toValue)
258 $ inlineLinkText & toMarkup
259 InlineStrong x -> b $ x & toMarkup
260 InlineText x -> x & toMarkup
261 Inlines xs -> xs & foldMap toMarkup
262 instance ToMarkup FlexItem where
269 [ "justify-self" := itm & flexItemJustifySelf & toCSS
270 , "align-self" := itm & flexItemAlignSelf & toCSS
273 instance ToMarkup (Container Block) where
275 (maybe elem (\anchor -> elem ! HA.id (anchor & toValue)) (cnt & containerAnchor))
276 & (!? classes (cnt & containerClasses))
277 $ cnt & containerContent & toMarkup
279 elem = case cnt & containerSemantic of
281 Just tag -> Parent (String.fromString t) (String.fromString ("<" <> t)) (String.fromString ("</" <> t <> ">"))
283 t = tag & unSemantic & Text.unpack
284 instance ToMarkup (Container Inline) where
287 { containerAnchor = maybeId
288 , containerClasses = cls
289 , containerContent = x
291 (maybe span (\anchor -> span ! HA.id (anchor & toValue)) maybeId) !? classes cls $
293 instance ToMarkup Flex where
297 [ "display" := "flex"
298 , "flex-direction" := flx & flexDirection & toCSS
299 , "gap" := flx & flexGap & toCSS
300 , "align-content" := flx & flexAlignContent & toCSS
301 , "align-items" := flx & flexAlignItems & toCSS
302 , "justify-content" := flx & flexJustifyContent & toCSS
303 , "justify-items" := flx & flexJustifyItems & toCSS
304 , -- , "height" := "100%"
305 -- FixMe(role): is it possible to avoid that to get the flex item to stretch?
309 forM_ (flx & flexItems) toMarkup
310 instance ToMarkup List where
311 toMarkup List{listItems} =
315 [ "grid-template-columns" :=
316 [ LengthRelative LengthRelativeMaxContent
317 , LengthRelative $ 1 & fr
323 forM_ (listItems & ol1) \(itemCount, (itemKey, itemValue)) -> do
324 div !? classes ["list-body", if even itemCount then "even" else "odd"] $ do
326 !? classes ["list-key"]
327 !? styles ["justify-items" := JustifyEnd & toCSS]
331 !? classes ["list-value"]
332 !? styles ["justify-items" := JustifyStart & toCSS]
335 instance ToMarkup Table where
339 !? styles ["grid-template-columns" := tbl & tableTemplateFinal <&> toCSS & List.unwords]
341 forM_ (tbl & tableHeads) \hd -> do
342 div !? classes ["table-head"] $ do
343 forM_ (hd & tableHeadColumns) \cel -> do
344 div !? classes ["table-cell"] $ do
346 cel & tableCellContent & toMarkup
347 forM_ (tbl & tableRows & ol1) \(rowCount, row) -> do
348 div !? classes ["table-body", if even rowCount then "even" else "odd"] $ do
349 forM_ (row & tableRowColumns) \cel -> do
351 !? classes ["table-cell"]
352 !? styles ["justify-items" := cel & tableCellJustify & toCSS]
353 !? styles ["align-items" := cel & tableCellAlign & toCSS]
355 cel & tableCellContent & toMarkup
356 instance ToMarkup Dict where
361 forM_ (dic & dictEntries) \(key, val) ->
362 div !? classes ["dict-entry"] $ do
363 div !? classes ["dict-key"] $ do
367 div !? classes ["dict-value"] $ do