+{-# LANGUAGE DeriveFunctor #-}
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Symantic.Plaintext.Writer2 where
+
+import Control.Applicative (Alternative (..), Applicative (..))
+import Data.Bool
+import Data.Char (Char)
+import Data.Eq (Eq (..))
+import Data.Function (($), (.))
+import Data.Functor (Functor (..), (<$>))
+import Data.List qualified as List
+import Data.Maybe (Maybe (..), fromMaybe)
+import Data.Monoid (Monoid (..))
+import Data.Ord (Ord (..))
+import Data.Semigroup (Semigroup (..))
+import Data.String (IsString (..), String)
+import Data.Traversable (Traversable (traverse))
+import GHC.Natural (
+ minusNatural,
+ minusNaturalMaybe,
+ quotRemNatural,
+ )
+import Numeric.Natural (Natural)
+import Text.Show (
+ Show (..),
+ showParen,
+ showString,
+ )
+import Prelude (
+ Num (..),
+ error,
+ fromIntegral,
+ undefined,
+ )
+
+--import qualified Data.Text.Lazy.Builder as TLB
+--import qualified Data.Text.Lazy.Builder as TLB
+
+import Control.Monad (Monad ((>>=)))
+import Data.Functor.Identity (Identity (..))
+import Data.Text.Internal.Fusion.Types (RS (RS3))
+import Data.Tuple (fst, snd, swap)
+import Debug.Trace (trace, traceShow)
+import GHC.IO.Exception (IOErrorType (SystemError))
+import GHC.List (sum)
+import GHC.Real (Integral (div))
+import Symantic.Plaintext.Classes ()
+import Symantic.Plaintext.Output
+
+data Measured o = Measured
+ { measuredHorizontal :: Natural
+ , measuredVertical :: Natural
+ , unMeasured :: o
+ }
+instance Dimensionable (Measured o) where
+ width = measuredHorizontal
+ height = measuredVertical
+
+-- renderRect :: Outputable o => Measured (Rect o) -> [o]
+-- renderRect Measured{..} = case unMeasured of
+-- RectEmpty -> []
+-- RectChunk o -> [o]
+-- Rects{..} -> case rectDirection of
+-- DirectionHorizontal -> []
+
+resize :: Measured [o] -> [o]
+resize Measured{..} = undefined
+
+-- | Doc: https://personal.cis.strath.ac.uk/conor.mcbride/so-pigworker.pdf#section.4.9
+data PaddedList a = (:-)
+ { padded :: [a]
+ , padder :: a
+ }
+ deriving (Show, Eq)
+
+infixr 5 :-
+
+instance Functor PaddedList where
+ fmap = (<*>) . pure
+instance Applicative PaddedList where
+ pure = ([] :-)
+ as :- ap <*> bs :- bp = go as bs :- ap bp
+ where
+ go [] xs = ap <$> xs
+ go fs [] = ($ bp) <$> fs
+ go (f : fs) (x : xs) = f x : go fs xs
+
+-- >>> "om":-' ' <*> "mane":-' '
+
+-- >>> "om":-' ' <*> "mane":-' '
+-- Couldn't match type ‘Char’ with ‘Char -> b’
+-- Expected type: PaddedList (Char -> b)
+-- Actual type: PaddedList Char
+
+-- >>> deggar ' ' ["om", "mane", "padme", "hum12345"]
+-- (:-) {padded = ["omph","maau"," ndm"," em1"," e2"," 3"," 4"," 5"], padder = " "}
+deggar pad = traverse (:- pad)
+
+lines0 = ["om", "mane", "padme", "hum12345"]
+columns0 = [["om"], ["mane", "padme"], ["hum", "12345"]]
+measureList :: Lengthable a => [a] -> (Natural, [(Natural, a)])
+measureList =
+ List.mapAccumL (\acc s -> let len = length s in (max len acc, (len, s))) 0
+measureVert :: Lengthable a => [a] -> (Natural, [(Natural, a)])
+measureVert ss = (fromIntegral $ List.length ss, (1,) <$> ss)
+
+-- >>> padList (\n -> List.replicate (fromIntegral n) ' ') (measureList lines0)
+-- ["om ","mane ","padme ","hum12345"]
+-- >>> (measureVert lines0)
+-- (4,[(1,"om"),(1,"mane"),(1,"padme"),(1,"hum12345")])
+padList :: Semigroup o => (Natural -> o) -> (Natural, [(Natural, o)]) -> [o]
+padList pad (inpLen, inp) =
+ fmap
+ ( \(itemLen, item) -> case inpLen `minusNaturalMaybe` itemLen of
+ Nothing -> item
+ Just padLen -> item <> pad padLen
+ )
+ inp
+
+whites :: Integral a => a -> [Char]
+whites n = List.replicate (fromIntegral n) ' '
+withLength :: Lengthable a => [a] -> (Natural, [a])
+withLength x = (sum $ length <$> x, x)
+
+text0 :: [Chunk (Line [Chunk (Word String)])]
+text0 = convert @String "123 45\n67 89\n 10 11 12"
+
+{- |
+ >>> justi JustifyCenter text0
+ [Item (Line [Spaces 1,Item (Word "123"),Spaces 1,Item (Word "45"),Spaces 1]),Item (Line [Spaces 1,Item (Word "67"),Spaces 1,Item (Word "89"),Spaces 2]),Item (Line [Spaces 0,Item (Word "10"),Spaces 1,Item (Word "11"),Spaces 1,Item (Word "12"),Spaces 0])]
+-}
+
+-- >>> justi JustifyStart text0
+-- [Item (Line [Item (Word "123"),Spaces 1,Item (Word "45"),Spaces 2]),Item (Line [Item (Word "67"),Spaces 1,Item (Word "89"),Spaces 3]),Item (Line [Item (Word "10"),Spaces 1,Item (Word "11"),Spaces 1,Item (Word "12"),Spaces 0])]
+
+-- >>> justi JustifyEnd text0
+-- [Item (Line [Spaces 2,Item (Word "123"),Spaces 1,Item (Word "45")]),Item (Line [Spaces 3,Item (Word "67"),Spaces 1,Item (Word "89")]),Item (Line [Spaces 0,Item (Word "10"),Spaces 1,Item (Word "11"),Spaces 1,Item (Word "12")])]
+
+-- >>> justi JustifySpaceBetween text0
+-- [Item (Line [Item (Word "123"),Spaces 2,Item (Word "45")]),Item (Line [Item (Word "67"),Spaces 2,Item (Word "89")]),Item (Line [Item (Word "10"),Spaces 1,Item (Word "11"),Spaces 1,Item (Word "12")])]
+{-
+justi ::
+ Semigroup o =>
+ Lengthable o =>
+ Justify ->
+ [Chunk [Chunk o]] ->
+ [Chunk [Chunk o]]
+justi jus ls =
+ (<$> ls) $
+ \lineChunk ->
+ (<$> lineChunk) $ \li -> (<$> li) $ \l ->
+ justifyChunks jus minWitdh (length l, l)
+ where
+ --(maxCols, maxWidths, rows) = List.foldr go (0, List.repeat 0, []) ls
+ minWitdh :: Natural
+ minWitdh =
+ List.foldl'
+ ( \acc -> \case
+ ChunkInvisible{} -> acc
+ ChunkItem o -> length o
+ ChunkSpaces{} -> acc
+ )
+ 0
+ ls
+-}
+-- formatChunks ::
+-- forall o.
+-- Semigroup o =>
+-- Justify ->
+-- Justify ->
+-- {-vertLimit-} Natural ->
+-- {-horizLimit-} Natural ->
+-- [Chunk [Measured (Chunk o)]] ->
+-- {-lines-} Measured [Chunk {-words-} (Measured [Chunk o])]
+-- formatChunks vertJus horizJus vertLimit horizLimit vertChunks =
+-- justifyChunks
+-- DirectionHorizontal
+-- vertJus
+-- vertLimit
+-- $ fmap (\(v,cs) -> Measured 0 v cs)
+-- $ List.mapAccumL
+-- ( \vertDim vertChunk ->
+-- case vertChunk of
+-- ChunkInvisible o -> (vertDim, ChunkInvisible o)
+-- ChunkSpaces s -> (vertDim + s, ChunkSpaces s)
+-- ChunkItem vertItem ->
+-- ( vertDim + horizHeight
+-- , ChunkItem $
+-- --Measured horizLimit horizHeight
+-- justifyChunks
+-- DirectionVertical
+-- horizJus
+-- horizLimit
+-- vertItem
+-- )
+-- where
+-- horizHeight = maxOn' measuredHeight vertItem
+-- )
+-- 0
+-- vertChunks
+
+{- | A strict version of 'sum', using a custom valuation function.
+
+ > sumOn' read ["1", "2", "3"] == 6
+-}
+sumOn' :: Num b => (a -> b) -> [a] -> b
+sumOn' f = List.foldl' (\acc x -> acc + f x) 0
+
+maxOn' :: (a -> Natural) -> [a] -> Natural
+maxOn' f = List.foldl' (\acc x -> acc `max` f x) 0
+
+{- | A version of 'maximum' where the comparison is done on some extracted value.
+ Raises an error if the list is empty. Only calls the function once per element.
+
+ > maximumOn id [] == undefined
+ > maximumOn length ["test","extra","a"] == "extra"
+-}
+maximumOn :: Ord b => (a -> b) -> [a] -> a
+maximumOn f [] = error "Data.List.Extra.maximumOn: empty list"
+maximumOn f (x : xs) = g x (f x) xs
+ where
+ g v mv [] = v
+ g v mv (x : xs)
+ | mx > mv = g x mx xs
+ | otherwise = g v mv xs
+ where
+ mx = f x
+
+{-
+>>> text0
+[Item (Line [Item (Word "123"),Spaces 1,Item (Word "45")]),Item (Line [Item (Word "67")]),Item (Line [Item (Word "89"),Spaces 1,Item (Word "10")])]
+
+>>> length text0
+3
+
+>>> justifyChunks JustifyCenter 30 (length text0, text0)
+[Spaces 13,Item (Line [Item (Word "123"),Spaces 1,Item (Word "45")]),Item (Line [Item (Word "67")]),Item (Line [Item (Word "89"),Spaces 1,Item (Word "10")]),Spaces 14]
+
+>>> justifyChunks JustifySpaceBetween 30 (length text0, text0)
+[Item (Line [Item (Word "123"),Spaces 1,Item (Word "45")]),Item (Line [Item (Word "67")]),Item (Line [Item (Word "89"),Spaces 1,Item (Word "10")])]
+-}
+justifyChunks ::
+ Semigroup o =>
+ Direction ->
+ Justify ->
+ Natural ->
+ [Measured (Chunk o)] ->
+ Measured [Chunk o]
+justifyChunks dir jus limitDim measuredChunks =
+ let chunksDim = List.sum (mea <$> measuredChunks)
+ chunks = unMeasured <$> measuredChunks
+ mea = case dir of
+ DirectionHorizontal -> measuredVertical
+ DirectionVertical -> measuredHorizontal
+ in Measured
+ { measuredHorizontal = 0
+ , measuredVertical = 0
+ , unMeasured = case limitDim `minusNaturalMaybe` chunksDim of
+ Nothing -> chunks
+ Just padLen ->
+ case jus of
+ JustifyStart -> chunks <> [ChunkSpaces padLen]
+ JustifyEnd -> [ChunkSpaces padLen] <> chunks
+ JustifyCenter -> [ChunkSpaces halfPadLen] <> chunks <> [ChunkSpaces (padLen - halfPadLen)]
+ where
+ -- NOTE: may be 0
+ halfPadLen = padLen `div` 2
+ JustifySpaceBetween ->
+ if itemsCount > 0
+ then spaceBetweenItems chunks padLens
+ else chunks <> [ChunkSpaces padLen]
+ where
+ itemsCount = countItems chunks
+ padLens = justifyPadding padLen itemsCount
+ spaceBetweenItems :: [Chunk o] -> [Natural] -> [Chunk o]
+ spaceBetweenItems (x : xs) pads@(p : ps) =
+ case x of
+ ChunkSpaces _w -> ChunkSpaces (p + 1) : spaceBetweenItems xs ps
+ _ -> x : spaceBetweenItems xs pads
+ spaceBetweenItems (w : ws) [] = w : spaceBetweenItems ws []
+ spaceBetweenItems [] _os = mempty
+ }
+
+instance IsString (Rect String) where
+ fromString s =
+ Rects
+ { rectDirection = DirectionHorizontal
+ , rectJustify = JustifyStart
+ , rectWrap = False
+ , rectAxis = rectOfWords . wordsNoEmpty <$> lines s
+ }
+ where
+ rectOfWords :: [Word String] -> Rect String
+ rectOfWords ws =
+ Rects
+ { rectDirection = DirectionHorizontal
+ , rectJustify = JustifyStart
+ , rectWrap = False
+ , rectAxis = (\(Word x) -> Rect (fromIntegral (List.length x), [x])) <$> ws
+ }
+
+rect0 :: Rect String
+rect0 =
+ Rects
+ { rectDirection = DirectionHorizontal
+ , rectJustify = JustifyStart
+ , rectWrap = False
+ , rectAxis = ["abc"]
+ }
+rect1 :: Rect String
+rect1 =
+ Rects
+ { rectDirection = DirectionHorizontal
+ , rectJustify = JustifyStart
+ , rectWrap = True
+ , rectAxis = ["abc", "abcd", "abc"]
+ }
+rect2 :: Rect [Char]
+rect2 =
+ Rects
+ { rectDirection = DirectionVertical
+ , rectJustify = JustifyStart
+ , rectWrap = False
+ , rectAxis =
+ [ Rects
+ { rectDirection = DirectionHorizontal
+ , rectJustify = JustifyStart
+ , rectWrap = True
+ , rectAxis = ["123", "4567", "890"]
+ }
+ , Rects
+ { rectDirection = DirectionHorizontal
+ , rectJustify = JustifyStart
+ , rectWrap = True
+ , rectAxis = ["abc", "defg", "hij"]
+ }
+ ]
+ }
+
+{- | @(measureRect limitH limitV rect)@ return the given @(rect)@
+with minimal 'rectMeasure's, trying to fit the given limits when 'rectWrap' is 'True'.
+
+>>> measureRect Nothing Nothing rectH3V1
+Rect {rectContent = "abc"}
+
+>>> measureRect Nothing Nothing rect1
+Rects {rectMeasure = (10,1), rectDirection = DirectionHorizontal, rectJustify = JustifyStart, rectWrap = False, rectAxis = [Rect {rectContent = "abc"},Rect {rectContent = "abcd"},Rect {rectContent = "abc"}]}
+
+>>> measureRect (Just 4) Nothing rect1{rectWrap=True}
+Rects {rectMeasure = (4,3), rectDirection = DirectionHorizontal, rectJustify = JustifyStart, rectWrap = True, rectAxis = [Rect {rectContent = "abc"},Rect {rectContent = "abcd"},Rect {rectContent = "abc"}]}
+
+>>> measureRect (Just 2) Nothing rect1{rectWrap=True, rectDirection = DirectionVertical}
+Rects {rectMeasure = (7,2), rectDirection = DirectionVertical, rectJustify = JustifyStart, rectWrap = True, rectAxis = [Rect {rectContent = "abc"},Rect {rectContent = "abcd"},Rect {rectContent = "abc"}]}
+-}
+
+{- |
+
+>>> renderRect Nothing Nothing rect0
+((3,1),[["abc"]])
+
+>>> renderRect Nothing Nothing rect1
+((10,1),[["abc","abcd","abc"]])
+
+>>> renderRect Nothing Nothing rect2
+((10,2),[["123","4567","890","abc","defg","hij"]])
+
+>>> renderRect (Just 4) Nothing rect2
+((4,6),[["123"," ","abc"," "],["4567","defg"],["890"," ","hij"," "]])
+-}
+renderRect ::
+ forall o.
+ Semigroup o =>
+ Show o =>
+ Dimensionable o =>
+ Padable o =>
+ Maybe Natural ->
+ Maybe Natural ->
+ Rect o ->
+ ((Natural, Natural), [[o]])
+renderRect limitH limitV is =
+ traceShow ("renderRect" :: String, limitH, limitV, is, "res" :: String, res) res
+ where
+ res =
+ case is of
+ Rect (n, ws) -> ((n, 1), [ws])
+ Rects{..} ->
+ let (_finalH, _finalV, maximalH, maximalV, renderedRects) =
+ List.foldl'
+ ( \(currH, currV, maxH, maxV, rectHV) rect ->
+ let (dim, rectRendered) =
+ renderRect
+ (limitH `minusOrZero` currH)
+ (limitV `minusOrZero` currV)
+ rect
+ newDim@(rectH, rectV) =
+ case rectDirection of
+ DirectionHorizontal -> dim
+ DirectionVertical -> swap dim
+ newH = currH + (if currH == 0 || rectDirection == DirectionVertical then 0 else 1) + rectH
+ in case limitH of
+ Just limH
+ -- Limit overflow, and wrapping
+ | limH < newH && rectWrap ->
+ traceShow ("overflow" :: String, limH, newH) $
+ (rectH, maxV, maxH `max` rectH, maxV + rectV, newRectHV)
+ where
+ -- Word goes into a new line
+ newRectHV = [(newDim, rectRendered)] : rectHV
+ -- No limit, or no overflow, or no wrapping
+ _ -> (newH, currV, maxH + rectH, maxV `max` rectV, newRectHV)
+ where
+ -- Word goes into the same line
+ newRectHV = case rectHV of
+ [] -> [[(newDim, rectRendered)]]
+ r : rs -> ((newDim, rectRendered) : r) : rs
+ )
+ (0, 0, 0, 0, [])
+ rectAxis
+ in ( case rectDirection of
+ DirectionHorizontal -> (maximalH, maximalV)
+ DirectionVertical -> (maximalV, maximalH)
+ , concatHV maximalH maximalV (List.reverse $ List.reverse <$> renderedRects)
+ -- (traceShow ("renderedRects" :: String, renderedRects) $ List.reverse renderedRects)
+ )
+ -- concatRender :: [[o]] -> [[o]] -> [[o]]
+ -- concatRender = List.zipWith (<>)
+ -- Should probably change a wrapped H rect into a V rect containing H rects
+ -- Now reads rectAxis from the left to right, top to bottom
+ -- and make each line reach at least maximalH
+ -- case rectJustify of
+ -- JustifyStart ->
+ -- ((\line -> line <> replicate (maximalH - List.length line) ' ') <$> lines)
+ -- <> replicate (maximalV - List.length lines) (replicate maximalH ' ')
+
+ minusOrZero :: Maybe Natural -> Natural -> Maybe Natural
+ minusOrZero Nothing _m = Nothing
+ minusOrZero (Just n) m = minusNaturalMaybe n m <|> Just 0
+
+{-
+@
+
+>>> concatH 10 2 [((3,1), [["123"]])]
+[["123"," "],[" "," "]]
+
+>>> concatH 10 2 [((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]
+[["123","4567"," "],["ab "," "," "]]
+
+>>> concatH 4 3 [((4,3),[["abc"," "],["defg"],["hij"," "]])]
+[["abc"," "],["defg"],["hij"," "]]
+
+>>> concatH 8 3 [((4,3),[["123"," "],["4567"],["890"," "]]), ((4,3),[["abc"," "],["defg"],["hij"," "]])]
+[["123"," ","abc"," "],["4567","defg"],["890"," ","hij"," "]]
+
+@
+-}
+concatH :: Padable o => Show o => Natural -> Natural -> [((Natural, Natural), [[o]])] -> [[o]]
+concatH maxH maxV rs =
+ traceShow
+ ("concatH" :: String, maxH, maxV, rs, "res" :: String, res)
+ res
+ where
+ (res, rsH) =
+ List.foldr
+ ( \((rH, rV), r) (acc, accH) ->
+ let rPlusVPad = case maxV `minusNaturalMaybe` rV of
+ Nothing -> error "concatH: given maxV is lower than the actual maximal V length"
+ Just dV -> r <> List.replicate (fromIntegral dV) [padding rH]
+ in (List.zipWith (<>) rPlusVPad acc, accH + rH)
+ )
+ (List.replicate (fromIntegral maxV) [padding dH | dH /= 0], 0)
+ rs
+ dH =
+ fromMaybe (error "concatH: given maxH is lower than the actual maximal H length ") $
+ maxH `minusNaturalMaybe` rsH
+
+{-
+concatH maxH maxV [] = List.replicate (fromIntegral maxV) [padding maxH | maxH /= 0]
+concatH maxH maxV (((rH, rV), r) : rs) = case maxH `minusNaturalMaybe` rH of
+ Nothing -> error "concatH: given maxH is lower than the actual maximal H length "
+ Just dH -> List.zipWith (<>) rPlusVPad (concatH dH maxV rs)
+ where
+ rPlusVPad = case maxV `minusNaturalMaybe` rV of
+ Nothing -> error "concatH: given maxV is lower than the actual maximal V length"
+ Just dV -> r <> List.replicate (fromIntegral dV) [padding rH]
+-}
+{-
+@
+
+>>> concatV 10 2 [((3,1), [["123"]])]
+[["123"," "],[" "]]
+
+>>> concatV 4 3 [((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]
+[["123"," "],["ab "," "],["4567"]]
+
+>>> concatV 4 3 [((4,3),[["abc"," "],["defg"],["hij"," "]])]
+[["abc"," "],["defg"],["hij"," "]]
+
+>>> concatV 4 6 [((4,3),[["123"," "],["4567"],["890"," "]]), ((4,3),[["abc"," "],["defg"],["hij"," "]])]
+[["123"," "],["4567"],["890"," "],["abc"," "],["defg"],["hij"," "]]
+
+@
+-}
+concatV :: Padable o => Show o => Natural -> Natural -> [((Natural, Natural), [[o]])] -> [[o]]
+concatV maxH maxV [] = List.replicate (fromIntegral maxV) [padding maxH | maxH /= 0]
+concatV maxH maxV (((rH, rV), r) : rs) = case maxV `minusNaturalMaybe` rV of
+ Nothing -> error "concatV: given maxH is lower than actual maximal H length"
+ Just dV -> rPlusHPad <> concatV maxH dV rs
+ where
+ rPlusHPad = case maxH `minusNaturalMaybe` rH of
+ Nothing -> error "concatV: given maxV is lower than actual maximal V length"
+ Just dH
+ | dH == 0 -> r
+ | otherwise -> List.zipWith (<>) r $ List.replicate (fromIntegral rV) [padding dH]
+
+{- |
+@
+>>> concatHV 10 2 [[((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]]
+[["123","4567"," "],["ab "," "," "]]
+
+>>> concatHV 10 3 [[((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]]
+[["123","4567"," "],["ab "," "," "],[" "]]
+
+>>> concatHV 4 6 [ [((4,3),[["123"," "],["4567"],["890"," "]])], [((4,3),[["abc"," "],["defg"],["hij"," "]])] ]
+[["123"," "],["4567"],["890"," "],["abc"," "],["defg"],["hij"," "]]
+-}
+concatHV :: Padable o => Show o => Natural -> Natural -> [[((Natural, Natural), [[o]])]] -> [[o]]
+concatHV maxH maxV rs = concatV maxH maxV $ f <$> rs
+ where
+ f r = ((maxH, rV), concatH maxH rV r)
+ where
+ rV = List.maximum $ snd . fst <$> r
+
+{-
+@
+>>> concatV 10 2 [[((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]]
+[["123","4567"," "],["ab "," "," "]]
+
+>>> concatV 10 3 [[((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]]
+[["123","4567"," "],["ab "," "," "],[" "]]
+
+>>> concatV3 4 6 [ [((4,3),[["123"," "],["4567"],["890"," "]])], [((4,3),[["abc"," "],["defg"],["hij"," "]])] ]
+[["123"," "],["4567"],["890"," "],["abc"," "],["defg"],["hij"," "]]
+concatV :: Padable o => Show o => Natural -> Natural -> [[((Natural, Natural), [[o]])]] -> [[o]]
+concatV maxH maxV rs =
+ traceShow
+ ("concatV" :: String, maxH, maxV, rs, "res" :: String, res)
+ res
+ where
+ (res, rsV) =
+ List.foldr
+ ( \r (acc, accV) ->
+ let rV = List.maximum $ snd . fst <$> r
+ in (concatH maxH rV r <> acc, accV + rV)
+ )
+ (List.replicate (fromIntegral $ maxV - rsV) [padding maxH], 0)
+ rs
+-}
+
+{-
+concatV maxH maxV []
+ | maxV == 0 = []
+ | otherwise = List.replicate (fromIntegral maxV) [padding maxH]
+concatV maxH maxV (r : rs) =
+ List.zipWith (<>) (concatH maxH rV r) (concatV maxH (maxV - rV) rs)
+ where
+ rV = List.maximum $ snd . fst <$> r
+-}
+-- where
+-- paddedR = case maxH `minusNaturalMaybe` rH of
+-- Nothing -> r
+-- Just p -> r <> List.replicate (fromIntegral p) [padding rH]
+
+{- | @('zipWithLongest xPadLen yPadLen xs ys')@ zips together the lists @(xs)@ and @(ys)@
+ padding the @(xs)@ (resp. @(ys)@) elements with a 'padding' of @(xPadLen)@ (resp. @(yPadLen)@)
+ when it is shorter than the corresponding one in @(ys)@ (resp. @(xs)@).
+
+>>> zipWithLongest []
+-}
+zipWithLongest :: Padable o => Natural -> Natural -> [[o]] -> [[o]] -> [[o]]
+zipWithLongest _ _ [] [] = []
+zipWithLongest xP yP (x0 : xs) (y0 : ys) = x0 <> y0 : zipWithLongest xP yP xs ys
+zipWithLongest _xP yP [] ys
+ | yP <= 0 = ys
+ | otherwise = List.map (padding yP :) ys
+zipWithLongest xP _yP xs []
+ | xP <= 0 = xs
+ | otherwise = List.map (<> [padding xP]) xs
+
+class Padable o where
+ padding :: Natural -> o
+instance Padable String where
+ padding n = List.replicate (fromIntegral n) ' '
+
+{-
+>>> renderRect $ measureRect (Just 2) Nothing rect1{rectWrap=True, rectDirection = DirectionVertical}
+["abc","abcd","abc"]
+renderRect :: Dimensionable o => Rect (Natural, Natural) o -> [o]
+renderRect = \case
+ Rect{..} -> [rectContent]
+ Rects{rectMeasure = (measureH, measureV), ..} ->
+ List.concat $ renderRect <$> rectAxis
+-}
+
+-- fitRect :: Rect o -> Measured (Rect o)
+-- fitRect r = case r of
+-- Rect m -> m{unMeasured r}
+-- Rects{..} ->
+-- let rectsCount = fromIntegral $ max 0 $ List.length rectAxis - 1 in
+-- case rectDirection of
+-- DirectionHorizontal ->
+-- Rects
+-- {
+-- rectAxis =
+-- List.mapAccumL
+-- (\r acc ->
+-- let m = fitRect r in
+-- case measuredHeight acc `compare` measuredHeight m of
+-- LT -> undefined -- acc must be justified upto m
+-- EQ -> acc{measureWidth = measuredWidth acc + measuredWidth m}
+-- GT -> undefined -- m must be justified upto acc
+-- ) rectAxis
+-- }
+-- renderRect :: Maybe Natural -> Maybe Natural -> Rect o -> [[o]]
+-- renderRect horizLimit vertLimit = \case
+-- Rect (Measured w h c) -> [[c]]
+-- Rects{..} ->
+-- renderRect rectAxis
+
+-- * Type 'Rect'
+data Rect o
+ = Rect
+ { rectContent :: (Natural, [o])
+ }
+ | Rects
+ { rectDirection :: Direction
+ , rectJustify :: Justify
+ , rectWrap :: Bool
+ , rectAxis :: [Rect o]
+ }
+ deriving (Show)
+
+{-
+instance Dimensionable o => Dimensionable (Rect (Natural, Natural) o) where
+ width = \case
+ Rect{..} -> width rectContent
+ Rects{rectMeasure = (h, _)} -> h
+ height = \case
+ Rect{..} -> height rectContent
+ Rects{rectMeasure = (_, v)} -> v
+-}
+instance Dimensionable String where
+ width s = fromIntegral $ List.maximum $ List.length <$> splitOnChar (== '\n') s
+ height s = fromIntegral $ List.length $ splitOnChar (== '\n') s
+
+data Adjustment o
+ = AdjustmentVariable Natural
+ | AdjustmentFixed o
+
+-- ** Type 'Chunk'
+data Chunk o
+ = ChunkItem !o
+ | -- | Whites preserved to be interleaved
+ -- correctly with 'ChunkInvisible'.
+ ChunkSpaces !Natural
+ | -- | Ignored by the justification but kept in place.
+ -- Used to put ANSI sequences.
+ ChunkInvisible !o
+ deriving (Functor)
+
+runChunk :: Outputable o => Chunk o -> o
+runChunk = \case
+ ChunkInvisible o -> o
+ ChunkItem o -> o
+ ChunkSpaces s -> repeatedChar s ' '
+
+instance Show o => Show (Chunk o) where
+ showsPrec p x =
+ showParen (p > 10) $
+ case x of
+ ChunkInvisible o -> showString "Ignored" . showsPrec 11 o
+ ChunkItem o -> showString "Item " . showsPrec 11 o
+ ChunkSpaces w -> showString "Spaces " . showsPrec 11 w
+
+-- instance Lengthable o => Lengthable (Chunk (Line o)) where
+-- length = \case
+-- ChunkInvisible{} -> 0
+-- ChunkItem o
+-- | isEmpty o -> 0
+-- | otherwise -> 1
+-- ChunkSpaces w -> w
+-- isEmpty = \case
+-- ChunkInvisible{} -> True
+-- ChunkItem o -> isEmpty o
+-- ChunkSpaces w -> w == 0
+-- instance Lengthable o => Lengthable (Chunk (Word o)) where
+-- length = \case
+-- ChunkInvisible{} -> 0
+-- ChunkItem o -> length o
+-- ChunkSpaces w -> w
+-- isEmpty = \case
+-- ChunkInvisible{} -> True
+-- ChunkItem o -> isEmpty o
+-- ChunkSpaces w -> w == 0
+-- instance Lengthable (Chunk o) => Lengthable [Chunk o] where
+-- length = List.foldl' (\acc out -> acc + length out) 0
+-- isEmpty = List.all isEmpty
+
+{- | @('wordsCount' ps)@ returns the number of words in @(ps)@
+ clearly separated by spaces.
+-}
+countItems :: [Chunk o] -> Natural
+countItems = go False 0
+ where
+ go isAdjacentItem acc = \case
+ [] -> acc
+ ChunkInvisible{} : xs -> go isAdjacentItem acc xs
+ ChunkSpaces w : xs
+ | w == 0 -> go isAdjacentItem acc xs
+ | otherwise -> go False acc xs
+ ChunkItem{} : xs ->
+ if isAdjacentItem
+ then go isAdjacentItem acc xs
+ else go True (acc + 1) xs
+
+{- | @('justifyPadding' a b)@ returns the padding lengths
+to reach @(a)@ in @(b)@ pads,
+using the formula: @(a '==' m'*'(q '+' q'+'1) '+' ('r'-'m)'*'(q'+'1) '+' (b'-'r'-'m)'*'q)@
+where @(q+1)@ and @(q)@ are the two padding lengths used and @(m = min (b-r) r)@.
+
+A simple implementation of 'justifyPadding' could be:
+@
+'justifyPadding' a b =
+ 'join' ('List.replicate' m [q,q'+'1])
+ <> ('List.replicate' (r'-'m) (q'+'1)
+ <> ('List.replicate' ((b'-'r)'-'m) q
+ where
+ (q,r) = a`divMod`b
+ m = 'min' (b-r) r
+@
+
+>>> justifyPadding 30 7
+[4,5,4,5,4,4,4]
+-}
+justifyPadding :: Natural -> Natural -> [Natural]
+justifyPadding a b = go r (b - r) -- NOTE: r >= 0 && b-r >= 0 due to 'divMod'
+ where
+ (q, r) = a `quotRemNatural` b
+ go 0 bmr = List.replicate (fromIntegral bmr) q -- when min (b-r) r == b-r
+ go rr 0 = List.replicate (fromIntegral rr) (q + 1) -- when min (b-r) r == r
+ go rr bmr = q : (q + 1) : go (rr `minusNatural` 1) (bmr `minusNatural` 1)
+
+takeExactly :: o -> Natural -> [o] -> [o]
+takeExactly pad len inp =
+ if len <= 0
+ then inp
+ else case inp of
+ [] -> List.replicate (fromIntegral len) pad
+ o : next -> o : takeExactly pad (len - 1) next
+
+-- https://codepen.io/enxaneta/full/adLPwv/
+-- https://github.com/jordwalke/flex/blob/master/src/lib/Layout.re
+data Direction
+ = DirectionHorizontal
+ | DirectionVertical
+ deriving (Eq, Show)
+data Justify
+ = JustifyStart
+ | JustifyEnd
+ | JustifyCenter
+ | JustifyStretch
+ | JustifySpaceBetween
+ | JustifySpaceAround
+ deriving (Show)
+
+{-
+type ItemAlignSelf = Maybe Align
+
+data Container o = Container
+ { containerDirection :: Direction
+ , containerWrap :: Bool
+ , containerAlignItems :: Align
+ , containerJustifyContent :: Align
+ , containerAlignContent :: Align
+ , containerItems :: [Item o]
+ }
+data Item o = Item
+ { itemAlignSelf :: Maybe Align
+ , itemFlexGrow :: Natural
+ , itemFlexShrink :: Natural
+ , itemFlexOrder :: Natural
+ , itemContent :: o
+ }
+
+2. Determine the available main and cross space for the flex items.
+3. Determine the flex base size and hypothetical main size of each item:
+4. Determine the main size of the flex container using the rules of the formatting context in which it participates.
+5. Collect flex items into flex lines
+6. Resolve the flexible lengths of all the flex items to find their used main size.
+7. Determine the hypothetical cross size of each item by performing layout with the used main size and the available space, treating auto as fit-content.
+8. Calculate the cross size of each flex line.
+9. Handle 'align-content: stretch'.
+10. Collapse visibility:collapse items.
+11. Determine the used cross size of each flex item.
+12. Distribute any remaining free space.
+13. Resolve cross-axis auto margins.
+14. Align all flex items along the cross-axis per align-self, if neither of the item’s cross-axis margins are auto.
+15. Determine the flex container’s used cross size
+16. Align all flex lines per align-content.
+
+Resolving Flexible Lengths
+1. Determine the used flex factor.
+2. Size inflexible items.
+3. Calculate initial free space.
+4. Loop:
+ 4.1 Check for flexible items.
+ 4.2 Calculate the remaining free space as for initial free space, above.
+ 4.3 Distribute free space proportional to the flex factors.
+ 4.4 Fix min/max violations.
+ 4.5 Freeze over-flexed items.
+
+-}
+
+-- * Type 'Writer'
+
+-- -- | Church encoded for performance concerns.
+-- -- Kinda like 'ParsecT' in @megaparsec@ but a little bit different
+-- -- due to the use of 'WriterFit' for implementing 'breakingSpace' correctly
+-- -- when in the left hand side of ('<.>').
+-- -- Prepending is done using continuation, like in a difference list.
+-- newtype Writer (o :: Type) a = Writer
+-- { unWriter ::
+-- a ->
+-- {-curr-} WriterInh o ->
+-- {-curr-} WriterState o ->
+-- {-ok-} (({-prepend-} (o -> o {-new-}), WriterState o) -> WriterFit o) ->
+-- WriterFit o
+-- -- NOTE: equivalent to:
+-- -- ReaderT WriterInh (StateT (WriterState o) (Cont (WriterFit o))) (o->o)
+-- }
+--
+-- runWriter :: Monoid o => Writer o a -> a -> o
+-- runWriter x a =
+-- unWriter
+-- x
+-- a
+-- defWriterInh
+-- defWriterState
+-- {-k-} ( \(px, _sx) fits _overflow ->
+-- -- NOTE: if px fits, then appending mempty fits
+-- fits (px mempty)
+-- )
+-- {-fits-} id
+-- {-overflow-} id
+
+-- ** Type 'WriterFit'
+
+{-
+-- | Double continuation to qualify the returned document
+-- as fitting or overflowing the given 'plainInh_width'.
+-- It's like @('Bool',o)@ in a normal style
+-- (a non continuation-passing-style).
+type WriterFit o =
+ {-fits-} (o -> o) ->
+ {-overflow-} (o -> o) ->
+ o
+
+-- ** Type 'WriterInh'
+data WriterInh o = WriterInh
+ { plainInh_width :: !(Maybe Column)
+ , plainInh_justify :: !Bool
+ , plainInh_indent :: !Indent
+ , plainInh_indenting :: !(Writer o ())
+ , plainInh_sgr :: ![SGR]
+ }
+
+defWriterInh :: Monoid o => WriterInh o
+defWriterInh =
+ WriterInh
+ { plainInh_width = Nothing
+ , plainInh_justify = False
+ , plainInh_indent = 0
+ , plainInh_indenting = empty
+ , plainInh_sgr = []
+ }
+
+-- ** Type 'WriterState'
+data WriterState o = WriterState
+ { plainState_buffer :: ![WriterChunk o]
+ , -- | The 'Column' from which the 'plainState_buffer'
+ -- must be written.
+ plainState_bufferStart :: !Column
+ , -- | The 'Width' of the 'plainState_buffer' so far.
+ plainState_bufferWidth :: !Width
+ , -- | The amount of 'Indent' added by 'breakspace'
+ -- that can be reached by breaking the 'space'
+ -- into a 'newlineJustifyingWriter'.
+ plainState_breakIndent :: !Indent
+ }
+ deriving (Show)
+
+defWriterState :: WriterState o
+defWriterState =
+ WriterState
+ { plainState_buffer = mempty
+ , plainState_bufferStart = 0
+ , plainState_bufferWidth = 0
+ , plainState_breakIndent = 0
+ }
+
+-- ** Type 'WriterChunk'
+data WriterChunk o
+ = -- | Ignored by the justification but kept in place.
+ -- Used for instance to put ANSI sequences.
+ WriterChunk_Ignored !o
+ | WriterChunk_Word !(Word o)
+ | -- | 'spaces' preserved to be interleaved
+ -- correctly with 'WriterChunk_Ignored'.
+ WriterChunk_Spaces !Width
+instance Show o => Show (WriterChunk o) where
+ showsPrec p x =
+ showParen (p > 10) $
+ case x of
+ WriterChunk_Ignored o ->
+ showString "Z "
+ . showsPrec 11 o
+ WriterChunk_Word (Word o) ->
+ showString "W "
+ . showsPrec 11 o
+ WriterChunk_Spaces s ->
+ showString "S "
+ . showsPrec 11 s
+instance Lengthable o => Lengthable (WriterChunk o) where
+ length = \case
+ WriterChunk_Ignored{} -> 0
+ WriterChunk_Word o -> length o
+ WriterChunk_Spaces s -> s
+ isEmpty = \case
+ WriterChunk_Ignored{} -> True
+ WriterChunk_Word o -> isEmpty o
+ WriterChunk_Spaces s -> s == 0
+
+--instance From [SGR] o => From [SGR] (WriterChunk o) where
+-- from sgr = WriterChunk_Ignored (from sgr)
+
+instance Emptyable (Writer o) where
+ empty = Writer $ \_a _inh st k -> k (id, st)
+
+-}
+
+-- >>> wordsNoEmpty (Line (" a b c "::String))
+-- [Word {unWord = "a"},Word {unWord = "b"},Word {unWord = "c"}]
+instance Convertible String o => Convertible String [Chunk (Line [Chunk (Word o)])] where
+ convert =
+ ( ChunkItem . Line
+ . List.intersperse (ChunkSpaces 1)
+ . (ChunkItem . convert <$>)
+ . wordsNoEmpty
+ <$>
+ )
+ . lines