module Math.Combinat.ASCII where
import Data.Char ( isSpace )
import Data.List ( transpose , intercalate )
import Math.Combinat.Helper
data ASCII = ASCII
{ asciiSize :: (Int,Int)
, asciiLines :: [String]
}
class DrawASCII a where
ascii :: a -> ASCII
instance Show ASCII where
show = asciiString
emptyRect :: ASCII
emptyRect = ASCII (0,0) []
asciiXSize, asciiYSize :: ASCII -> Int
asciiXSize = fst . asciiSize
asciiYSize = snd . asciiSize
asciiString :: ASCII -> String
asciiString (ASCII sz ls) = unlines ls
printASCII :: ASCII -> IO ()
printASCII = putStrLn . asciiString
asciiFromLines :: [String] -> ASCII
asciiFromLines ls = ASCII (x,y) (map f ls) where
y = length ls
x = maximum (map length ls)
f l = l ++ replicate (x - length l) ' '
asciiFromString :: String -> ASCII
asciiFromString = asciiFromLines . lines
data HAlign
= HLeft
| HCenter
| HRight
deriving (Eq,Show)
data VAlign
= VTop
| VCenter
| VBottom
deriving (Eq,Show)
data Alignment = Align HAlign VAlign
data HSep
= HSepEmpty
| HSepSpaces Int
| HSepString String
deriving Show
hSepSize :: HSep -> Int
hSepSize hsep = case hsep of
HSepEmpty -> 0
HSepSpaces k -> k
HSepString s -> length s
hSepString :: HSep -> String
hSepString hsep = case hsep of
HSepEmpty -> ""
HSepSpaces k -> replicate k ' '
HSepString s -> s
data VSep
= VSepEmpty
| VSepSpaces Int
| VSepString [Char]
deriving Show
vSepSize :: VSep -> Int
vSepSize vsep = case vsep of
VSepEmpty -> 0
VSepSpaces k -> k
VSepString s -> length s
vSepString :: VSep -> [Char]
vSepString vsep = case vsep of
VSepEmpty -> []
VSepSpaces k -> replicate k ' '
VSepString s -> s
(|||) :: ASCII -> ASCII -> ASCII
(|||) p q = hCatWith VCenter HSepEmpty [p,q]
(===) :: ASCII -> ASCII -> ASCII
(===) p q = vCatWith HCenter VSepEmpty [p,q]
hCatTop :: [ASCII] -> ASCII
hCatTop = hCatWith VTop HSepEmpty
hCatBot :: [ASCII] -> ASCII
hCatBot = hCatWith VBottom HSepEmpty
vCatLeft :: [ASCII] -> ASCII
vCatLeft = vCatWith HLeft VSepEmpty
vCatRight :: [ASCII] -> ASCII
vCatRight = vCatWith HRight VSepEmpty
hCatWith :: VAlign -> HSep -> [ASCII] -> ASCII
hCatWith valign hsep rects = ASCII (x',maxy) final where
n = length rects
maxy = maximum [ y | ASCII (_,y) _ <- rects ]
xsz = [ x | ASCII (x,_) _ <- rects ]
sep = hSepString hsep
sepx = length sep
rects1 = map (vExtendTo valign maxy) rects
x' = sum' xsz + (n-1)*sepx
final = map (intercalate sep) $ transpose (map asciiLines rects1)
vCatWith :: HAlign -> VSep -> [ASCII] -> ASCII
vCatWith halign vsep rects = ASCII (maxx,y') final where
n = length rects
maxx = maximum [ x | ASCII (x,_) _ <- rects ]
ysz = [ y | ASCII (_,y) _ <- rects ]
sepy = vSepSize vsep
fullsep = transpose (replicate maxx $ vSepString vsep) :: [String]
rects1 = map (hExtendTo halign maxx) rects
y' = sum' ysz + (n-1)*sepy
final = intercalate fullsep $ map asciiLines rects1
hPad :: Int -> ASCII -> ASCII
hPad k (ASCII (x,y) ls) = ASCII (x+2*k,y) (map f ls) where
f l = pad ++ l ++ pad
pad = replicate k ' '
vPad :: Int -> ASCII -> ASCII
vPad k (ASCII (x,y) ls) = ASCII (x,y+2*k) (pad ++ ls ++ pad) where
pad = replicate k (replicate x ' ')
pad :: ASCII -> ASCII
pad = vPad 1 . hPad 2
hExtendTo :: HAlign -> Int -> ASCII -> ASCII
hExtendTo halign n0 rect@(ASCII (x,y) ls) = hExtendWith halign (max n0 x - x) rect
vExtendTo :: VAlign -> Int -> ASCII -> ASCII
vExtendTo valign n0 rect@(ASCII (x,y) ls) = vExtendWith valign (max n0 y - y) rect
hExtendWith :: HAlign -> Int -> ASCII -> ASCII
hExtendWith alignment d (ASCII (x,y) ls) = ASCII (x+d,y) (map f ls) where
f l = case alignment of
HLeft -> l ++ replicate d ' '
HRight -> replicate d ' ' ++ l
HCenter -> replicate a ' ' ++ l ++ replicate (d-a) ' '
a = div d 2
vExtendWith :: VAlign -> Int -> ASCII -> ASCII
vExtendWith valign d (ASCII (x,y) ls) = ASCII (x,y+d) (f ls) where
f ls = case valign of
VTop -> ls ++ replicate d emptyline
VBottom -> replicate d emptyline ++ ls
VCenter -> replicate a emptyline ++ ls ++ replicate (d-a) emptyline
a = div d 2
emptyline = replicate x ' '
hIndent :: Int -> ASCII -> ASCII
hIndent d = hExtendWith HRight d
vIndent :: Int -> ASCII -> ASCII
vIndent d = vExtendWith VBottom d
hCut :: HAlign -> Int -> ASCII -> ASCII
hCut halign k (ASCII (x,y) ls) = ASCII (x',y) (map f ls) where
x' = max 0 (x-k)
f = case halign of
HLeft -> reverse . drop k . reverse
HCenter -> reverse . drop (k-a) . reverse . drop a
HRight -> drop k
a = div k 2
vCut :: VAlign -> Int -> ASCII -> ASCII
vCut valign k (ASCII (x,y) ls) = ASCII (x,y') (g ls) where
y' = max 0 (y-k)
g = case valign of
VTop -> reverse . drop k . reverse
VCenter -> reverse . drop (k-a) . reverse . drop a
VBottom -> drop k
a = div k 2
pasteOnto :: (Int,Int) -> ASCII -> ASCII -> ASCII
pasteOnto = pasteOnto' isSpace
pasteOnto'
:: (Char -> Bool)
-> (Int,Int)
-> ASCII
-> ASCII
-> ASCII
pasteOnto' transparent (xpos,ypos) small big = new where
new = ASCII (xbig,ybig) lines'
(xbig,ybig) = asciiSize big
bigLines = asciiLines big
small' = (if (ypos>=0) then vExtendWith VBottom ypos else vCut VBottom (-ypos))
$ (if (xpos>=0) then hExtendWith HRight xpos else hCut HRight (-xpos))
$ small
smallLines = asciiLines small'
lines' = zipWith f bigLines (smallLines ++ repeat "")
f bl sl = zipWith g bl (sl ++ repeat ' ')
g b s = if transparent s then b else s
pasteOntoRel :: (HAlign,VAlign) -> (Int,Int) -> ASCII -> ASCII -> ASCII
pasteOntoRel = pasteOntoRel' isSpace
pasteOntoRel' :: (Char -> Bool) -> (HAlign,VAlign) -> (Int,Int) -> ASCII -> ASCII -> ASCII
pasteOntoRel' transparent (halign,valign) (xpos,ypos) small big = new where
new = pasteOnto' transparent (xpos',ypos') small big
(xsize,ysize) = asciiSize big
xpos' = case halign of
HLeft -> xpos
HCenter -> xpos + div xsize 2
HRight -> xpos + xsize
ypos' = case valign of
VTop -> ypos
VCenter -> ypos + div ysize 2
VBottom -> ypos + ysize
tabulate :: (HAlign,VAlign) -> (HSep,VSep) -> [[ASCII]] -> ASCII
tabulate (halign,valign) (hsep,vsep) rects0 = final where
n = length rects0
m = maximum (map length rects0)
rects1 = map (\rs -> rs ++ replicate (m - length rs) emptyRect) rects0
ys = map (\rs -> maximum (map asciiYSize rs)) rects1
xs = map (\rs -> maximum (map asciiXSize rs)) (transpose rects1)
rects2 = map (\rs -> [ hExtendTo halign x r | (x,r ) <- zip xs rs ]) rects1
rects3 = [ map (vExtendTo valign y) rs | (y,rs) <- zip ys rects2 ]
final = vCatWith HLeft vsep
$ map (hCatWith VTop hsep) rects3
data MatrixOrder
= RowMajor
| ColMajor
deriving (Eq,Ord,Show,Read)
autoTabulate
:: MatrixOrder
-> Either Int Int
-> [ASCII]
-> ASCII
autoTabulate mtxorder ei list = final where
final = tabulate (HLeft,VBottom) (HSepSpaces 2,VSepSpaces 1) rects
n = length list
rects = case ei of
Left y -> case mtxorder of
ColMajor -> transpose (parts y list)
RowMajor -> invparts y list
Right x -> case mtxorder of
ColMajor -> transpose (invparts x list)
RowMajor -> parts x list
transposeIf b = if b then transpose else id
parts d = go where
go [] = []
go xs = take d xs : go (drop d xs)
invparts d xs = parts' ds xs where
(q,r) = divMod n d
ds = replicate r (q+1) ++ replicate (d-r) q
parts' ds xs = go ds xs where
go _ [] = []
go [] _ = []
go (d:ds) xs = take d xs : go ds (drop d xs)
caption :: String -> ASCII -> ASCII
caption = caption' False HLeft
caption' :: Bool -> HAlign -> String -> ASCII -> ASCII
caption' emptyline halign str rect = vCatWith halign sep [rect,capt] where
sep = if emptyline then VSepSpaces 1 else VSepEmpty
capt = asciiFromString str
asciiBox :: (Int,Int) -> ASCII
asciiBox (x,y) = ASCII (max x 2, max y 2) (h : replicate (y-2) m ++ [h]) where
h = "+" ++ replicate (x-2) '-' ++ "+"
m = "|" ++ replicate (x-2) ' ' ++ "|"
roundedAsciiBox :: (Int,Int) -> ASCII
roundedAsciiBox (x,y) = ASCII (max x 2, max y 2) (a : replicate (y-2) m ++ [b]) where
a = "/" ++ replicate (x-2) '-' ++ "\\"
m = "|" ++ replicate (x-2) ' ' ++ "|"
b = "\\" ++ replicate (x-2) '-' ++ "/"
filledBox :: Char -> (Int,Int) -> ASCII
filledBox c (x0,y0) = asciiFromLines $ replicate y (replicate x c) where
x = max 0 x0
y = max 0 y0
transparentBox :: (Int,Int) -> ASCII
transparentBox = filledBox ' '
asciiNumber :: Int -> ASCII
asciiNumber = asciiShow
asciiShow :: Show a => a -> ASCII
asciiShow = asciiFromLines . (:[]) . show