packages feed

simple-cairo-0.1.0.5: src/Graphics/Cairo/Drawing/Paths/CairoPathT.hsc

{-# LANGUAGE BlockArguments, LambdaCase, TupleSections #-}
{-# LANGUAGE PatternSynonyms, ViewPatterns #-}
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

module Graphics.Cairo.Drawing.Paths.CairoPathT (
	Path(..), CairoPathT, pattern CairoPathT, withCairoPathT, mkCairoPathT,
	MoveTo(..), LineCurveTo(..), CloseTo(..),
	CairoPatchPathT, pattern CairoPatchPathT,
	pattern CairoPathTPatch, mkCairoPatchPathT ) where

import Foreign.Ptr
import Foreign.ForeignPtr hiding (newForeignPtr)
import Foreign.Concurrent
import Foreign.Marshal
import Foreign.Storable
import Foreign.C.Types
import Data.Bool
import Data.Word

import Graphics.Cairo.Exception

import System.IO.Unsafe

#include <cairo.h>

newtype CairoPathT = CairoPathT_ (ForeignPtr CairoPathT) deriving Show

pattern CairoPathT :: [Path] -> CairoPathT
pattern CairoPathT ps <- (unsafePerformIO . cairoPathTPathList -> ps) where
	CairoPathT = unsafePerformIO . pathListToCairoPathT

withCairoPathT :: CairoPathT -> (Ptr CairoPathT -> IO a) -> IO a
withCairoPathT (CairoPathT_ fpth) = withForeignPtr fpth

mkCairoPathT :: Ptr CairoPathT -> IO CairoPathT
mkCairoPathT p =
	CairoPathT_ <$> newForeignPtr p (c_cairo_path_destroy p)
		<* (cairoStatusToThrowError =<< cairoPathTStatus p)

foreign import ccall "cairo_path_destroy" c_cairo_path_destroy ::
	Ptr CairoPathT -> IO ()

cairoPathTStatus :: Ptr CairoPathT -> IO #{type cairo_status_t}
cairoPathTStatus = #{peek cairo_path_t, status}

cairoPathTData ::Ptr CairoPathT -> IO (Ptr CairoPathDataT)
cairoPathTData = #{peek cairo_path_t, data}

cairoPathTNumData :: Ptr CairoPathT -> IO CInt
cairoPathTNumData = #{peek cairo_path_t, num_data}

newtype CairoPathDataT = CairoPathDataT (Ptr CairoPathDataT) deriving Show

nextPtr :: Ptr a -> Int -> Int -> Ptr a
nextPtr p sz al = alignPtr (plusPtr p sz) al

nextCairoPathDataT :: Ptr CairoPathDataT -> Ptr CairoPathDataT
nextCairoPathDataT p = nextPtr p #{size cairo_path_data_t} #{alignment cairo_path_data_t}

nextByLength :: Ptr CairoPathDataT -> CInt -> Ptr CairoPathDataT
nextByLength p n | n < 1 = p
nextByLength p n = nextCairoPathDataT $ nextByLength p (n - 1)

cairoPathDataTHeaderType :: Ptr CairoPathDataT -> IO #{type cairo_path_data_type_t}
cairoPathDataTHeaderType = #{peek cairo_path_data_t, header.type}

cairoPathDataTHeaderLength :: Ptr CairoPathDataT -> IO CInt
cairoPathDataTHeaderLength = #{peek cairo_path_data_t, header.length}

cairoPathDataTPointX, cairoPathDataTPointY :: Ptr CairoPathDataT -> IO CDouble
cairoPathDataTPointX = #{peek cairo_path_data_t, point.x}
cairoPathDataTPointY = #{peek cairo_path_data_t, point.y}

data Path
	= PathMoveTo CDouble CDouble
	| PathLineTo CDouble CDouble
	| PathCurveTo CDouble CDouble CDouble CDouble CDouble CDouble
	| PathClosePath
	deriving Show

cairoPathTPathList :: CairoPathT -> IO [Path]
cairoPathTPathList (CairoPathT_ fp) = withForeignPtr fp \p -> do
	d <- cairoPathTData p
	n <- cairoPathTNumData p
	cairoPathDataTPathList d n

cairoPathDataTPathList :: Ptr CairoPathDataT -> CInt -> IO [Path]
cairoPathDataTPathList _ n | n < 1 = pure []
cairoPathDataTPathList p n = unsafeInterleaveIO do
	pth <- unsafeInterleaveIO $ cairoPathDataTHeaderType p >>= \case
		#{const CAIRO_PATH_MOVE_TO} -> PathMoveTo
			<$> cairoPathDataTPointX p1 <*> cairoPathDataTPointY p1
		#{const CAIRO_PATH_LINE_TO} -> PathLineTo
			<$> cairoPathDataTPointX p1 <*> cairoPathDataTPointY p1
		#{const CAIRO_PATH_CURVE_TO} -> PathCurveTo
			<$> cairoPathDataTPointX p1 <*> cairoPathDataTPointY p1
			<*> cairoPathDataTPointX p2 <*> cairoPathDataTPointY p2
			<*> cairoPathDataTPointX p3 <*> cairoPathDataTPointY p3
		#{const CAIRO_PATH_CLOSE_PATH} -> pure PathClosePath
		_ -> error "no such path"
	ln <- cairoPathDataTHeaderLength p
	(pth :) <$> cairoPathDataTPathList (nextByLength p ln) (n - ln)
	where
	p1 = nextByLength p 1
	p2 = nextByLength p 2
	p3 = nextByLength p 3

pathToNumData :: Path -> Int
pathToNumData = \case
	PathMoveTo _ _ -> 2; PathLineTo _ _ -> 2; PathCurveTo _ _ _ _ _ _ -> 4; PathClosePath -> 1

pathToCairoPathData :: Ptr CairoPathDataT -> Path -> IO ()
pathToCairoPathData p = \case
	PathMoveTo x y -> do
		#{poke cairo_path_data_t, header.type} p (#{const CAIRO_PATH_MOVE_TO} :: #{type cairo_path_data_type_t})
		#{poke cairo_path_data_t, header.length} p (2 :: CInt)
		#{poke cairo_path_data_t, point.x} p1 x
		#{poke cairo_path_data_t, point.y} p1 y
	PathLineTo x y -> do
		#{poke cairo_path_data_t, header.type} p (#{const CAIRO_PATH_LINE_TO} :: #{type cairo_path_data_type_t})
		#{poke cairo_path_data_t, header.length} p (2 :: CInt)
		#{poke cairo_path_data_t, point.x} p1 x
		#{poke cairo_path_data_t, point.y} p1 y
	PathCurveTo x1 y1 x2 y2 x3 y3 -> do
		#{poke cairo_path_data_t, header.type} p (#{const CAIRO_PATH_CURVE_TO} :: #{type cairo_path_data_type_t})
		#{poke cairo_path_data_t, header.length} p (4 :: CInt)
		#{poke cairo_path_data_t, point.x} p1 x1
		#{poke cairo_path_data_t, point.y} p1 y1
		#{poke cairo_path_data_t, point.x} p2 x2
		#{poke cairo_path_data_t, point.y} p2 y2
		#{poke cairo_path_data_t, point.x} p3 x3
		#{poke cairo_path_data_t, point.y} p3 y3
	PathClosePath -> do
		#{poke cairo_path_data_t, header.type} p (#{const CAIRO_PATH_CLOSE_PATH} :: #{type cairo_path_data_type_t})
		#{poke cairo_path_data_t, header.length} p (1 :: CInt)
	where
	p1 = nextByLength p 1
	p2 = nextByLength p 2
	p3 = nextByLength p 3

calcAlignedSize :: Int -> Int -> Int
calcAlignedSize sz al = (sz `div` al + signum (sz `mod` al)) * al

cairoPathDataTSize :: Int
cairoPathDataTSize = calcAlignedSize #{size cairo_path_data_t} #{alignment cairo_path_data_t}

pathListToCairoPathT :: [Path] -> IO CairoPathT
pathListToCairoPathT pths = CairoPathT_ <$> do
	pd <- mallocBytes $ sum (pathToNumData <$> pths) * cairoPathDataTSize
	pathListToCairoPathDataT pd pths
	p <- mallocBytes #{size cairo_path_t}
	#{poke cairo_path_t, status} p (#{const CAIRO_STATUS_SUCCESS} :: #{type cairo_status_t})
	#{poke cairo_path_t, data} p pd
	#{poke cairo_path_t, num_data} p $ sum (pathToNumData <$> pths)
	newForeignPtr p $ free pd >> free p

pathListToCairoPathDataT :: Ptr CairoPathDataT -> [Path] -> IO ()
pathListToCairoPathDataT pd = \case
	[] -> pure ()
	pth : pths -> pathToCairoPathData pd pth >> pathListToCairoPathDataT (pd `plusPtr` (cairoPathDataTSize * pathToNumData pth)) pths

cairoPathTCheckPaths :: CairoPathT -> [#{type cairo_path_data_type_t} -> Bool] -> IO Bool
cairoPathTCheckPaths (CairoPathT_ fpth) ts = withForeignPtr fpth \ppth -> do
	d <- cairoPathTData ppth
	n <- cairoPathTNumData ppth
	cairoPathDataCheckPaths d ts n

cairoPathDataCheckPaths :: Ptr CairoPathDataT -> [#{type cairo_path_data_type_t} -> Bool] -> CInt -> IO Bool
cairoPathDataCheckPaths _ [] 0 = pure True
cairoPathDataCheckPaths _ [] _ = pure False
cairoPathDataCheckPaths _ _ 0 = pure False
cairoPathDataCheckPaths p (t : ts) n = do
	b <- t <$> cairoPathDataTHeaderType p
	ln <- cairoPathDataTHeaderLength p
	(b &&) <$> unsafeInterleaveIO (cairoPathDataCheckPaths (nextByLength p ln) ts (n - ln))

cairoPathTGetGoal :: CairoPathT -> Int -> IO (Maybe (CDouble, CDouble))
cairoPathTGetGoal (CairoPathT_ fpth) i = withForeignPtr fpth \ppth -> do
	d <- cairoPathTData ppth
	n <- cairoPathTNumData ppth
	cairoPathDataTGetGoal d i n

cairoPathDataTGetGoal :: Ptr CairoPathDataT -> Int -> CInt -> IO (Maybe (CDouble, CDouble))
cairoPathDataTGetGoal _ _ n | n < 1 = pure Nothing
cairoPathDataTGetGoal p 0 _ = unsafeInterleaveIO do
	cairoPathDataTHeaderType p >>= \case
		#{const CAIRO_PATH_MOVE_TO} -> (\x y -> Just (x, y))
			<$> cairoPathDataTPointX p1 <*> cairoPathDataTPointY p1
		#{const CAIRO_PATH_LINE_TO} -> (\x y -> Just (x, y))
			<$> cairoPathDataTPointX p1 <*> cairoPathDataTPointY p1
		#{const CAIRO_PATH_CURVE_TO} -> (\x y -> Just (x, y))
			<$> cairoPathDataTPointX p3 <*> cairoPathDataTPointY p3
		#{const CAIRO_PATH_CLOSE_PATH} -> pure Nothing
		_ -> error "no such path"
	where p1 = nextByLength p 1; p3 = nextByLength p 3
cairoPathDataTGetGoal p i n = do
	ln <- cairoPathDataTHeaderLength p
	cairoPathDataTGetGoal (nextByLength p ln) (i - 1) (n - ln)

lineOrCurve :: #{type cairo_path_data_type_t} -> Bool
lineOrCurve = (||) <$> (== #{const CAIRO_PATH_LINE_TO}) <*> (== #{const CAIRO_PATH_CURVE_TO})

isCairoPatchPath :: CairoPathT -> IO Bool
isCairoPatchPath pth = do
	b1 <- cairoPathTCheckPaths pth [
		(== #{const CAIRO_PATH_MOVE_TO}), lineOrCurve, lineOrCurve, lineOrCurve,
		(== #{const CAIRO_PATH_CLOSE_PATH}) ]
	b2 <- cairoPathTCheckPaths pth [
		(== #{const CAIRO_PATH_MOVE_TO}), lineOrCurve, lineOrCurve, lineOrCurve, lineOrCurve ]
	b3 <- cairoPathTGetGoal pth 0 >>= \case
		Nothing -> pure False
		Just (x0, y0) -> cairoPathTGetGoal pth 4 >>= \case
			Nothing -> pure False
			Just (xc, yc) -> pure $ xc == x0 && yc == y0
	pure $ b1 || (b2 && b3)

newtype CairoPatchPathT = CairoPatchPathT_ (ForeignPtr CairoPathT) deriving Show

mkCairoPatchPathT :: Ptr CairoPathT -> IO CairoPatchPathT
mkCairoPatchPathT p =
	CairoPatchPathT_ <$> newForeignPtr p (c_cairo_path_destroy p)
		<* (cairoStatusToThrowError =<< cairoPathTStatus p)

pattern CairoPathTPatch :: CairoPatchPathT -> CairoPathT
pattern CairoPathTPatch ppth <- (unsafePerformIO . cairoPathTPatch -> Just ppth) where
	CairoPathTPatch (CairoPatchPathT_ fpth) = CairoPathT_ fpth

cairoPathTPatch :: CairoPathT -> IO (Maybe CairoPatchPathT)
cairoPathTPatch pth@(CairoPathT_ fpth) =
	bool Nothing (Just $ CairoPatchPathT_ fpth) <$> isCairoPatchPath pth

data MoveTo = MoveTo CDouble CDouble deriving Show

data LineCurveTo
	= LineTo CDouble CDouble
	| CurveTo CDouble CDouble CDouble CDouble CDouble CDouble
	deriving Show

data CloseTo
	= CloseLineTo
	| CloseCurveTo CDouble CDouble CDouble CDouble
	deriving Show

pattern CairoPatchPathT :: MoveTo -> LineCurveTo -> LineCurveTo -> LineCurveTo -> CloseTo -> CairoPatchPathT
pattern CairoPatchPathT mt lct1 lct2 lct3 cls <- (unsafePerformIO . drawFromCairoPatchPathT -> (mt, lct1, lct2, lct3, cls)) where
	CairoPatchPathT mt lct1 lct2 lct3 cls = unsafePerformIO $ patchPathsToCairoPatchPathT mt lct1 lct2 lct3 cls

drawFromCairoPatchPathT :: CairoPatchPathT -> IO (MoveTo, LineCurveTo, LineCurveTo, LineCurveTo, CloseTo)
drawFromCairoPatchPathT (CairoPatchPathT_ fpth) = withForeignPtr fpth \ppth -> do
	d <- cairoPathTData ppth
	n <- cairoPathTNumData ppth
	drawFromCairoPathDataT d n >>= \case
		(Just r, 0) -> pure r
		_ -> error "badbadbad"
		
drawFromCairoPathDataT :: Ptr CairoPathDataT -> CInt ->
	IO (Maybe (MoveTo, LineCurveTo, LineCurveTo, LineCurveTo, CloseTo), CInt)
drawFromCairoPathDataT p0 n0 = do
	(mmt, pn1) <- drawMoveToFromCairoPathDataT p0 n0
	(mlct1, pn2) <- uncurry drawLineCurveToFromCairoPathDataT pn1
	(mlct2, pn3) <- uncurry drawLineCurveToFromCairoPathDataT pn2
	(mlct3, pn4) <- uncurry drawLineCurveToFromCairoPathDataT pn3
	(mcls_, (_p5, n5)) <- uncurry drawCloseToFromCairoPathDataT pn4
	let	mcls = case mmt of
			Nothing -> error "bad"
			Just (MoveTo x0 y0) -> case mcls_ of
				Nothing -> error "bad"
				Just (m, Nothing) -> Just m
				Just (m, Just (x1, y1))
					| x0 == x1 && y0 == y1 -> Just m
					| otherwise -> error "bad"
	if n5 /= 0 then error "bad" else pure 
		. (, n5) $ (,,,,) <$> mmt <*> mlct1 <*> mlct2 <*> mlct3 <*> mcls

drawMoveToFromCairoPathDataT :: Ptr CairoPathDataT -> CInt -> IO (Maybe MoveTo, (Ptr CairoPathDataT, CInt))
drawMoveToFromCairoPathDataT p n = do
	mt <- cairoPathDataTHeaderType p >>= \case
		#{const CAIRO_PATH_MOVE_TO} -> Just
			<$> (MoveTo <$> cairoPathDataTPointX p1 <*> cairoPathDataTPointY p1)
		_ -> pure Nothing
	ln <- cairoPathDataTHeaderLength p
	if n - ln < 0 then error "bad" else pure (mt, (nextByLength p ln, n - ln))
	where p1 = nextByLength p 1

drawLineCurveToFromCairoPathDataT :: Ptr CairoPathDataT -> CInt -> IO (Maybe LineCurveTo, (Ptr CairoPathDataT, CInt))
drawLineCurveToFromCairoPathDataT p n = do
	lct <- cairoPathDataTHeaderType p >>= \case
		#{const CAIRO_PATH_LINE_TO} -> Just
			<$> (LineTo <$> cairoPathDataTPointX p1 <*> cairoPathDataTPointY p1)
		#{const CAIRO_PATH_CURVE_TO} -> (Just <$>) $ CurveTo
			<$> cairoPathDataTPointX p1 <*> cairoPathDataTPointY p1
			<*> cairoPathDataTPointX p2 <*> cairoPathDataTPointY p2
			<*> cairoPathDataTPointX p3 <*> cairoPathDataTPointY p3
		_ -> pure Nothing
	ln <- cairoPathDataTHeaderLength p
	if n - ln < 0 then error "bad" else pure (lct, (nextByLength p ln, n - ln))
	where p1 = nextByLength p 1; p2 = nextByLength p 2; p3 = nextByLength p 3

drawCloseToFromCairoPathDataT :: Ptr CairoPathDataT -> CInt -> IO (Maybe (CloseTo, Maybe (CDouble, CDouble)), (Ptr CairoPathDataT, CInt))
drawCloseToFromCairoPathDataT p n = do
	ctgl <- cairoPathDataTHeaderType p >>= \case
		#{const CAIRO_PATH_LINE_TO} -> (Just <$>) $ ((CloseLineTo ,) . Just <$>) $
			(,) <$> cairoPathDataTPointX p1 <*> cairoPathDataTPointY p1
		#{const CAIRO_PATH_CURVE_TO} -> (Just <$>) $ (,)
			<$> (CloseCurveTo
				<$> cairoPathDataTPointX p1 <*> cairoPathDataTPointY p1
				<*> cairoPathDataTPointX p2 <*> cairoPathDataTPointY p2)
			<*> (Just <$> ((,) <$> cairoPathDataTPointX p3 <*> cairoPathDataTPointY p3))
		#{const CAIRO_PATH_CLOSE_PATH} -> pure $ Just (CloseLineTo, Nothing)
		_ -> pure Nothing
	ln <- cairoPathDataTHeaderLength p
	if n - ln < 0 then error "bad" else pure (ctgl, (nextByLength p ln, n - ln))
	where [p1, p2, p3] = nextByLength p <$> [1, 2, 3]

patchPathsToCairoPatchPathT ::
	MoveTo -> LineCurveTo -> LineCurveTo -> LineCurveTo -> CloseTo -> IO CairoPatchPathT
patchPathsToCairoPatchPathT mt lct1 lct2 lct3 cls = CairoPatchPathT_ <$> do
	pd <- mallocBytes $ patchPathSize mt lct1 lct2 lct3 cls * cairoPathDataTSize
	patchPathsToCairoPathDataT pd mt lct1 lct2 lct3 cls
	p <- mallocBytes #{size cairo_path_t}
	#{poke cairo_path_t, status} p (#{const CAIRO_STATUS_SUCCESS} :: #{type cairo_status_t})
	#{poke cairo_path_t, data} p pd
	#{poke cairo_path_t, num_data} p $ patchPathSize mt lct1 lct2 lct3 cls
	newForeignPtr p $ free pd >> free p

patchPathSize :: MoveTo -> LineCurveTo -> LineCurveTo -> LineCurveTo -> CloseTo -> Int
patchPathSize _mt lct1 lct2 lct3 cls =
	2 + sum (lineCurveToSize <$> [lct1, lct2, lct3]) + closeToSize cls

lineCurveToSize :: LineCurveTo -> Int
lineCurveToSize (LineTo _ _) = 2
lineCurveToSize (CurveTo _ _ _ _ _ _) = 4

closeToSize :: CloseTo -> Int
closeToSize CloseLineTo = 1
closeToSize (CloseCurveTo _ _ _ _) = 4

patchPathsToCairoPathDataT :: Ptr CairoPathDataT ->
	MoveTo -> LineCurveTo -> LineCurveTo -> LineCurveTo -> CloseTo -> IO ()
patchPathsToCairoPathDataT pd mt lct1 lct2 lct3 cls = do
	pd1 <- moveToToCairoPathDataT pd mt
	pd2 <- lineCurveToToCairoPathDataT pd1 lct1
	pd3 <- lineCurveToToCairoPathDataT pd2 lct2
	pd4 <- lineCurveToToCairoPathDataT pd3 lct3
	_ <- closeToToCairoPathDataT pd4 mt cls
	pure ()

moveToToCairoPathDataT :: Ptr CairoPathDataT -> MoveTo -> IO (Ptr CairoPathDataT)
moveToToCairoPathDataT p (MoveTo x y) = do
	#{poke cairo_path_data_t, header.type} p (#{const CAIRO_PATH_MOVE_TO} :: #{type cairo_path_data_type_t})
	#{poke cairo_path_data_t, header.length} p (2 :: CInt)
	#{poke cairo_path_data_t, point.x} p1 x
	#{poke cairo_path_data_t, point.y} p1 y
	pure $ p `plusPtr` (cairoPathDataTSize * 2)
	where p1 = nextByLength p 1

lineCurveToToCairoPathDataT :: Ptr CairoPathDataT -> LineCurveTo -> IO (Ptr CairoPathDataT)
lineCurveToToCairoPathDataT p = \case
	LineTo x y -> do
		#{poke cairo_path_data_t, header.type} p (#{const CAIRO_PATH_LINE_TO} :: #{type cairo_path_data_type_t})
		#{poke cairo_path_data_t, header.length} p (2 :: CInt)
		#{poke cairo_path_data_t, point.x} p1 x
		#{poke cairo_path_data_t, point.y} p1 y
		pure $ p `plusPtr` (cairoPathDataTSize * 2)
	CurveTo x1 y1 x2 y2 x3 y3 -> do
		#{poke cairo_path_data_t, header.type} p (#{const CAIRO_PATH_CURVE_TO} :: #{type cairo_path_data_type_t})
		#{poke cairo_path_data_t, header.length} p (4 :: CInt)
		#{poke cairo_path_data_t, point.x} p1 x1
		#{poke cairo_path_data_t, point.y} p1 y1
		#{poke cairo_path_data_t, point.x} p2 x2
		#{poke cairo_path_data_t, point.y} p2 y2
		#{poke cairo_path_data_t, point.x} p3 x3
		#{poke cairo_path_data_t, point.y} p3 y3
		pure $ p `plusPtr` (cairoPathDataTSize * 4)
	where p1 = nextByLength p 1; p2 = nextByLength p 2; p3 = nextByLength p 3

closeToToCairoPathDataT :: Ptr CairoPathDataT -> MoveTo -> CloseTo -> IO (Ptr CairoPathDataT)
closeToToCairoPathDataT p (MoveTo x0 y0) = \case
	CloseLineTo -> do
		#{poke cairo_path_data_t, header.type} p (#{const CAIRO_PATH_CLOSE_PATH} :: #{type cairo_path_data_type_t})
		#{poke cairo_path_data_t, header.length} p (1 :: CInt)
		pure $ p `plusPtr` cairoPathDataTSize
	CloseCurveTo x1 y1 x2 y2 -> do
		#{poke cairo_path_data_t, header.type} p (#{const CAIRO_PATH_CURVE_TO} :: #{type cairo_path_data_type_t})
		#{poke cairo_path_data_t, header.length} p (4 :: CInt)
		#{poke cairo_path_data_t, point.x} p1 x1
		#{poke cairo_path_data_t, point.y} p1 y1
		#{poke cairo_path_data_t, point.x} p2 x2
		#{poke cairo_path_data_t, point.y} p2 y2
		#{poke cairo_path_data_t, point.x} p3 x0
		#{poke cairo_path_data_t, point.y} p3 y0
		pure $ p `plusPtr` (cairoPathDataTSize * 4)
	where p1 = nextByLength p 1; p2 = nextByLength p 2; p3 = nextByLength p 3