1 {-# LANGUAGE OverloadedLists #-}
3 module Rosetta.Reading where
5 import Data.ByteString.Builder (Builder)
7 -- import Data.Char qualified as Char
8 import Data.List qualified as List
9 import Data.Map.Strict qualified as Map
11 -- import Data.Set qualified as Set
12 import Data.Text qualified as Text
14 -- import Data.Text.Short qualified as ShortText
15 import System.FilePath.Posix ((</>))
16 import System.FilePath.Posix qualified as File
18 import Text.Blaze.Html5 qualified as HTML
19 import Text.Blaze.Html5.Attributes qualified as HA
20 import Text.Blaze.Renderer.Utf8 qualified as Blaze
21 import Prelude (div, even)
24 import Language.Chinese (ChineseDict (..), ChineseDictEntries (..))
25 import Language.Chinese qualified
26 import Language.Chinese qualified as Chinese
27 import Language.English qualified as English
28 import Language.French qualified as French
29 import Language.Pronunciation as Pron
30 import Paths_worksheets qualified as Self
31 import Worksheets.Utils.Char as Char
32 import Worksheets.Utils.HTML (Length, className, classes, cm, fr, styles)
33 import Worksheets.Utils.HTML qualified as HTML
34 import Worksheets.Utils.IPA qualified as IPA
35 import Worksheets.Utils.Paper
36 import Worksheets.Utils.Prelude
38 type Difficulties = Map Langue (Map UnicodeBlock Difficulty)
39 data Difficulty = Difficulty
40 { difficultyCharWidth :: Length
41 , difficultyCharHeight :: Length
42 , difficultyWordSpacing :: Length
43 , difficultyFontSize :: Length
44 , difficultyColor :: Text
46 deriving (Eq, Show, Generic)
47 instance HasTypeDefault Difficulty where
50 { difficultyCharWidth = 1 & cm
51 , difficultyCharHeight = 1 & cm
52 , difficultyWordSpacing = 0.5 & cm
53 , difficultyFontSize = 1 & cm
54 , difficultyColor = "#000000"
57 difficultyBig :: Char.UnicodeBlock -> Modifier Difficulty
58 difficultyBig ub v = case ub of
59 Char.UnicodeBlockLatin{} ->
61 { difficultyCharWidth = 1 & cm
62 , difficultyCharHeight = 1 & cm
63 , difficultyWordSpacing = 0.5 & cm
64 , difficultyFontSize = 0.90 & cm
66 Char.UnicodeBlockCJK{} ->
68 { difficultyCharWidth = 1.50 & cm
69 , difficultyCharHeight = 1.50 & cm
70 , difficultyWordSpacing = 0.5 & cm
71 , difficultyFontSize = 1.40 & cm
73 Char.UnicodeBlockHalfwidth_and_Fullwidth_Forms{} ->
75 { difficultyCharWidth = 1.50 & cm
76 , difficultyCharHeight = 1.50 & cm
77 , difficultyWordSpacing = 0.5 & cm
78 , difficultyFontSize = 1.40 & cm
83 { partText :: InputLexemes
84 , partLangue :: Langue
86 deriving (Eq, Show, Generic)
87 deriving (HasTypeDefault) via (Generically Part)
91 , cardDescription :: Text
92 , cardPicture :: File.FilePath
93 , cardPictureCSS :: HTML.CSSBlock
95 deriving (Eq, Show, Generic)
97 instance HasTypeDefault Card where
101 , cardDescription = ""
102 , cardPictureCSS = mempty
107 { pageCards :: [Card]
108 , pageCardsColumns :: Int -- = 8
109 , pageCardsRows :: Int -- = 8
110 , pageSize :: PageSize
111 , pageCardWidth :: Length
112 , pageCardHeight :: Length
113 , pageOrientation :: PageOrientation
115 deriving (Eq, Show, Generic)
116 instance HasTypeDefault Page where
120 , pageCardsColumns = 4
122 , pageSize = typeDefault
123 , pageCardWidth = 6.3 & cm
124 , pageCardHeight = 8.8 & cm
125 , pageOrientation = typeDefault
129 { pagesList :: [Page]
130 , pagesDifficulties :: Difficulties
132 deriving (Eq, Show, Generic)
133 instance HasTypeDefault Pages where
137 , pagesDifficulties =
139 [ block := typeDefault
140 | block <- Char.unicodeBlocks & toList
143 | lang <- langues & toList
149 { dictsChinese :: Chinese.ChineseDict
150 , dictsFrench :: Pron.Table
151 , dictsEnglish :: Pron.Table
154 pagesHTML :: Dicts -> Text -> Pages -> IO Builder
155 pagesHTML Dicts{..} title Pages{..} = do
156 -- FIXME: this absolute path is not portable out of my system
157 dataPath <- Self.getDataDir <&> File.normalise
158 return $ Blaze.renderMarkupBuilder do
161 HTML.title $ title & HTML.toHtml
163 ( [ "styles/Paper.css"
164 , "styles/Rosetta/Common.css"
165 , "styles/Rosetta/Reading.css"
171 ! HA.rel "stylesheet"
172 ! HA.type_ "text/css"
173 ! HA.href (dataPath </> cssFile & toValue)
174 -- styleCSS $ cssPrintPage pageOrientation pageSize
175 HTML.styleCSS $ pagesDifficulties & difficultyCSS
177 forM_ pagesList \Page{..} -> do
179 let pageCardsNum = pageCardsColumns * pageCardsRows
180 forM_ (pageCards & chunksOf pageCardsNum) \cardsGiven -> do
181 let cards = cardsGiven & (<> List.repeat typeDefault) & List.take pageCardsNum
182 let forPageCards cardType cardsForPage k = do
188 , pageOrientation & HTML.cssPageOrientation
193 [ pageSize & HTML.cssPageSize
194 , pageOrientation & HTML.cssPageOrientation
202 , "cards-" <> cardType
205 [ "grid-template-columns" := "repeat(" <> show pageCardsColumns <> "," <> HTML.toCSS pageCardWidth <> ")"
206 , "grid-template-rows" := "repeat(" <> show pageCardsRows <> "," <> HTML.toCSS pageCardHeight <> ")"
209 forM_ cardsForPage \card_ -> do
214 , "card-" <> cardType
218 -- ExplanationNote: short-edge binding, enable recto and verso to have the same bottom and top margins
219 let organize = case pageOrientation :: PageOrientation of
220 PageOrientationPortrait -> List.concatMap List.reverse
221 PageOrientationLandscape -> List.concatMap List.reverse
222 forPageCards "front" (cards & chunksOf pageCardsColumns & organize) \Card{..} -> do
223 forM_ cardParts \Part{..} -> do
228 , "lang-" <> className wordLangue
229 -- , "script-" <> className uniScript
232 let classEven i = if even i then "part-even" else "part-odd"
233 HTML.div ! classes ["word-field", "word-parts"] $ do
234 (\f -> foldM_ f (0 :: Int) wordParts) \i Part{..} -> do
235 HTML.span ! classes ["word-part", classEven i, "word-part-" <> show partSyllabs] $ do
236 forM_ (partText & ShortText.unpack) \char -> do
237 HTML.span ! classes ["word-char"] $ do
240 $ if partSyllabs == 1
243 unless (wordIPABroad & null) do
244 HTML.div ! classes ["word-field", "word-ipas", "word-ipas-broad"] $ do
245 forM_ (wordIPABroad & List.zip [0 :: Int ..]) \(i, ipa) -> do
246 HTML.span ! classes ["word-ipa-broad", "word-ipa", classEven i] $ do
248 unless (wordPinyin & null) do
249 HTML.div ! classes ["word-field", "word-pinyins"] $ do
250 forM_ (wordPinyin & List.zip [0 :: Int ..]) \(i, pinyin) -> do
251 HTML.span ! classes ["word-pinyin", classEven i] $ do
258 , "lang-" <> className partLangue
261 let textToSoundsGroup :: [[Either Char Pron.Pron]] =
265 & Pron.unInputLexemes
266 & Pron.runParser dictsEnglish
267 & either errorShow id
271 & Pron.unInputLexemes
272 & Pron.runParser dictsFrench
273 & either errorShow id
277 & Pron.unInputLexemes
278 & Chinese.pronunciation dictsChinese
279 _ -> errorShow ("partLangue unsupported" :: Text, partLangue)
280 forM_ (textToSoundsGroup & Pron.addIndexes) \textToSounds -> do
281 HTML.div ! classes ["sentence-horiz"] $ do
282 forM_ textToSounds \syl@Syl{..} -> do
288 [ "grid-template-columns" :=
290 <> (sylText & Text.length & show)
294 let sylSoundsSpan = sylText & Text.length
295 let sylSoundParts = sylSound & Text.words & \l -> if null l then [""] else l
296 let sylSounsSpanRest = sylSoundsSpan - List.length sylSoundParts + 1
297 forM_ (sylSoundParts & List.zip (sylSounsSpanRest : List.repeat 1)) \(sylSoundSpan, sylSoundPart) -> do
302 , "cell-" <> case syl of
303 Pron.Syl{sylSilent = True} -> "silent"
304 Pron.Syl{sylIndex = i} -> if i & even then "even" else "odd"
305 , if sylDependsOnAfter then "cell-depends-on-after" else ""
306 , if sylDependsOnBefore then "cell-depends-on-before" else ""
307 , if sylDependsOnMeaning then "cell-depends-on-meaning" else ""
308 , if sylSplit then "cell-split" else ""
311 [ "grid-column-end" := "span " <> show sylSoundSpan
314 HTML.toHtml sylSoundPart
315 forM_ (sylText & Text.unpack) \pronChar -> do
316 let uniScript = pronChar & Char.unicodeBlock & fromMaybe (UnicodeBlockLatin UnicodeBlockLatin_Basic)
321 , "script-" <> className uniScript
324 HTML.span ! classes ["cell"] $ do
325 pronChar & HTML.toHtml
326 unless (cardDescription & Text.null) do
332 cardDescription & HTML.toHtml
333 forPageCards "back" cards \Card{..} -> do
339 unless (null cardPicture) do
341 ! styles cardPictureCSS
342 ! HA.title (cardPicture & toValue)
343 ! HA.src ("file://" <> dataPath </> "images" </> cardPicture & toValue)
347 { pageOrientation = PageOrientationPortrait
351 { pageOrientation = PageOrientationLandscape
354 difficultyCSS :: Difficulties -> HTML.CSS
355 difficultyCSS diffs =
356 [ [ [ [".lang-" <> show lang <> " " <> ".script-" <> className uniScript] :=
357 [ "width" := difficultyCharWidth & HTML.toCSS
358 , "height" := difficultyCharHeight & HTML.toCSS
359 , "font-size" := difficultyFontSize & HTML.toCSS
360 , "color" := difficultyColor & HTML.toCSS
362 , [ ".lang-" <> show lang <> ".sentence"
364 := ["column-gap" := difficultyWordSpacing & HTML.toCSS]
366 | (uniScript, Difficulty{..}) <- blocks & toList
368 | (lang, blocks) <- diffs & Map.toList