aboutsummaryrefslogtreecommitdiff
path: root/server/src/Thermoprint/Server/Printer/Generic.hs
blob: b993eaf2bd5a6fb425ed96370307603431666d49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE OverloadedStrings #-}

module Thermoprint.Server.Printer.Generic
       ( genericPrint
       ) where

import Thermoprint.Printout
import Thermoprint.API (PrintingError(..))
import Thermoprint.Server.Printer

import System.FileLock
import System.IO

import Data.Sequence (Seq, (|>), (<|), ViewL(..), viewl)
import qualified Data.Sequence as Seq

import qualified Data.ByteString.Lazy as Lazy (ByteString)
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Lazy.Char8 as CLBS

import qualified Data.Text.Lazy as Lazy (Text)
import qualified Data.Text.Lazy as TL
       
import Data.Encoding
import Data.Encoding.CP437
import Data.Word

import Control.Monad
import Control.Monad.Reader
import Control.Monad.Trans.State
import Control.Monad.IO.Class

import Control.Monad.Trans.Resource
import Data.Acquire

import Control.Exception.Base (IOException)
import Control.Monad.Catch

import Data.Foldable

import Data.List (genericReplicate, intercalate)

import Data.Int (Int64)

import Prelude hiding (mapM_, sequence_)

genericPrint :: FilePath -> PrinterMethod
genericPrint path = PM $ genericPrint' path

genericPrint' :: (MonadIO m, MonadMask m) => FilePath -> Printout -> m (Maybe PrintingError)
genericPrint' path = flip catches handlers . withLockedFile path . print
  where
    print printout handle = liftIO $ runReaderT (bracket_ initialize (render printout) finalize) handle >> return Nothing
    handlers = [ Handler $ return . Just . IOError . (show :: IOException -> String)
               , Handler $ return . Just . EncError
               , Handler $ return . Just
               ]

withLockedFile :: (MonadIO m, MonadMask m) => FilePath -> (Handle -> m a) -> m a
withLockedFile path f = withEx (lockedFile path) (f . snd)

lockedFile :: FilePath -> Acquire (FileLock, Handle)
lockedFile f = mkAcquire ((,) <$> lockFile f Exclusive <*> openFile f WriteMode) (\(lock, h) -> () <$ hClose h <* unlockFile lock)

type Render = ReaderT Handle IO ()

encode' :: ByteSink m => String -> m ()
encode' = encode CP437

width :: Int64
width = 32

esc :: Word8
esc = 27

escSequence :: [Word8] -> Render
escSequence = mapM_ pushWord8 . (esc:)

initialize :: Render
initialize = replicateM_ 2 $ escSequence [64]

newl :: Lazy.ByteString
newl = "\r\n"

newls :: Integer -> Lazy.ByteString
newls i = mconcat $ genericReplicate i newl

newls' :: Integer -> Render
newls' = mapM_ pushWord8 . LBS.unpack . newls

finalize :: Render -- TODO: adjust this to produce proper padding
finalize = newls' 2

intersperse :: b -> (a -> b) -> Seq a -> Seq b
intersperse _ _ (viewl -> EmptyL)                 = Seq.empty
intersperse _ f (viewl -> x :< (viewl -> EmptyL)) = Seq.singleton $ f x
intersperse b f (viewl -> x :< xs)                = f x <| b <| intersperse b f xs

intersperse' :: Monad m => m b -> (a -> m b) -> Seq a -> m ()
intersperse' b f = sequence_ . intersperse b f

render :: Printout -> Render
render = intersperse' (newls' 1) renderPar

renderPar :: Paragraph -> Render
renderPar = mapM_ renderChunk
  where
    renderChunk (Raw bs)       = mapM_ pushWord8 $ LBS.unpack bs
    renderChunk (Cooked block) = renderBlock block

renderBlock :: Block -> Render
renderBlock (VSpace n)   = newls' n
renderBlock (NewlSep xs) = intersperse' (newls' 1) renderBlock xs
renderBlock (Line x)     = undefined