1 {-# LANGUAGE DeriveFunctor #-}
2 {-# OPTIONS_GHC -Wno-orphans #-}
4 module Symantic.Plaintext.Writer2 where
6 import Control.Applicative (Alternative (..), Applicative (..))
8 import Data.Char (Char)
9 import Data.Eq (Eq (..))
10 import Data.Function (($), (.))
11 import Data.Functor (Functor (..), (<$>))
12 import Data.List qualified as List
13 import Data.Maybe (Maybe (..), fromMaybe)
14 import Data.Monoid (Monoid (..))
15 import Data.Ord (Ord (..))
16 import Data.Semigroup (Semigroup (..))
17 import Data.String (IsString (..), String)
18 import Data.Traversable (Traversable (traverse))
24 import Numeric.Natural (Natural)
37 --import qualified Data.Text.Lazy.Builder as TLB
38 --import qualified Data.Text.Lazy.Builder as TLB
40 import Control.Monad (Monad ((>>=)))
41 import Data.Functor.Identity (Identity (..))
42 import Data.Text.Internal.Fusion.Types (RS (RS3))
43 import Data.Tuple (fst, snd, swap)
44 import Debug.Trace (trace, traceShow)
45 import GHC.IO.Exception (IOErrorType (SystemError))
47 import GHC.Real (Integral (div))
48 import Symantic.Plaintext.Classes ()
49 import Symantic.Plaintext.Output
51 data Measured o = Measured
52 { measuredHorizontal :: Natural
53 , measuredVertical :: Natural
56 instance Dimensionable (Measured o) where
57 width = measuredHorizontal
58 height = measuredVertical
60 -- renderRect :: Outputable o => Measured (Rect o) -> [o]
61 -- renderRect Measured{..} = case unMeasured of
64 -- Rects{..} -> case rectDirection of
65 -- DirectionHorizontal -> []
67 resize :: Measured [o] -> [o]
68 resize Measured{..} = undefined
70 -- | Doc: https://personal.cis.strath.ac.uk/conor.mcbride/so-pigworker.pdf#section.4.9
71 data PaddedList a = (:-)
79 instance Functor PaddedList where
81 instance Applicative PaddedList where
83 as :- ap <*> bs :- bp = go as bs :- ap bp
86 go fs [] = ($ bp) <$> fs
87 go (f : fs) (x : xs) = f x : go fs xs
89 -- >>> "om":-' ' <*> "mane":-' '
91 -- >>> "om":-' ' <*> "mane":-' '
92 -- Couldn't match type ‘Char’ with ‘Char -> b’
93 -- Expected type: PaddedList (Char -> b)
94 -- Actual type: PaddedList Char
96 -- >>> deggar ' ' ["om", "mane", "padme", "hum12345"]
97 -- (:-) {padded = ["omph","maau"," ndm"," em1"," e2"," 3"," 4"," 5"], padder = " "}
98 deggar pad = traverse (:- pad)
100 lines0 = ["om", "mane", "padme", "hum12345"]
101 columns0 = [["om"], ["mane", "padme"], ["hum", "12345"]]
102 measureList :: Lengthable a => [a] -> (Natural, [(Natural, a)])
104 List.mapAccumL (\acc s -> let len = length s in (max len acc, (len, s))) 0
105 measureVert :: Lengthable a => [a] -> (Natural, [(Natural, a)])
106 measureVert ss = (fromIntegral $ List.length ss, (1,) <$> ss)
108 -- >>> padList (\n -> List.replicate (fromIntegral n) ' ') (measureList lines0)
109 -- ["om ","mane ","padme ","hum12345"]
110 -- >>> (measureVert lines0)
111 -- (4,[(1,"om"),(1,"mane"),(1,"padme"),(1,"hum12345")])
112 padList :: Semigroup o => (Natural -> o) -> (Natural, [(Natural, o)]) -> [o]
113 padList pad (inpLen, inp) =
115 ( \(itemLen, item) -> case inpLen `minusNaturalMaybe` itemLen of
117 Just padLen -> item <> pad padLen
121 whites :: Integral a => a -> [Char]
122 whites n = List.replicate (fromIntegral n) ' '
123 withLength :: Lengthable a => [a] -> (Natural, [a])
124 withLength x = (sum $ length <$> x, x)
126 text0 :: [Chunk (Line [Chunk (Word String)])]
127 text0 = convert @String "123 45\n67 89\n 10 11 12"
130 >>> justi JustifyCenter text0
131 [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])]
134 -- >>> justi JustifyStart text0
135 -- [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])]
137 -- >>> justi JustifyEnd text0
138 -- [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")])]
140 -- >>> justi JustifySpaceBetween text0
141 -- [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")])]
152 (<$> lineChunk) $ \li -> (<$> li) $ \l ->
153 justifyChunks jus minWitdh (length l, l)
155 --(maxCols, maxWidths, rows) = List.foldr go (0, List.repeat 0, []) ls
160 ChunkInvisible{} -> acc
161 ChunkItem o -> length o
172 -- {-vertLimit-} Natural ->
173 -- {-horizLimit-} Natural ->
174 -- [Chunk [Measured (Chunk o)]] ->
175 -- {-lines-} Measured [Chunk {-words-} (Measured [Chunk o])]
176 -- formatChunks vertJus horizJus vertLimit horizLimit vertChunks =
178 -- DirectionHorizontal
181 -- $ fmap (\(v,cs) -> Measured 0 v cs)
183 -- ( \vertDim vertChunk ->
185 -- ChunkInvisible o -> (vertDim, ChunkInvisible o)
186 -- ChunkSpaces s -> (vertDim + s, ChunkSpaces s)
187 -- ChunkItem vertItem ->
188 -- ( vertDim + horizHeight
190 -- --Measured horizLimit horizHeight
198 -- horizHeight = maxOn' measuredHeight vertItem
203 {- | A strict version of 'sum', using a custom valuation function.
205 > sumOn' read ["1", "2", "3"] == 6
207 sumOn' :: Num b => (a -> b) -> [a] -> b
208 sumOn' f = List.foldl' (\acc x -> acc + f x) 0
210 maxOn' :: (a -> Natural) -> [a] -> Natural
211 maxOn' f = List.foldl' (\acc x -> acc `max` f x) 0
213 {- | A version of 'maximum' where the comparison is done on some extracted value.
214 Raises an error if the list is empty. Only calls the function once per element.
216 > maximumOn id [] == undefined
217 > maximumOn length ["test","extra","a"] == "extra"
219 maximumOn :: Ord b => (a -> b) -> [a] -> a
220 maximumOn f [] = error "Data.List.Extra.maximumOn: empty list"
221 maximumOn f (x : xs) = g x (f x) xs
225 | mx > mv = g x mx xs
226 | otherwise = g v mv xs
232 [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")])]
237 >>> justifyChunks JustifyCenter 30 (length text0, text0)
238 [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]
240 >>> justifyChunks JustifySpaceBetween 30 (length text0, text0)
241 [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")])]
248 [Measured (Chunk o)] ->
250 justifyChunks dir jus limitDim measuredChunks =
251 let chunksDim = List.sum (mea <$> measuredChunks)
252 chunks = unMeasured <$> measuredChunks
254 DirectionHorizontal -> measuredVertical
255 DirectionVertical -> measuredHorizontal
257 { measuredHorizontal = 0
258 , measuredVertical = 0
259 , unMeasured = case limitDim `minusNaturalMaybe` chunksDim of
263 JustifyStart -> chunks <> [ChunkSpaces padLen]
264 JustifyEnd -> [ChunkSpaces padLen] <> chunks
265 JustifyCenter -> [ChunkSpaces halfPadLen] <> chunks <> [ChunkSpaces (padLen - halfPadLen)]
268 halfPadLen = padLen `div` 2
269 JustifySpaceBetween ->
271 then spaceBetweenItems chunks padLens
272 else chunks <> [ChunkSpaces padLen]
274 itemsCount = countItems chunks
275 padLens = justifyPadding padLen itemsCount
276 spaceBetweenItems :: [Chunk o] -> [Natural] -> [Chunk o]
277 spaceBetweenItems (x : xs) pads@(p : ps) =
279 ChunkSpaces _w -> ChunkSpaces (p + 1) : spaceBetweenItems xs ps
280 _ -> x : spaceBetweenItems xs pads
281 spaceBetweenItems (w : ws) [] = w : spaceBetweenItems ws []
282 spaceBetweenItems [] _os = mempty
285 instance IsString (Rect String) where
288 { rectDirection = DirectionHorizontal
289 , rectJustify = JustifyStart
291 , rectAxis = rectOfWords . wordsNoEmpty <$> lines s
294 rectOfWords :: [Word String] -> Rect String
297 { rectDirection = DirectionHorizontal
298 , rectJustify = JustifyStart
300 , rectAxis = (\(Word x) -> Rect (fromIntegral (List.length x), [x])) <$> ws
306 { rectDirection = DirectionHorizontal
307 , rectJustify = JustifyStart
314 { rectDirection = DirectionHorizontal
315 , rectJustify = JustifyStart
317 , rectAxis = ["abc", "abcd", "abc"]
322 { rectDirection = DirectionVertical
323 , rectJustify = JustifyStart
327 { rectDirection = DirectionHorizontal
328 , rectJustify = JustifyStart
330 , rectAxis = ["123", "4567", "890"]
333 { rectDirection = DirectionHorizontal
334 , rectJustify = JustifyStart
336 , rectAxis = ["abc", "defg", "hij"]
341 {- | @(measureRect limitH limitV rect)@ return the given @(rect)@
342 with minimal 'rectMeasure's, trying to fit the given limits when 'rectWrap' is 'True'.
344 >>> measureRect Nothing Nothing rectH3V1
345 Rect {rectContent = "abc"}
347 >>> measureRect Nothing Nothing rect1
348 Rects {rectMeasure = (10,1), rectDirection = DirectionHorizontal, rectJustify = JustifyStart, rectWrap = False, rectAxis = [Rect {rectContent = "abc"},Rect {rectContent = "abcd"},Rect {rectContent = "abc"}]}
350 >>> measureRect (Just 4) Nothing rect1{rectWrap=True}
351 Rects {rectMeasure = (4,3), rectDirection = DirectionHorizontal, rectJustify = JustifyStart, rectWrap = True, rectAxis = [Rect {rectContent = "abc"},Rect {rectContent = "abcd"},Rect {rectContent = "abc"}]}
353 >>> measureRect (Just 2) Nothing rect1{rectWrap=True, rectDirection = DirectionVertical}
354 Rects {rectMeasure = (7,2), rectDirection = DirectionVertical, rectJustify = JustifyStart, rectWrap = True, rectAxis = [Rect {rectContent = "abc"},Rect {rectContent = "abcd"},Rect {rectContent = "abc"}]}
359 >>> renderRect Nothing Nothing rect0
362 >>> renderRect Nothing Nothing rect1
363 ((10,1),[["abc","abcd","abc"]])
365 >>> renderRect Nothing Nothing rect2
366 ((10,2),[["123","4567","890","abc","defg","hij"]])
368 >>> renderRect (Just 4) Nothing rect2
369 ((4,6),[["123"," ","abc"," "],["4567","defg"],["890"," ","hij"," "]])
380 ((Natural, Natural), [[o]])
381 renderRect limitH limitV is =
382 traceShow ("renderRect" :: String, limitH, limitV, is, "res" :: String, res) res
386 Rect (n, ws) -> ((n, 1), [ws])
388 let (_finalH, _finalV, maximalH, maximalV, renderedRects) =
390 ( \(currH, currV, maxH, maxV, rectHV) rect ->
391 let (dim, rectRendered) =
393 (limitH `minusOrZero` currH)
394 (limitV `minusOrZero` currV)
396 newDim@(rectH, rectV) =
397 case rectDirection of
398 DirectionHorizontal -> dim
399 DirectionVertical -> swap dim
400 newH = currH + (if currH == 0 || rectDirection == DirectionVertical then 0 else 1) + rectH
403 -- Limit overflow, and wrapping
404 | limH < newH && rectWrap ->
405 traceShow ("overflow" :: String, limH, newH) $
406 (rectH, maxV, maxH `max` rectH, maxV + rectV, newRectHV)
408 -- Word goes into a new line
409 newRectHV = [(newDim, rectRendered)] : rectHV
410 -- No limit, or no overflow, or no wrapping
411 _ -> (newH, currV, maxH + rectH, maxV `max` rectV, newRectHV)
413 -- Word goes into the same line
414 newRectHV = case rectHV of
415 [] -> [[(newDim, rectRendered)]]
416 r : rs -> ((newDim, rectRendered) : r) : rs
420 in ( case rectDirection of
421 DirectionHorizontal -> (maximalH, maximalV)
422 DirectionVertical -> (maximalV, maximalH)
423 , concatHV maximalH maximalV (List.reverse $ List.reverse <$> renderedRects)
424 -- (traceShow ("renderedRects" :: String, renderedRects) $ List.reverse renderedRects)
426 -- concatRender :: [[o]] -> [[o]] -> [[o]]
427 -- concatRender = List.zipWith (<>)
428 -- Should probably change a wrapped H rect into a V rect containing H rects
429 -- Now reads rectAxis from the left to right, top to bottom
430 -- and make each line reach at least maximalH
431 -- case rectJustify of
433 -- ((\line -> line <> replicate (maximalH - List.length line) ' ') <$> lines)
434 -- <> replicate (maximalV - List.length lines) (replicate maximalH ' ')
436 minusOrZero :: Maybe Natural -> Natural -> Maybe Natural
437 minusOrZero Nothing _m = Nothing
438 minusOrZero (Just n) m = minusNaturalMaybe n m <|> Just 0
443 >>> concatH 10 2 [((3,1), [["123"]])]
444 [["123"," "],[" "," "]]
446 >>> concatH 10 2 [((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]
447 [["123","4567"," "],["ab "," "," "]]
449 >>> concatH 4 3 [((4,3),[["abc"," "],["defg"],["hij"," "]])]
450 [["abc"," "],["defg"],["hij"," "]]
452 >>> concatH 8 3 [((4,3),[["123"," "],["4567"],["890"," "]]), ((4,3),[["abc"," "],["defg"],["hij"," "]])]
453 [["123"," ","abc"," "],["4567","defg"],["890"," ","hij"," "]]
457 concatH :: Padable o => Show o => Natural -> Natural -> [((Natural, Natural), [[o]])] -> [[o]]
458 concatH maxH maxV rs =
460 ("concatH" :: String, maxH, maxV, rs, "res" :: String, res)
465 ( \((rH, rV), r) (acc, accH) ->
466 let rPlusVPad = case maxV `minusNaturalMaybe` rV of
467 Nothing -> error "concatH: given maxV is lower than the actual maximal V length"
468 Just dV -> r <> List.replicate (fromIntegral dV) [padding rH]
469 in (List.zipWith (<>) rPlusVPad acc, accH + rH)
471 (List.replicate (fromIntegral maxV) [padding dH | dH /= 0], 0)
474 fromMaybe (error "concatH: given maxH is lower than the actual maximal H length ") $
475 maxH `minusNaturalMaybe` rsH
478 concatH maxH maxV [] = List.replicate (fromIntegral maxV) [padding maxH | maxH /= 0]
479 concatH maxH maxV (((rH, rV), r) : rs) = case maxH `minusNaturalMaybe` rH of
480 Nothing -> error "concatH: given maxH is lower than the actual maximal H length "
481 Just dH -> List.zipWith (<>) rPlusVPad (concatH dH maxV rs)
483 rPlusVPad = case maxV `minusNaturalMaybe` rV of
484 Nothing -> error "concatH: given maxV is lower than the actual maximal V length"
485 Just dV -> r <> List.replicate (fromIntegral dV) [padding rH]
490 >>> concatV 10 2 [((3,1), [["123"]])]
493 >>> concatV 4 3 [((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]
494 [["123"," "],["ab "," "],["4567"]]
496 >>> concatV 4 3 [((4,3),[["abc"," "],["defg"],["hij"," "]])]
497 [["abc"," "],["defg"],["hij"," "]]
499 >>> concatV 4 6 [((4,3),[["123"," "],["4567"],["890"," "]]), ((4,3),[["abc"," "],["defg"],["hij"," "]])]
500 [["123"," "],["4567"],["890"," "],["abc"," "],["defg"],["hij"," "]]
504 concatV :: Padable o => Show o => Natural -> Natural -> [((Natural, Natural), [[o]])] -> [[o]]
505 concatV maxH maxV [] = List.replicate (fromIntegral maxV) [padding maxH | maxH /= 0]
506 concatV maxH maxV (((rH, rV), r) : rs) = case maxV `minusNaturalMaybe` rV of
507 Nothing -> error "concatV: given maxH is lower than actual maximal H length"
508 Just dV -> rPlusHPad <> concatV maxH dV rs
510 rPlusHPad = case maxH `minusNaturalMaybe` rH of
511 Nothing -> error "concatV: given maxV is lower than actual maximal V length"
514 | otherwise -> List.zipWith (<>) r $ List.replicate (fromIntegral rV) [padding dH]
518 >>> concatHV 10 2 [[((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]]
519 [["123","4567"," "],["ab "," "," "]]
521 >>> concatHV 10 3 [[((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]]
522 [["123","4567"," "],["ab "," "," "],[" "]]
524 >>> concatHV 4 6 [ [((4,3),[["123"," "],["4567"],["890"," "]])], [((4,3),[["abc"," "],["defg"],["hij"," "]])] ]
525 [["123"," "],["4567"],["890"," "],["abc"," "],["defg"],["hij"," "]]
527 concatHV :: Padable o => Show o => Natural -> Natural -> [[((Natural, Natural), [[o]])]] -> [[o]]
528 concatHV maxH maxV rs = concatV maxH maxV $ f <$> rs
530 f r = ((maxH, rV), concatH maxH rV r)
532 rV = List.maximum $ snd . fst <$> r
536 >>> concatV 10 2 [[((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]]
537 [["123","4567"," "],["ab "," "," "]]
539 >>> concatV 10 3 [[((3,2), [["123"], ["ab "]]), ((4,1), [["4567"]])]]
540 [["123","4567"," "],["ab "," "," "],[" "]]
542 >>> concatV3 4 6 [ [((4,3),[["123"," "],["4567"],["890"," "]])], [((4,3),[["abc"," "],["defg"],["hij"," "]])] ]
543 [["123"," "],["4567"],["890"," "],["abc"," "],["defg"],["hij"," "]]
544 concatV :: Padable o => Show o => Natural -> Natural -> [[((Natural, Natural), [[o]])]] -> [[o]]
545 concatV maxH maxV rs =
547 ("concatV" :: String, maxH, maxV, rs, "res" :: String, res)
553 let rV = List.maximum $ snd . fst <$> r
554 in (concatH maxH rV r <> acc, accV + rV)
556 (List.replicate (fromIntegral $ maxV - rsV) [padding maxH], 0)
563 | otherwise = List.replicate (fromIntegral maxV) [padding maxH]
564 concatV maxH maxV (r : rs) =
565 List.zipWith (<>) (concatH maxH rV r) (concatV maxH (maxV - rV) rs)
567 rV = List.maximum $ snd . fst <$> r
570 -- paddedR = case maxH `minusNaturalMaybe` rH of
572 -- Just p -> r <> List.replicate (fromIntegral p) [padding rH]
574 {- | @('zipWithLongest xPadLen yPadLen xs ys')@ zips together the lists @(xs)@ and @(ys)@
575 padding the @(xs)@ (resp. @(ys)@) elements with a 'padding' of @(xPadLen)@ (resp. @(yPadLen)@)
576 when it is shorter than the corresponding one in @(ys)@ (resp. @(xs)@).
578 >>> zipWithLongest []
580 zipWithLongest :: Padable o => Natural -> Natural -> [[o]] -> [[o]] -> [[o]]
581 zipWithLongest _ _ [] [] = []
582 zipWithLongest xP yP (x0 : xs) (y0 : ys) = x0 <> y0 : zipWithLongest xP yP xs ys
583 zipWithLongest _xP yP [] ys
585 | otherwise = List.map (padding yP :) ys
586 zipWithLongest xP _yP xs []
588 | otherwise = List.map (<> [padding xP]) xs
590 class Padable o where
591 padding :: Natural -> o
592 instance Padable String where
593 padding n = List.replicate (fromIntegral n) ' '
596 >>> renderRect $ measureRect (Just 2) Nothing rect1{rectWrap=True, rectDirection = DirectionVertical}
598 renderRect :: Dimensionable o => Rect (Natural, Natural) o -> [o]
600 Rect{..} -> [rectContent]
601 Rects{rectMeasure = (measureH, measureV), ..} ->
602 List.concat $ renderRect <$> rectAxis
605 -- fitRect :: Rect o -> Measured (Rect o)
606 -- fitRect r = case r of
607 -- Rect m -> m{unMeasured r}
609 -- let rectsCount = fromIntegral $ max 0 $ List.length rectAxis - 1 in
610 -- case rectDirection of
611 -- DirectionHorizontal ->
617 -- let m = fitRect r in
618 -- case measuredHeight acc `compare` measuredHeight m of
619 -- LT -> undefined -- acc must be justified upto m
620 -- EQ -> acc{measureWidth = measuredWidth acc + measuredWidth m}
621 -- GT -> undefined -- m must be justified upto acc
624 -- renderRect :: Maybe Natural -> Maybe Natural -> Rect o -> [[o]]
625 -- renderRect horizLimit vertLimit = \case
626 -- Rect (Measured w h c) -> [[c]]
628 -- renderRect rectAxis
633 { rectContent :: (Natural, [o])
636 { rectDirection :: Direction
637 , rectJustify :: Justify
639 , rectAxis :: [Rect o]
644 instance Dimensionable o => Dimensionable (Rect (Natural, Natural) o) where
646 Rect{..} -> width rectContent
647 Rects{rectMeasure = (h, _)} -> h
649 Rect{..} -> height rectContent
650 Rects{rectMeasure = (_, v)} -> v
652 instance Dimensionable String where
653 width s = fromIntegral $ List.maximum $ List.length <$> splitOnChar (== '\n') s
654 height s = fromIntegral $ List.length $ splitOnChar (== '\n') s
657 = AdjustmentVariable Natural
663 | -- | Whites preserved to be interleaved
664 -- correctly with 'ChunkInvisible'.
666 | -- | Ignored by the justification but kept in place.
667 -- Used to put ANSI sequences.
671 runChunk :: Outputable o => Chunk o -> o
673 ChunkInvisible o -> o
675 ChunkSpaces s -> repeatedChar s ' '
677 instance Show o => Show (Chunk o) where
681 ChunkInvisible o -> showString "Ignored" . showsPrec 11 o
682 ChunkItem o -> showString "Item " . showsPrec 11 o
683 ChunkSpaces w -> showString "Spaces " . showsPrec 11 w
685 -- instance Lengthable o => Lengthable (Chunk (Line o)) where
687 -- ChunkInvisible{} -> 0
691 -- ChunkSpaces w -> w
693 -- ChunkInvisible{} -> True
694 -- ChunkItem o -> isEmpty o
695 -- ChunkSpaces w -> w == 0
696 -- instance Lengthable o => Lengthable (Chunk (Word o)) where
698 -- ChunkInvisible{} -> 0
699 -- ChunkItem o -> length o
700 -- ChunkSpaces w -> w
702 -- ChunkInvisible{} -> True
703 -- ChunkItem o -> isEmpty o
704 -- ChunkSpaces w -> w == 0
705 -- instance Lengthable (Chunk o) => Lengthable [Chunk o] where
706 -- length = List.foldl' (\acc out -> acc + length out) 0
707 -- isEmpty = List.all isEmpty
709 {- | @('wordsCount' ps)@ returns the number of words in @(ps)@
710 clearly separated by spaces.
712 countItems :: [Chunk o] -> Natural
713 countItems = go False 0
715 go isAdjacentItem acc = \case
717 ChunkInvisible{} : xs -> go isAdjacentItem acc xs
719 | w == 0 -> go isAdjacentItem acc xs
720 | otherwise -> go False acc xs
723 then go isAdjacentItem acc xs
724 else go True (acc + 1) xs
726 {- | @('justifyPadding' a b)@ returns the padding lengths
727 to reach @(a)@ in @(b)@ pads,
728 using the formula: @(a '==' m'*'(q '+' q'+'1) '+' ('r'-'m)'*'(q'+'1) '+' (b'-'r'-'m)'*'q)@
729 where @(q+1)@ and @(q)@ are the two padding lengths used and @(m = min (b-r) r)@.
731 A simple implementation of 'justifyPadding' could be:
733 'justifyPadding' a b =
734 'join' ('List.replicate' m [q,q'+'1])
735 <> ('List.replicate' (r'-'m) (q'+'1)
736 <> ('List.replicate' ((b'-'r)'-'m) q
742 >>> justifyPadding 30 7
745 justifyPadding :: Natural -> Natural -> [Natural]
746 justifyPadding a b = go r (b - r) -- NOTE: r >= 0 && b-r >= 0 due to 'divMod'
748 (q, r) = a `quotRemNatural` b
749 go 0 bmr = List.replicate (fromIntegral bmr) q -- when min (b-r) r == b-r
750 go rr 0 = List.replicate (fromIntegral rr) (q + 1) -- when min (b-r) r == r
751 go rr bmr = q : (q + 1) : go (rr `minusNatural` 1) (bmr `minusNatural` 1)
753 takeExactly :: o -> Natural -> [o] -> [o]
754 takeExactly pad len inp =
758 [] -> List.replicate (fromIntegral len) pad
759 o : next -> o : takeExactly pad (len - 1) next
761 -- https://codepen.io/enxaneta/full/adLPwv/
762 -- https://github.com/jordwalke/flex/blob/master/src/lib/Layout.re
764 = DirectionHorizontal
772 | JustifySpaceBetween
777 type ItemAlignSelf = Maybe Align
779 data Container o = Container
780 { containerDirection :: Direction
781 , containerWrap :: Bool
782 , containerAlignItems :: Align
783 , containerJustifyContent :: Align
784 , containerAlignContent :: Align
785 , containerItems :: [Item o]
788 { itemAlignSelf :: Maybe Align
789 , itemFlexGrow :: Natural
790 , itemFlexShrink :: Natural
791 , itemFlexOrder :: Natural
795 2. Determine the available main and cross space for the flex items.
796 3. Determine the flex base size and hypothetical main size of each item:
797 4. Determine the main size of the flex container using the rules of the formatting context in which it participates.
798 5. Collect flex items into flex lines
799 6. Resolve the flexible lengths of all the flex items to find their used main size.
800 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.
801 8. Calculate the cross size of each flex line.
802 9. Handle 'align-content: stretch'.
803 10. Collapse visibility:collapse items.
804 11. Determine the used cross size of each flex item.
805 12. Distribute any remaining free space.
806 13. Resolve cross-axis auto margins.
807 14. Align all flex items along the cross-axis per align-self, if neither of the item’s cross-axis margins are auto.
808 15. Determine the flex container’s used cross size
809 16. Align all flex lines per align-content.
811 Resolving Flexible Lengths
812 1. Determine the used flex factor.
813 2. Size inflexible items.
814 3. Calculate initial free space.
816 4.1 Check for flexible items.
817 4.2 Calculate the remaining free space as for initial free space, above.
818 4.3 Distribute free space proportional to the flex factors.
819 4.4 Fix min/max violations.
820 4.5 Freeze over-flexed items.
826 -- -- | Church encoded for performance concerns.
827 -- -- Kinda like 'ParsecT' in @megaparsec@ but a little bit different
828 -- -- due to the use of 'WriterFit' for implementing 'breakingSpace' correctly
829 -- -- when in the left hand side of ('<.>').
830 -- -- Prepending is done using continuation, like in a difference list.
831 -- newtype Writer (o :: Type) a = Writer
834 -- {-curr-} WriterInh o ->
835 -- {-curr-} WriterState o ->
836 -- {-ok-} (({-prepend-} (o -> o {-new-}), WriterState o) -> WriterFit o) ->
838 -- -- NOTE: equivalent to:
839 -- -- ReaderT WriterInh (StateT (WriterState o) (Cont (WriterFit o))) (o->o)
842 -- runWriter :: Monoid o => Writer o a -> a -> o
849 -- {-k-} ( \(px, _sx) fits _overflow ->
850 -- -- NOTE: if px fits, then appending mempty fits
856 -- ** Type 'WriterFit'
859 -- | Double continuation to qualify the returned document
860 -- as fitting or overflowing the given 'plainInh_width'.
861 -- It's like @('Bool',o)@ in a normal style
862 -- (a non continuation-passing-style).
865 {-overflow-} (o -> o) ->
868 -- ** Type 'WriterInh'
869 data WriterInh o = WriterInh
870 { plainInh_width :: !(Maybe Column)
871 , plainInh_justify :: !Bool
872 , plainInh_indent :: !Indent
873 , plainInh_indenting :: !(Writer o ())
874 , plainInh_sgr :: ![SGR]
877 defWriterInh :: Monoid o => WriterInh o
880 { plainInh_width = Nothing
881 , plainInh_justify = False
882 , plainInh_indent = 0
883 , plainInh_indenting = empty
887 -- ** Type 'WriterState'
888 data WriterState o = WriterState
889 { plainState_buffer :: ![WriterChunk o]
890 , -- | The 'Column' from which the 'plainState_buffer'
892 plainState_bufferStart :: !Column
893 , -- | The 'Width' of the 'plainState_buffer' so far.
894 plainState_bufferWidth :: !Width
895 , -- | The amount of 'Indent' added by 'breakspace'
896 -- that can be reached by breaking the 'space'
897 -- into a 'newlineJustifyingWriter'.
898 plainState_breakIndent :: !Indent
902 defWriterState :: WriterState o
905 { plainState_buffer = mempty
906 , plainState_bufferStart = 0
907 , plainState_bufferWidth = 0
908 , plainState_breakIndent = 0
911 -- ** Type 'WriterChunk'
913 = -- | Ignored by the justification but kept in place.
914 -- Used for instance to put ANSI sequences.
915 WriterChunk_Ignored !o
916 | WriterChunk_Word !(Word o)
917 | -- | 'spaces' preserved to be interleaved
918 -- correctly with 'WriterChunk_Ignored'.
919 WriterChunk_Spaces !Width
920 instance Show o => Show (WriterChunk o) where
924 WriterChunk_Ignored o ->
927 WriterChunk_Word (Word o) ->
930 WriterChunk_Spaces s ->
933 instance Lengthable o => Lengthable (WriterChunk o) where
935 WriterChunk_Ignored{} -> 0
936 WriterChunk_Word o -> length o
937 WriterChunk_Spaces s -> s
939 WriterChunk_Ignored{} -> True
940 WriterChunk_Word o -> isEmpty o
941 WriterChunk_Spaces s -> s == 0
943 --instance From [SGR] o => From [SGR] (WriterChunk o) where
944 -- from sgr = WriterChunk_Ignored (from sgr)
946 instance Emptyable (Writer o) where
947 empty = Writer $ \_a _inh st k -> k (id, st)
951 -- >>> wordsNoEmpty (Line (" a b c "::String))
952 -- [Word {unWord = "a"},Word {unWord = "b"},Word {unWord = "c"}]
953 instance Convertible String o => Convertible String [Chunk (Line [Chunk (Word o)])] where
956 . List.intersperse (ChunkSpaces 1)
957 . (ChunkItem . convert <$>)