1 {-# LANGUAGE OverloadedLists #-}
3 module Rosetta.Writing 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 Control.Monad.Trans.State qualified as MT
13 -- import Control.Monad.Trans.Class qualified as MT
14 import Data.Text qualified as Text
15 import Data.Text.Short qualified as ShortText
16 import System.FilePath.Posix ((</>))
17 import System.FilePath.Posix qualified as File
19 import Text.Blaze.Html5 qualified as HTML
20 import Text.Blaze.Html5.Attributes qualified as HA
21 import Text.Blaze.Renderer.Utf8 qualified as Blaze
22 import Prelude (error)
25 import Language.Chinese (ChineseDict (..), ChineseDictEntries (..))
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 Rosetta.Reading qualified as Reading
32 import Worksheets.Utils.Char as Char
33 import Worksheets.Utils.HTML (className, classes, fr, styles)
34 import Worksheets.Utils.HTML qualified as HTML
35 import Worksheets.Utils.IPA qualified as IPA
36 import Worksheets.Utils.Paper
37 import Worksheets.Utils.Prelude
39 type Difficulties = Map Langue (Map UnicodeBlock Difficulty)
40 data Difficulty = Difficulty
41 { difficultyReading :: Reading.Difficulty
42 , difficultyModel :: Bool
43 , difficultyHiddenPatterns :: Bool
45 deriving (Eq, Show, Generic)
46 instance HasTypeDefault Difficulty where
49 { difficultyReading = typeDefault
50 , difficultyModel = True
51 , difficultyHiddenPatterns = False
55 { partPicture :: File.FilePath
56 , partPictureCSS :: HTML.CSSBlock
57 , partDescription :: ShortText
58 , partText :: [(Pron.Table, Text)]
59 , partLangue :: Langue
61 deriving (Eq, Show, Generic)
62 deriving (HasTypeDefault) via (Generically Part)
66 , pagesDifficulties :: Difficulties
68 deriving (Eq, Show, Generic)
69 instance HasTypeDefault Pages where
75 [ block := typeDefault
76 | block <- Char.unicodeBlocks & toList
79 | lang <- langues & toList
86 , pageColumnPictureWidth :: HTML.Length
87 , pageColumnTextWidth :: HTML.Length
88 , pageSize :: PageSize
89 , pageOrientation :: PageOrientation
91 deriving (Eq, Show, Generic)
93 instance HasTypeDefault Page where
97 , pageColumnPictureWidth = 1 & fr
98 , pageColumnTextWidth = 1 & fr
99 , pageSize = typeDefault
100 , pageOrientation = typeDefault
105 { pageOrientation = PageOrientationPortrait
106 , pageColumnPictureWidth = 3 & fr
107 , pageColumnTextWidth = 4 & fr
111 { pageOrientation = PageOrientationLandscape
112 , pageColumnPictureWidth = 1 & fr
113 , pageColumnTextWidth = 2 & fr
116 pagesHTML :: ChineseDict -> Text -> Pages -> IO Builder
117 pagesHTML chineseDict title Pages{..} = do
118 -- FIXME: this absolute path is not portable out of my system
119 dataPath <- Self.getDataDir <&> File.normalise
120 return $ Blaze.renderMarkupBuilder do
126 HTML.title $ title & HTML.toHtml
128 ( [ "styles/Paper.css"
129 , "styles/Rosetta/Common.css"
130 , "styles/Rosetta/Writing.css"
136 ! HA.rel "stylesheet"
137 ! HA.type_ "text/css"
138 ! HA.href (dataPath </> cssFile & toValue)
139 -- HTML.styleCSS $ HTML.cssPrintPage pagesOrientation pagesSize
140 HTML.styleCSS $ pagesDifficulties <&> fmap difficultyReading & Reading.difficultyCSS
142 forM_ pagesList \Page{..} -> do
144 let numOfParts = pageParts & List.length
150 , pageOrientation & HTML.cssPageOrientation
153 [ "grid-template-columns" := [pageColumnPictureWidth, pageColumnTextWidth] <&> HTML.toCSS & List.unwords
154 , "grid-template-rows" := "1fr" & List.replicate numOfParts & List.unwords
157 [ pageSize & HTML.cssPageSize
158 , pageOrientation & HTML.cssPageOrientation
162 forM_ pageParts \Part{..} -> do
166 [ "rosetta-writing-part"
167 , "rosetta-writing-part-picture"
168 , if partDescription & ShortText.null then "" else "with-description"
172 ! styles partPictureCSS
173 ! HA.src ("file://" <> dataPath </> "images" </> partPicture & toValue)
174 unless (partDescription & ShortText.null) do
175 HTML.span ! classes ["description"] $ do
176 partDescription & HTML.toHtml
177 HTML.div ! classes ["rosetta-writing-part", "sentence"] $ do
178 -- traceShowM ("partText"::Text, partText)
179 -- forM_ (partText & rosettaTokenizer & groupByHoriz) \writingHoriz -> do
181 -- traceShowM ("writingHoriz"::Text, writingHoriz)
182 -- HTML.div ! classes ["sentence-horiz"] $ do
183 -- forM_ (writingHoriz & splitWords) \writingWord -> do
184 -- let writingWord :: Text = "choux hibou genoux caillou glace"
185 -- traceShowM ("writingWord"::String, writingWord)
186 let textToSoundsGroup :: [[Either Char Pron.Pron]] =
192 & either (\err -> errorShow (txt, err)) id
194 & either (\err -> errorShow (txt, err)) id
198 let textToSoundsGroup =
201 LangueMandarin -> Chinese.pronunciation chineseDict
202 LangueFrançais -> French.pronunciation
203 LangueAnglais -> English.pronunciation
204 _ -> error $ "partLangue unsupported: " <> show partLangue
206 -- let addIndexes (idx :: Int) = \case
208 -- lexs : t -> List.reverse lexs' : addIndexes idx' t
213 -- case kv & lexemePron of
214 -- PronunciationIPABroad{pronunciationIPA = IPA.Syllable [IPA.Zero]} -> (i, (i, kv) : is)
215 -- PronunciationIPABroad{} -> (i + 1, (i + 1, kv) : is)
221 -- == Pron.borderLeftChar
223 -- == Pron.borderRightChar
224 forM_ (textToSoundsGroup & Pron.addIndexes) \textToSounds -> do
225 HTML.div ! classes ["sentence-horiz"] $ do
226 -- let addWordIndexes (wordIndex :: Int) = \case
228 -- (pronIndex, lex@Lexeme{..}) : t ->
229 -- (wordIndex, (pronIndex, lex)) : addWordIndexes idx' t
232 -- | lexemeKey == Text.singleton Pron.borderLeftChar = wordIndex
233 -- | lexemeKey == Text.singleton Pron.borderRightChar = wordIndex
234 -- | otherwise = wordIndex + 1
236 forM_ (textToSounds <&> (0 :: Int,)) \(wordIndex, (pronIndex, charOrPron)) -> do
237 let pronChars = case charOrPron of
239 Right pron -> pron & Pron.pronInput & Pron.lexemesChars
240 -- let addCharIndexes (idx :: Int) = \case
242 -- c : cs -> (idx', c) : addCharIndexes idx' cs
245 -- case c & charMetaUnicodeCategory of
248 -- | isBreak (charMetaChar c) -> idx
249 -- | otherwise -> idx + 1
250 -- let charsMeta = lexemeKey & metanizer & addCharIndexes 0
254 , "word-index-" <> show wordIndex
255 , "lang-" <> className partLangue
258 [ "grid-template-columns" :=
260 <> (pronChars & List.length & show)
268 , -- , "cell-index-"<>show pronIndex
271 Right Pron.Pron{pronRule = Pron.Rule{rulePron = PronunciationIPABroad{pronunciationIPA = [IPA.Syllable [IPA.Zero]]}}} -> "cell-silent"
272 Right Pron.Pron{pronRule = Pron.Rule{rulePron = PronunciationIPABroad{}}} -> if even pronIndex then "cell-even" else "cell-odd"
275 [ "grid-column-end" := "span " <> show (pronChars & List.length)
276 -- , "display" := if partLangue == LangueAnglais then "none" else ""
279 HTML.toHtml $ case charOrPron of
281 Right Pron.Pron{pronRule = Pron.Rule{rulePron = PronunciationIPABroad{pronunciationIPA = [IPA.Syllable [IPA.Zero]]}}} -> ""
282 Right Pron.Pron{pronRule = Pron.Rule{rulePron = PronunciationIPABroad{Pron.pronunciationText = txt}}} -> txt
283 forM_ pronChars \pronChar -> do
284 let uniScript = pronChar & Char.unicodeBlock & fromMaybe (UnicodeBlockLatin UnicodeBlockLatin_Basic)
289 , "script-" <> className uniScript
290 , if pagesDifficulties
291 & lookupOrTypeDefault partLangue
292 & lookupOrTypeDefault uniScript
296 -- , if isBreak $ charMetaChar charMeta
301 HTML.span ! classes ["cell"] $ do
302 pronChar & HTML.toHtml
303 forM_ (pronChars & List.zip [0 :: Int ..]) \(charIndex, pronChar) -> do
304 let uniScript = pronChar & Char.unicodeBlock & fromMaybe (UnicodeBlockLatin UnicodeBlockLatin_Basic)
309 , "input-index-" <> show charIndex
310 , "script-" <> className uniScript
311 , if pagesDifficulties
312 & lookupOrTypeDefault partLangue
313 & lookupOrTypeDefault uniScript
314 & difficultyHiddenPatterns
317 -- , if isBreak $ charMetaChar charMeta
322 HTML.span ! classes ["cell"] $ do
323 pronChar & HTML.toHtml