{-| Module : Client.Image.LineWrap Description : Chat message view Copyright : (c) Eric Mertens, 2016 License : ISC Maintainer : emertens@gmail.com Provides utilities for line wrapping images. -} module Client.Image.LineWrap (lineWrap, lineWrapChat) where import Client.Image.PackedImage import Data.Semigroup import qualified Graphics.Vty.Image as Vty import Graphics.Vty.Attributes import qualified Data.Text.Lazy as L -- | Given an image, break the image up into chunks of at most the -- given width and stack the resulting chunks vertically top-to-bottom. lineWrap :: Int {- ^ terminal width -} -> Vty.Image {- ^ unwrapped image -} -> Vty.Image {- ^ wrapped image -} lineWrap w img | Vty.imageWidth img == 0 = Vty.emptyImage | Vty.imageWidth img <= w = terminate w img | otherwise = terminate w (Vty.cropRight w img) Vty.<-> lineWrap w (Vty.cropLeft (Vty.imageWidth img - w) img) -- | Trailing space with default attributes deals with bug in VTY -- where the formatting will continue past the end of chat messages. -- This adds an extra space if a line doesn't end on the terminal edge. terminate :: Int {- ^ terminal width -} -> Vty.Image {- ^ unwrapped image -} -> Vty.Image {- ^ wrapped image -} terminate n img | Vty.imageWidth img == n = img | otherwise = img Vty.<|> Vty.char defAttr ' ' lineWrapChat :: Int {- ^ terminal width -} -> Maybe Int {- ^ optional indentation -} -> Image' {- ^ unwrapped image -} -> [Image'] {- ^ wrapped image -} lineWrapChat w (Just i) | 2*i <= w = reverse . addPadding i . wordLineWrap w (w-i) lineWrapChat w _ = reverse . wordLineWrap w w addPadding :: Int -> [Image'] -> [Image'] addPadding _ [] = [] addPadding i (x:xs) = x : map indent xs where indent = (string defAttr (replicate i ' ') <>) wordLineWrap :: Int {- ^ first line length -} -> Int {- ^ secondary line length -} -> Image' {- ^ image -} -> [Image'] {- ^ splits -} wordLineWrap w wNext img | imgW == 0 = [] | imgW <= w = [img] | otherwise = l : wordLineWrap wNext wNext (dropSpaces r) where imgW = imageWidth img x:xs = splitOptions img (l,r) = splitImage width img width | x <= w = go x xs | otherwise = w go y [] = min y w go y (z:zs) | z-y > wNext = w | z > w = y | otherwise = go z zs -- | List of image widths suitable for breaking the image on -- that correspond to word breaks. splitOptions :: Image' -> [Int] splitOptions = dropWhile (0==) . scanl1 (\x y -> 1 + x + y) . map (Vty.wcswidth . L.unpack) . L.split (' '==) . imageText -- | Drop the leading spaces from an image dropSpaces :: Image' -> Image' dropSpaces img | n == 0 = img | otherwise = snd (splitImage n img) where n = fromIntegral $ L.length $ L.takeWhile (' '==) $ imageText img