HPDF-1.8: Graphics/PDF/Expression.hs
---------------------------------------------------------
-- |
-- Copyright : (c) 2023, Henning Thielemann
-- License : BSD-style
--
-- Maintainer : haskell@henning-thielemann.de
-- Stability : experimental
-- Portability : portable
--
-- PDF expressions and functions
---------------------------------------------------------
module Graphics.PDF.Expression (
PDFExpression (..),
Function,
Result,
Argument,
serialize,
(==%), (/=%),
(<%), (>=%), (>%), (<=%),
min, max,
true, false,
(&&*), (||*), not,
ifThenElse,
sqrt,
sinDeg, cosDeg, log, log10,
pow, atan2Deg,
floor, ceiling, round, truncate,
) where
import qualified Data.ByteString.Lazy.Char8 as CL
import qualified Data.ByteString.Char8 as C
import qualified Data.Ratio as Ratio
import qualified Control.Monad.Trans as MT
import qualified Control.Monad.RWS as MRWS
import qualified Control.Monad.Writer as MW
import qualified Control.Monad.State as MS
import Control.Monad (when)
import Control.Applicative (pure, liftA2, liftA3, (<*>))
import Prelude hiding
(not, min, max, log, sqrt, floor, ceiling, round, truncate)
data Token = Token C.ByteString | Index Int
deriving (Show)
type ExprMonad = MRWS.RWS () [Token] Int
newtype PDFExpression a = PDFExpression (ExprMonad ())
serialize :: (Function f) => f -> CL.ByteString
serialize f =
CL.unwords $
(\(n,_,stream) ->
map (\token ->
case token of
Token str -> CL.fromStrict str
Index k -> CL.pack $ show $ n-1+k) stream) $
MRWS.runRWS
(do
n <- MS.execStateT (serializeFunction f) 0
tokens $ replicate n "pop"
return n)
()
0
class Function f where
serializeFunction :: f -> MS.StateT Int ExprMonad ()
instance (Argument a, Function f) => Function (a -> f) where
serializeFunction f = do
arg <- argumentExpression
serializeFunction $ f arg
instance Function (PDFExpression a) where
serializeFunction = serializeResult
instance (Result a, Result b) => Function (a,b) where
serializeFunction = serializeResult
instance (Result a, Result b, Result c) => Function (a,b,c) where
serializeFunction = serializeResult
instance
(Result a, Result b, Result c, Result d) =>
Function (a,b,c,d) where
serializeFunction = serializeResult
{-
Extra class is needed in order
to forbid nested functions like (a -> (a, a->a))
-}
class (Function a) => Result a where
serializeResult :: a -> MS.StateT Int ExprMonad ()
instance Result (PDFExpression a) where
serializeResult (PDFExpression a) = do
n <- MS.get
MT.lift $ do
a
when (n>0) (tokens [show (n+1), "1", "roll"])
MS.modify pred
depth <- MS.get
when (depth/=0) $ error "stack must be empty after evaluation"
instance (Result a, Result b) => Result (a,b) where
serializeResult (a,b) =
serializeResult a >> serializeResult b
instance (Result a, Result b, Result c) => Result (a,b,c) where
serializeResult (a,b,c) =
serializeResult a >> serializeResult b >> serializeResult c
instance
(Result a, Result b, Result c, Result d) =>
Result (a,b,c,d) where
serializeResult (a,b,c,d) =
serializeResult a >> serializeResult b >>
serializeResult c >> serializeResult d
class Argument a where
argumentExpression :: (Monad m) => MS.StateT Int m a
instance Argument (PDFExpression a) where
argumentExpression = MS.StateT $ \n -> pure (argument n, n+1)
instance (Argument a, Argument b) => Argument (a,b) where
argumentExpression =
liftA2 (,) argumentExpression argumentExpression
instance (Argument a, Argument b, Argument c) => Argument (a,b,c) where
argumentExpression =
liftA3 (,,) argumentExpression argumentExpression argumentExpression
instance
(Argument a, Argument b, Argument c, Argument d) =>
Argument (a,b,c,d) where
argumentExpression =
pure (,,,)
<*> argumentExpression
<*> argumentExpression
<*> argumentExpression
<*> argumentExpression
tokens :: [String] -> ExprMonad ()
tokens = MW.tell . map (Token . C.pack)
argument :: Int -> PDFExpression a
argument k = PDFExpression $ do
depth <- MS.get
MW.tell [Index (depth-k), Token $ C.pack "index"]
MS.modify succ
constant :: [String] -> PDFExpression b
constant ts =
PDFExpression $ tokens ts >> MS.modify succ
function1 :: String -> PDFExpression a -> PDFExpression b
function1 name (PDFExpression a) =
PDFExpression $ a >> tokens [name]
function2 :: String -> PDFExpression a -> PDFExpression b -> PDFExpression c
function2 name (PDFExpression a) (PDFExpression b) =
PDFExpression $ a >> b >> tokens [name] >> MS.modify pred
infix 4 ==%, /=%, <%, <=%, >%, >=%
(==%), (/=%) ::
(Eq a) => PDFExpression a -> PDFExpression a -> PDFExpression Bool
(==%) = function2 "eq"
(/=%) = function2 "ne"
(<%), (>=%), (>%), (<=%) ::
(Ord a) => PDFExpression a -> PDFExpression a -> PDFExpression Bool
(<%) = function2 "lt"
(>=%) = function2 "ge"
(>%) = function2 "gt"
(<=%) = function2 "le"
minMax ::
(Ord a) => String -> PDFExpression a -> PDFExpression a -> PDFExpression a
minMax cmp (PDFExpression a) (PDFExpression b) =
PDFExpression $ do
a
b
tokens ["2", "copy", cmp, "{1 pop}", "{exch 1 pop}", "ifelse"]
MS.modify pred
min, max :: (Ord a) => PDFExpression a -> PDFExpression a -> PDFExpression a
min = minMax "lt"
max = minMax "gt"
true, false :: PDFExpression Bool
true = constant ["true"]
false = constant ["false"]
infixr 3 &&*
(&&*) :: PDFExpression Bool -> PDFExpression Bool -> PDFExpression Bool
(&&*) = function2 "and"
infixr 2 ||*
(||*) :: PDFExpression Bool -> PDFExpression Bool -> PDFExpression Bool
(||*) = function2 "or"
not :: PDFExpression Bool -> PDFExpression Bool
not = function1 "not"
ifThenElse ::
(Ord a) =>
PDFExpression Bool -> PDFExpression a -> PDFExpression a -> PDFExpression a
ifThenElse (PDFExpression cond) (PDFExpression a) (PDFExpression b) =
PDFExpression $ do
cond
tokens ["{"] >> a >> tokens ["}"]
tokens ["{"] >> b >> tokens ["}"]
tokens ["ifelse"]
MS.modify pred
MS.modify pred
instance (Num a) => Num (PDFExpression a) where
fromInteger k = constant [show k]
negate = function1 "negate"
abs = function1 "abs"
(+) = function2 "add"
(-) = function2 "sub"
(*) = function2 "mul"
signum (PDFExpression a) =
PDFExpression $ do
a
tokens ["dup", "0", "gt"]
tokens ["{", "pop", "1", "}"]
tokens ["{", "0", "lt", "{-1}", "{0}", "ifelse", "}"]
tokens ["ifelse"]
instance (Fractional a) => Fractional (PDFExpression a) where
fromRational r =
constant [show (Ratio.numerator r), show (Ratio.denominator r), "div"]
(/) = function2 "div"
sqrt, sinDeg, cosDeg, log, log10 ::
(Floating a) => PDFExpression a -> PDFExpression a
sqrt = function1 "sqrt"
sinDeg = function1 "sin"
cosDeg = function1 "cos"
log = function1 "ln"
log10 = function1 "log"
pow, atan2Deg ::
(Floating a) => PDFExpression a -> PDFExpression a -> PDFExpression a
pow = function2 "exp"
atan2Deg = function2 "atan"
floor, ceiling, round, truncate ::
(RealFrac a) => PDFExpression a -> PDFExpression Int
floor = function1 "floor"
ceiling = function1 "ceiling"
round = function1 "round"
truncate = function1 "truncate"