{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | Shared types for the whole reader. The parser ('EpubParser') produces a -- 'BookStructure' cheaply and renders each 'Chapter' on demand; the UI -- consumes both through the 'BookInfo' interface. module Types ( -- * Identifiers ChapterIndex (..), InternalPath (..), -- * Book content Chapter (..), ChapterRef (..), TocEntry (..), BookStructure (..), -- * The book interface the UI talks to BookInfo (..), -- * UI state AppState (..), UserAction (..), -- * EPUB environment EpubEnv (..), EpubAction, ) where import Codec.Archive.Zip (Archive) import qualified Codec.Epub.Data.Manifest as DMan import qualified Codec.Epub.Data.Metadata as DMeta import Control.Monad.Except (ExceptT) import Control.Monad.Reader (ReaderT) import qualified Data.Map.Strict as M import qualified Data.Text as T -- | Position of a chapter within the linear reading order (the spine). newtype ChapterIndex = ChapterIndex Int deriving (Show, Eq, Ord, Num) -- | A fully-resolved path to a file inside the EPUB zip. newtype InternalPath = InternalPath FilePath deriving (Show, Eq, Ord) -- | A lightweight pointer to a chapter: enough to navigate and label it in the -- sidebar without having rendered its (potentially large) content yet. data ChapterRef = ChapterRef { refPath :: InternalPath, refTitle :: T.Text, refIndex :: ChapterIndex } deriving (Show) -- | A chapter rendered to self-contained HTML, ready for WebKit. data Chapter = Chapter { chapterPath :: InternalPath, chapterTitle :: T.Text, chapterHtml :: T.Text, chapterIndex :: ChapterIndex } deriving (Show) -- | A node in the (possibly nested) table of contents. @tocTarget@ is the -- resolved chapter path; @tocFragment@ is the optional in-page anchor. data TocEntry = TocEntry { tocLabel :: T.Text, tocTarget :: InternalPath, tocFragment :: T.Text, tocChildren :: [TocEntry] } deriving (Show) -- | The cheap-to-compute skeleton of a book: reading order + navigation tree. data BookStructure = BookStructure { structRefs :: [ChapterRef], structToc :: [TocEntry] } -- | The UI only ever sees a book through this interface, so it stays -- format-agnostic. The single instance ('EpubEnv') lives in 'EpubParser'. class BookInfo a where bookTitle :: a -> T.Text bookAuthors :: a -> [T.Text] bookFilePath :: a -> FilePath -- | Resolve the spine and TOC. Fast: does not render chapter bodies. loadStructure :: a -> IO (Either String BookStructure) -- | Render a single chapter's body to self-contained HTML, on demand. renderChapter :: a -> ChapterRef -> IO (Either String Chapter) -- | Extract a single chapter's plain text (tags stripped), for search. chapterText :: a -> ChapterRef -> IO (Either String T.Text) -- | Mutable application state, held in a 'Control.Concurrent.STM.TVar'. -- Rendered chapters accumulate in 'stCache' as the reader visits them. data AppState = AppState { stRefs :: [ChapterRef], stToc :: [TocEntry], stCache :: M.Map ChapterIndex Chapter, stIndex :: ChapterIndex, stZoom :: Double, stTitle :: T.Text, stBookPath :: FilePath, stGen :: Int, stUse :: M.Map ChapterIndex Int, stTick :: Int, stSearchIndex :: M.Map ChapterIndex T.Text } -- | A user request, produced by the keyboard / sidebar and handled centrally. -- 'GoToChapter' carries an optional anchor to scroll to within the chapter. data UserAction = NextChapter | PrevChapter | ZoomIn | ZoomOut | GoToChapter ChapterIndex T.Text -- | Parsed EPUB, carried through the parser as a reader environment. data EpubEnv = EpubEnv { archive :: Archive, opfXml :: String, baseDir :: FilePath, bookPath :: FilePath, eMetadata :: DMeta.Metadata, eManifest :: DMan.Manifest, eVersion :: T.Text } -- | The parser monad: read-only access to the 'EpubEnv', with string errors. type EpubAction a = ReaderT EpubEnv (ExceptT String IO) a