1 {-# LANGUAGE OverloadedLists #-}
3 module Rosetta.Matching 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
13 import System.Random qualified as Random
14 import System.Random.Shuffle as Random
16 -- import Data.Text.Short qualified as ShortText
17 import System.FilePath.Posix ((</>))
18 import System.FilePath.Posix qualified as File
20 import Text.Blaze.Html5 qualified as HTML
21 import Text.Blaze.Html5.Attributes qualified as HA
22 import Text.Blaze.Renderer.Utf8 qualified as Blaze
23 import Prelude (div, even)
26 import Language.Chinese (ChineseDict (..), ChineseDictEntries (..))
27 import Language.Chinese qualified
28 import Language.Chinese qualified as Chinese
29 import Language.English qualified as English
30 import Language.French qualified as French
31 import Language.Pronunciation qualified as Pron
32 import Paths_worksheets qualified as Self
33 import Worksheets.Utils.Char as Char
34 import Worksheets.Utils.HTML (Length, className, classes, cm, fr, styles)
35 import Worksheets.Utils.HTML qualified as HTML
36 import Worksheets.Utils.IPA qualified as IPA
37 import Worksheets.Utils.Paper
38 import Worksheets.Utils.Prelude
40 type Difficulties = Map Langue (Map UnicodeBlock Difficulty)
41 data Difficulty = Difficulty
42 { difficultyCharWidth :: Length
43 , difficultyCharHeight :: Length
44 , difficultyWordSpacing :: Length
45 , difficultyFontSize :: Length
46 , difficultyColor :: Text
48 deriving (Eq, Show, Generic)
49 instance HasTypeDefault Difficulty where
52 { difficultyCharWidth = 1 & cm
53 , difficultyCharHeight = 1 & cm
54 , difficultyWordSpacing = 0.5 & cm
55 , difficultyFontSize = 1 & cm
56 , difficultyColor = "#000000"
59 difficultyBig :: Char.UnicodeBlock -> Modifier Difficulty
60 difficultyBig ub v = case ub of
61 Char.UnicodeBlockLatin{} ->
63 { difficultyCharWidth = 1 & cm
64 , difficultyCharHeight = 1 & cm
65 , difficultyWordSpacing = 0.5 & cm
66 , difficultyFontSize = 0.90 & cm
68 Char.UnicodeBlockCJK{} ->
70 { difficultyCharWidth = 1.50 & cm
71 , difficultyCharHeight = 1.50 & cm
72 , difficultyWordSpacing = 0.5 & cm
73 , difficultyFontSize = 1.40 & cm
75 Char.UnicodeBlockHalfwidth_and_Fullwidth_Forms{} ->
77 { difficultyCharWidth = 1.50 & cm
78 , difficultyCharHeight = 1.50 & cm
79 , difficultyWordSpacing = 0.5 & cm
80 , difficultyFontSize = 1.40 & cm
85 { matterText :: Pron.InputLexemes
86 , matterLangue :: Langue
88 deriving (Eq, Show, Generic)
89 deriving (HasTypeDefault) via (Generically Matter)
92 { matchMatters :: [Matter]
93 , matchPicture :: File.FilePath
94 , matchPictureCSS :: HTML.CSSBlock
96 deriving (Eq, Show, Generic)
98 instance HasTypeDefault Match where
102 , matchPictureCSS = mempty
107 { pageMatches :: [Match]
108 , pageMatchesNum :: Natural
109 , pageSize :: PageSize
110 , pageOrientation :: PageOrientation
112 deriving (Eq, Show, Generic)
113 instance HasTypeDefault Page where
116 { pageMatches = mempty
117 , pageMatchesNum = 10
118 , pageSize = typeDefault
119 , pageOrientation = typeDefault
123 { pagesList :: [Page]
124 , pagesDifficulties :: Difficulties
126 deriving (Eq, Show, Generic)
127 instance HasTypeDefault Pages where
131 , pagesDifficulties =
133 [ block := typeDefault
134 | block <- Char.unicodeBlocks & toList
137 | lang <- langues & toList
143 { dictsChinese :: Chinese.ChineseDict
144 , dictsFrench :: Pron.Table
145 , dictsEnglish :: Pron.Table
148 pagesHTML :: Dicts -> Text -> Pages -> IO Builder
149 pagesHTML Dicts{..} title pages = do
150 -- FIXME: this absolute path is not portable out of my system
151 dataPath <- Self.getDataDir <&> File.normalise
152 randomGen <- Random.getStdGen
153 pagesListShuffled :: [Page] <-
155 forM (pages & pagesList) \page -> do
156 forM (page & pageMatches & chunksOf (page & pageMatchesNum)) \matches -> do
157 pageMatches <- Random.shuffleM matches
158 return page{pageMatches} -- Random.shuffleM
160 -- forM_ (pageMatch & ol0) \(matchIdx :: Int, match) -> do
161 -- forM_ (match & matchMatters & ol1) \(matterIdx :: Natural, matter) -> do
163 -- let n = page & pageMatchesNum & fromIntegral
164 -- in let (q, r) = page & pageMatches & List.length & (div n)
165 -- in List.unfoldr _ (List.replicate q n <> [r])
166 return $ Blaze.renderMarkupBuilder do
169 HTML.title $ title & HTML.toHtml
171 ( [ "styles/Paper.css"
172 , "styles/Rosetta/Common.css"
173 , "styles/Rosetta/Matching.css"
179 ! HA.rel "stylesheet"
180 ! HA.type_ "text/css"
181 ! HA.href (dataPath </> cssFile & toValue)
182 -- styleCSS $ cssPrintPage pageOrientation pageSize
183 HTML.styleCSS $ pages & pagesDifficulties & difficultyCSS
185 forM_ (pages & pagesList) \page -> do
187 forM_ (page & pageMatches & chunksOf (page & pageMatchesNum)) \pageMatch -> do
193 , page & pageOrientation & HTML.cssPageOrientation
198 [ page & pageSize & HTML.cssPageSize
199 , page & pageOrientation & HTML.cssPageOrientation
204 let matchColumns = pageMatch & foldMap (matchMatters >>> List.length >>> Max) & getMax
205 let pageColumns = pageMatch & List.length
207 ! classes ["matches"]
208 ! styles ["grid-template-columns" := "1fr" <> mconcat (List.replicate (max 0 (2 * (matchColumns - 1) + 1)) " 2em 1fr 2em 1fr")]
210 let pageMatchNum = pageMatch & List.length
211 forM_ (pageMatch & ol0) \(matchIdx :: Int, match) -> do
213 [ "grid-row-start" := show (pageColumns - matchIdx + 1)
214 , "grid-row-end" := show (pageColumns - matchIdx + 1)
219 ! classes ["match-anchor"]
220 ! styles commonStyles
226 ! classes ["match-links"]
227 ! styles commonStyles
231 forM_ (match & matchMatters & ol1) \(matterIdx :: Natural, matter) -> do
232 when (matterIdx /= 1) do
234 let words :: [[Either Char Pron.Pron]] =
235 case matter & matterLangue of
239 & Pron.unInputLexemes
240 & Pron.runParser dictsEnglish
241 & either errorShow id
246 & Pron.unInputLexemes
247 & Pron.runParser dictsFrench
248 & either errorShow id
253 & Pron.unInputLexemes
254 & Chinese.pronunciation dictsChinese
255 _ -> errorShow ("matterLangue unsupported" :: Text, matter & matterLangue)
259 , "lang-" <> className (matter & matterLangue)
261 ! styles commonStyles
263 forM_ (words & Pron.addIndexes) \word -> do
264 HTML.div ! classes ["sentence-horiz"] $ do
265 forM_ word \syl@Pron.Syl{..} -> do
266 let sylTextLength = sylText & Text.length
269 [ "sentence-syllable"
272 [ "grid-template-columns" :=
274 <> (sylText & Text.length & show)
278 let sylSoundParts = sylSound & Text.words & \l -> if null l then [""] else l
279 let sylSoundsSpanRest = sylTextLength - List.length sylSoundParts + 1
280 forM_ (sylSoundParts & List.zip (sylSoundsSpanRest : List.repeat 1)) \(sylSoundSpan, sylSoundPart) -> do
285 , "cell-" <> case syl of
286 Pron.Syl{sylSilent = True} -> "silent"
287 Pron.Syl{sylIndex = i} -> if i & even then "even" else "odd"
288 , if sylDependsOnAfter then "cell-depends-on-after" else ""
289 , if sylDependsOnBefore then "cell-depends-on-before" else ""
290 , if sylDependsOnMeaning then "cell-depends-on-meaning" else ""
291 , if sylSplit then "cell-split" else ""
294 ( [ "grid-column-end" := "span " <> show sylSoundSpan
299 sylSoundPart & HTML.toHtml
304 , "lang-" <> className (matter & matterLangue)
306 ! styles commonStyles
308 forM_ (words & Pron.addIndexes) \word -> do
309 HTML.div ! classes ["sentence-horiz"] $ do
310 forM_ word \syl -> do
311 forM_ (syl & Pron.sylText & Text.unpack) \pronChar -> do
312 let uniScript = pronChar & Char.unicodeBlock & fromMaybe (UnicodeBlockLatin UnicodeBlockLatin_Basic)
317 , "script-" <> className uniScript
320 HTML.span ! classes ["cell"] $ do
321 pronChar & HTML.toHtml
325 { pageOrientation = PageOrientationPortrait
329 { pageOrientation = PageOrientationLandscape
332 difficultyCSS :: Difficulties -> HTML.CSS
333 difficultyCSS diffs =
334 [ [ [ [".lang-" <> show lang <> " " <> ".script-" <> className uniScript] :=
335 [ "width" := difficultyCharWidth & HTML.toCSS
336 , "height" := difficultyCharHeight & HTML.toCSS
337 , "font-size" := difficultyFontSize & HTML.toCSS
338 , "color" := difficultyColor & HTML.toCSS
340 , [ ".lang-" <> show lang <> ".sentence"
342 := ["column-gap" := difficultyWordSpacing & HTML.toCSS]
344 | (uniScript, Difficulty{..}) <- blocks & toList
346 | (lang, blocks) <- diffs & Map.toList