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
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 qualified 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 { matterText :: Pron.InputLexemes
84 , matterLangue :: Langue
86 deriving (Eq, Show, Generic)
87 deriving (HasTypeDefault) via (Generically Matter)
90 { matchMatters :: [Matter]
91 , matchPicture :: File.FilePath
92 , matchPictureCSS :: HTML.CSSBlock
94 deriving (Eq, Show, Generic)
96 instance HasTypeDefault Match where
100 , matchPictureCSS = mempty
105 { pageMatches :: [Match]
106 , pageMatchesNum :: Natural
107 , pageSize :: PageSize
108 , pageOrientation :: PageOrientation
110 deriving (Eq, Show, Generic)
111 instance HasTypeDefault Page where
114 { pageMatches = mempty
115 , pageMatchesNum = 10
116 , pageSize = typeDefault
117 , pageOrientation = typeDefault
121 { pagesList :: [Page]
122 , pagesDifficulties :: Difficulties
124 deriving (Eq, Show, Generic)
125 instance HasTypeDefault Pages where
129 , pagesDifficulties =
131 [ block := typeDefault
132 | block <- Char.unicodeBlocks & toList
135 | lang <- langues & toList
141 { dictsChinese :: Chinese.ChineseDict
142 , dictsFrench :: Pron.Table
143 , dictsEnglish :: Pron.Table
146 pagesHTML :: Dicts -> Text -> Pages -> IO Builder
147 pagesHTML Dicts{..} title Pages{..} = do
148 -- FIXME: this absolute path is not portable out of my system
149 dataPath <- Self.getDataDir <&> File.normalise
150 return $ Blaze.renderMarkupBuilder do
153 HTML.title $ title & HTML.toHtml
155 ( [ "styles/Paper.css"
156 , "styles/Rosetta/Common.css"
157 , "styles/Rosetta/Matching.css"
163 ! HA.rel "stylesheet"
164 ! HA.type_ "text/css"
165 ! HA.href (dataPath </> cssFile & toValue)
166 -- styleCSS $ cssPrintPage pageOrientation pageSize
167 HTML.styleCSS $ pagesDifficulties & difficultyCSS
169 forM_ pagesList \page -> do
171 forM_ (page & pageMatches & chunksOf (page & pageMatchesNum)) \pageMatch -> do
177 , page & pageOrientation & HTML.cssPageOrientation
182 [ page & pageSize & HTML.cssPageSize
183 , page & pageOrientation & HTML.cssPageOrientation
188 let matchColumns = pageMatch & foldMap (matchMatters >>> List.length >>> Max) & getMax
189 let pageColumns = pageMatch & List.length
191 ! classes ["matches"]
192 ! styles ["grid-template-columns" := "1fr" <> mconcat (List.replicate (max 0 (2 * (matchColumns - 1) + 1)) " 2em 1fr 2em 1fr")]
194 let pageMatchNum = pageMatch & List.length
195 forM_ (pageMatch & ol0) \(matchIdx :: Int, match) -> do
197 [ "grid-row-start" := show (pageColumns - matchIdx + 1)
198 , "grid-row-end" := show (pageColumns - matchIdx + 1)
203 ! classes ["match-anchor"]
204 ! styles commonStyles
210 ! classes ["match-links"]
211 ! styles commonStyles
215 forM_ (match & matchMatters & ol1) \(matterIdx :: Natural, matter) -> do
216 when (matterIdx /= 1) do
218 let words :: [[Either Char Pron.Pron]] =
219 case matter & matterLangue of
223 & Pron.unInputLexemes
224 & Pron.runParser dictsEnglish
225 & either errorShow id
230 & Pron.unInputLexemes
231 & Pron.runParser dictsFrench
232 & either errorShow id
237 & Pron.unInputLexemes
238 & Chinese.pronunciation dictsChinese
239 _ -> errorShow ("matterLangue unsupported" :: Text, matter & matterLangue)
243 , "lang-" <> className (matter & matterLangue)
245 ! styles commonStyles
247 forM_ (words & Pron.addIndexes) \word -> do
248 HTML.div ! classes ["sentence-horiz"] $ do
249 forM_ word \syl@Pron.Syl{..} -> do
250 let sylTextLength = sylText & Text.length
253 [ "sentence-syllable"
256 [ "grid-template-columns" :=
258 <> (sylText & Text.length & show)
262 let sylSoundParts = sylSound & Text.words & \l -> if null l then [""] else l
263 let sylSoundsSpanRest = sylTextLength - List.length sylSoundParts + 1
264 forM_ (sylSoundParts & List.zip (sylSoundsSpanRest : List.repeat 1)) \(sylSoundSpan, sylSoundPart) -> do
269 , "cell-" <> case syl of
270 Pron.Syl{sylSilent = True} -> "silent"
271 Pron.Syl{sylIndex = i} -> if i & even then "even" else "odd"
272 , if sylDependsOnAfter then "cell-depends-on-after" else ""
273 , if sylDependsOnBefore then "cell-depends-on-before" else ""
274 , if sylDependsOnMeaning then "cell-depends-on-meaning" else ""
275 , if sylSplit then "cell-split" else ""
278 ( [ "grid-column-end" := "span " <> show sylSoundSpan
283 sylSoundPart & HTML.toHtml
288 , "lang-" <> className (matter & matterLangue)
290 ! styles commonStyles
292 forM_ (words & Pron.addIndexes) \word -> do
293 HTML.div ! classes ["sentence-horiz"] $ do
294 forM_ word \syl -> do
295 forM_ (syl & Pron.sylText & Text.unpack) \pronChar -> do
296 let uniScript = pronChar & Char.unicodeBlock & fromMaybe (UnicodeBlockLatin UnicodeBlockLatin_Basic)
301 , "script-" <> className uniScript
304 HTML.span ! classes ["cell"] $ do
305 pronChar & HTML.toHtml
309 { pageOrientation = PageOrientationPortrait
313 { pageOrientation = PageOrientationLandscape
316 difficultyCSS :: Difficulties -> HTML.CSS
317 difficultyCSS diffs =
318 [ [ [ [".lang-" <> show lang <> " " <> ".script-" <> className uniScript] :=
319 [ "width" := difficultyCharWidth & HTML.toCSS
320 , "height" := difficultyCharHeight & HTML.toCSS
321 , "font-size" := difficultyFontSize & HTML.toCSS
322 , "color" := difficultyColor & HTML.toCSS
324 , [ ".lang-" <> show lang <> ".sentence"
326 := ["column-gap" := difficultyWordSpacing & HTML.toCSS]
328 | (uniScript, Difficulty{..}) <- blocks & toList
330 | (lang, blocks) <- diffs & Map.toList