on master help sign in
in progress
commite973a0de7f7ae27f7bb3aa04be1789606fec8cdb authorCorey O'Connor <coreyoconnor@gmail.com> dateThu 16 May 2013, 13:44 (13 years ago) parent052cee9
8 files changed+556−469
src/Codec/Binary/UTF8/Width.hs−33 src/Graphics/Text/Width.hs+34 src/Graphics/Vty/Image.hs+196−288 src/Graphics/Vty/Image/Internal.hs+162 src/Graphics/Vty/Picture.hs+3−1 src/Graphics/Vty/Span.hs+129−147 test/current_benchmark_results.txt+16 vty.cabal+16
src/Codec/Binary/UTF8/Width.hsdeleted−33
@@ -1,33 +0,0 @@
1−-- Copyright 2009 Corey O'Connor
2−{-# OPTIONS_GHC -D_XOPEN_SOURCE -fno-cse #-}
3−{-# LANGUAGE ForeignFunctionInterface, BangPatterns #-}
4−module Codec.Binary.UTF8.Width ( wcwidth
5− , wcswidth
6− )
7− where
8−
9−import Foreign.C.Types
10−import Foreign.C.String
11−import Foreign.Storable
12−import Foreign.Ptr
13−
14−import System.IO.Unsafe
15−
16−wcwidth :: Char -> Int
17−wcwidth c = unsafePerformIO (withCWString [c] $! \ws -> do
18− wc <- peek ws
19− let !w = fromIntegral $! wcwidth' wc
20− return w
21− )
22−{-# NOINLINE wcwidth #-}
23−
24−foreign import ccall unsafe "vty_mk_wcwidth" wcwidth' :: CWchar -> CInt
25−
26−wcswidth :: String -> Int
27−wcswidth str = unsafePerformIO (withCWStringLen str $! \(ws, ws_len) -> do
28− let !w = fromIntegral $! wcswidth' ws (fromIntegral ws_len)
29− return w
30− )
31−{-# NOINLINE wcswidth #-}
32−
33−foreign import ccall unsafe "vty_mk_wcswidth" wcswidth' :: Ptr CWchar -> CSize -> CInt
src/Graphics/Text/Width.hsnew file+34
@@ -0,0 +1,34 @@
1+-- Copyright 2009 Corey O'Connor
2+{-# OPTIONS_GHC -D_XOPEN_SOURCE #-}
3+{-# LANGUAGE ForeignFunctionInterface #-}
4+module Graphics.Text.Width ( wcwidth
5+ , wcswidth
6+ , safe_wcwidth
7+ , safe_wcswidth
8+ )
9+ where
10+
11+foreign import ccall unsafe "vty_mk_wcwidth" wcwidth :: Char -> Int
12+
13+wcswidth :: String -> Int
14+wcswidth = sum . map wcwidth
15+
16+-- XXX: Characters with unknown widths occupy 1 column?
17+--
18+-- Not sure if this is actually correct. I presume there is a replacement character that is output
19+-- by the terminal instead of the character and this replacement character is 1 column wide. If this
20+-- is not true for all terminals then a per-terminal replacement character width needs to be
21+-- implemented.
22+
23+-- | Returns the display width of a character. Assumes all characters with unknown widths are 1 width
24+safe_wcwidth :: Char -> Int
25+safe_wcwidth c = case wcwidth c of
26+ i | i < 0 -> 1
27+ | otherwise -> i
28+
29+-- | Returns the display width of a string. Assumes all characters with unknown widths are 1 width
30+safe_wcswidth :: String -> Int
31+safe_wcswidth str = case wcswidth str of
32+ i | i < 0 -> 1
33+ | otherwise -> i
34+
src/Graphics/Vty/Image.hs+196−288
@@ -2,294 +2,140 @@
22{-# LANGUAGE BangPatterns #-}
33{-# LANGUAGE NamedFieldPuns #-}
44{-# LANGUAGE DisambiguateRecordFields #-}
5−{-# OPTIONS_GHC -funbox-strict-fields #-}
6−module Graphics.Vty.Image ( DisplayString
7− , Image(..)
5+module Graphics.Vty.Image ( DisplayText
6+ , Image
87 , image_width
98 , image_height
9+ , horiz_join
1010 , (<|>)
11+ , vert_join
1112 , (<->)
1213 , horiz_cat
1314 , vert_cat
1415 , background_fill
16+ , text
17+ , strict_text
1518 , char
1619 , string
1720 , iso_10646_string
1821 , utf8_string
1922 , utf8_bytestring
23+ , utf8_strict_bytestring
2024 , char_fill
2125 , empty_image
22− , translate
2326 , safe_wcwidth
2427 , safe_wcswidth
2528 , wcwidth
2629 , wcswidth
2730 , crop
31+ , crop_right
32+ , crop_left
33+ , crop_bottom
34+ , crop_top
2835 , pad
36+ , resize
37+ , translate
2938 -- | The possible display attributes used in constructing an `Image`.
3039 , module Graphics.Vty.Attributes
3140 )
3241 where
3342
3443import Graphics.Vty.Attributes
35−
36−import Codec.Binary.UTF8.Width
37−
38−import Codec.Binary.UTF8.String ( decode )
39−
40−import Control.DeepSeq
41−
42−import qualified Data.ByteString as BS
43−import Data.Monoid
44−import qualified Data.Sequence as Seq
45−import qualified Data.String.UTF8 as UTF8
44+import Graphics.Vty.Image.Internal
45+import Graphics.Text.Width
46+
47+import qualified Data.ByteString as B
48+import qualified Data.ByteString.Lazy as BL
49+import qualified Data.Text as T
50+import qualified Data.Text.Encoding as T
51+import qualified Data.Text.Lazy as TL
52+import qualified Data.Text.Lazy.Encoding as TL
4653import Data.Word
4754
4855infixr 5 <|>
4956infixr 4 <->
5057
51−-- | We pair each character with it's display length. This way we only compute the length once per
52−-- character.
53−-- * Though currently the width of some strings is still compute multiple times.
54−type DisplayString = Seq.Seq (Char, Word)
55−
56−-- | An image in VTY defines:
57−--
58−-- * properties required to display the image. These are properties that effect the output image
59−-- but are independent of position
60−--
61−-- * A set of position-dependent text and attribute regions. The possible regions are:
58+-- | combines two images side by side
6259--
63−-- * a point. ( char )
60+-- Combines text chunks where possible. Assures output_width and output_height properties are not
61+-- violated.
6462--
65−-- * a horizontal line of characters with a single attribute. (string, utf8_string,
66−-- utf8_bytestring )
63+-- The result image will have a width equal to the sum of the two images width. And the height will
64+-- equal the largest height of the two images. The area not defined in one image due to a height
65+-- missmatch will be filled with the background pattern.
6766--
68−-- * a fill of a single character. (char_fill)
67+-- TODO: the bg fill is biased towards top to bottom languages(?)
68+horiz_join :: Image -> Image -> Image
69+horiz_join EmptyImage i = i
70+horiz_join i EmptyImage = i
71+horiz_join i_0@(HorizText a_0 t_0 w_0 cw_0) i_1@(HorizText a_1 t_1 w_1 cw_1)
72+ | a_0 == a_1 = HorizText a_0 (TL.append t_0 t_1) (w_0 + w_1) (cw_0 + cw_1)
73+ | otherwise = HorizJoin i_0 i_1 (w_0 + w_1) (image_height i_0 + image_height i_1)
74+horiz_join i_0 i_1
75+ -- If the images are of the same height then no padding is required
76+ | h_0 == h_1 = HorizJoin i_0 i_1 w h_0
77+ -- otherwise one of the images needs to be padded to the right size.
78+ | h_0 < h_1 -- Pad i_0
79+ = let pad_amount = h_1 - h_0
80+ in HorizJoin (VertJoin i_0 (BGFill w_0 pad_amount) w_0 h_1) i_1 w h_1
81+ | h_0 > h_1 -- Pad i_1
82+ = let pad_amount = h_0 - h_1
83+ in HorizJoin i_0 (VertJoin i_1 (BGFill w_1 pad_amount) w_1 h_0) w h_0
84+ where
85+ w_0 = image_width i_0
86+ w_1 = image_width i_1
87+ w = w_0 + w_1
88+ h_0 = image_height i_0
89+ h_1 = image_height i_1
90+horiz_join _ _ = error "horiz_join applied to undefined values."
91+
92+-- | combines two images vertically
6993--
70−-- * a fill of the picture's background. (background_fill)
94+-- The result image will have a height equal to the sum of the heights of both images.
95+-- The width will equal the largest width of the two images.
96+-- The area not defined in one image due to a width missmatch will be filled with the background
97+-- pattern.
7198--
72−-- todo: increase the number of encoded bytestring formats supported.
73−data Image =
74− -- | A horizontal text span is always >= 1 column and has a row height of 1.
75− HorizText
76− { attr :: !Attr
77− -- All character data is stored as Char sequences with the ISO-10646 encoding.
78− , text :: DisplayString
79− , output_width :: !Word -- >= 0
80− , char_width :: !Word -- >= 1
81− }
82− -- | A horizontal join can be constructed between any two images. However a HorizJoin instance is
83− -- required to be between two images of equal height. The horiz_join constructor adds background
84− -- filles to the provided images that assure this is true for the HorizJoin value produced.
85− | HorizJoin
86− { part_left :: Image
87− , part_right :: Image
88− , output_width :: !Word -- >= 1
89− , output_height :: !Word -- >= 1
90− }
91− -- | A veritical join can be constructed between any two images. However a VertJoin instance is
92− -- required to be between two images of equal width. The vert_join constructor adds background
93− -- fills to the provides images that assure this is true for the VertJoin value produced.
94− | VertJoin
95− { part_top :: Image
96− , part_bottom :: Image
97− , output_width :: !Word -- >= 1
98− , output_height :: !Word -- >= 1
99− }
100− -- | A background fill will be filled with the background pattern. The background pattern is
101− -- defined as a property of the Picture this Image is used to form.
102− | BGFill
103− { output_width :: !Word -- >= 1
104− , output_height :: !Word -- >= 1
105− }
106− -- | The combining operators identity constant.
107− -- EmptyImage <|> a = a
108− -- EmptyImage <-> a = a
109− --
110− -- Any image of zero size equals the empty image.
111− | EmptyImage
112− | Translation (Int, Int) Image
113− -- Crop an image to a size
114− | ImageCrop (Word, Word) Image
115− -- Pad an image up to a size
116− | ImagePad (Word, Word) Image
117− deriving Eq
118−
119−instance Show Image where
120− show ( HorizText { output_width = ow, text = txt } )
121− = "HorizText [" ++ show ow ++ "] (" ++ show (fmap fst txt) ++ ")"
122− show ( BGFill { output_width = c, output_height = r } )
123− = "BGFill (" ++ show c ++ "," ++ show r ++ ")"
124− show ( HorizJoin { part_left = l, part_right = r, output_width = c } )
125− = "HorizJoin " ++ show c ++ " ( " ++ show l ++ " <|> " ++ show r ++ " )"
126− show ( VertJoin { part_top = t, part_bottom = b, output_width = c, output_height = r } )
127− = "VertJoin (" ++ show c ++ ", " ++ show r ++ ") ( " ++ show t ++ " ) <-> ( " ++ show b ++ " )"
128− show ( Translation offset i )
129− = "Translation " ++ show offset ++ " ( " ++ show i ++ " )"
130− show ( ImageCrop size i )
131− = "ImageCrop " ++ show size ++ " ( " ++ show i ++ " )"
132− show ( ImagePad size i )
133− = "ImagePad " ++ show size ++ " ( " ++ show i ++ " )"
134− show ( EmptyImage ) = "EmptyImage"
135−
136−-- | Currently append in the Monoid instance is equivalent to <->.
137−instance Monoid Image where
138− mempty = empty_image
139− mappend = (<->)
140−
141−instance NFData Image where
142− rnf EmptyImage = ()
143− rnf (Translation s i) = s `deepseq` i `deepseq` ()
144− rnf (ImagePad s i) = s `deepseq` i `deepseq` ()
145− rnf (ImageCrop s i) = s `deepseq` i `deepseq` ()
146− rnf (BGFill !w !h) = ()
147− rnf (VertJoin t b !w !h) = t `deepseq` b `deepseq` ()
148− rnf (HorizJoin l r !w !h) = l `deepseq` r `deepseq` ()
149− rnf (HorizText !a s !w !cw) = s `deepseq` ()
150−
151−-- A horizontal text image of 0 characters in width simplifies to the EmptyImage
152−horiz_text :: Attr -> DisplayString -> Word -> Image
153−horiz_text a txt ow
154− | ow == 0 = EmptyImage
155− | otherwise = HorizText a txt ow (toEnum $ Seq.length txt)
156−
157−horiz_join :: Image -> Image -> Word -> Word -> Image
158−horiz_join i_0 i_1 w h
159− -- A horiz join of two 0 width images simplifies to the EmptyImage
160− | w == 0 = EmptyImage
161− -- A horizontal join where either part is 0 columns in width simplifies to the other part.
162− -- This covers the case where one part is the EmptyImage.
163− | image_width i_0 == 0 = i_1
164− | image_width i_1 == 0 = i_0
165− -- If the images are of the same height then no BG padding is required
166− | image_height i_0 == image_height i_1 = HorizJoin i_0 i_1 w h
167− -- otherwise one of the imagess needs to be padded to the right size.
168− | image_height i_0 < image_height i_1 -- Pad i_0
169− = let pad_amount = image_height i_1 - image_height i_0
170− in horiz_join ( vert_join i_0
171− ( BGFill ( image_width i_0 ) pad_amount )
172− ( image_width i_0 )
173− ( image_height i_1 )
174− )
175− i_1
176− w h
177− | image_height i_0 > image_height i_1 -- Pad i_1
178− = let pad_amount = image_height i_0 - image_height i_1
179− in horiz_join i_0
180− ( vert_join i_1
181− ( BGFill ( image_width i_1 ) pad_amount )
182− ( image_width i_1 )
183− ( image_height i_0 )
184− )
185− w h
186−horiz_join _ _ _ _ = error "horiz_join applied to undefined values."
187−
188−vert_join :: Image -> Image -> Word -> Word -> Image
189−vert_join i_0 i_1 w h
190− -- A vertical join of two 0 height images simplifies to the EmptyImage
191− | h == 0 = EmptyImage
192− -- A vertical join where either part is 0 rows in height simplifies to the other part.
193− -- This covers the case where one part is the EmptyImage
194− | image_height i_0 == 0 = i_1
195− | image_height i_1 == 0 = i_0
99+-- TODO: the bg fill is biased towards right to left languages(?)
100+vert_join :: Image -> Image -> Image
101+vert_join EmptyImage i = i
102+vert_join i EmptyImage = i
103+vert_join i_0 i_1
196104 -- If the images are of the same height then no background padding is required
197− | image_width i_0 == image_width i_1 = VertJoin i_0 i_1 w h
105+ | w_0 == w_1 = VertJoin i_0 i_1 w_0 h
198106 -- Otherwise one of the images needs to be padded to the size of the other image.
199− | image_width i_0 < image_width i_1
200− = let pad_amount = image_width i_1 - image_width i_0
201− in vert_join ( horiz_join i_0
202− ( BGFill pad_amount ( image_height i_0 ) )
203− ( image_width i_1 )
204− ( image_height i_0 )
205− )
206− i_1
207− w h
208− | image_width i_0 > image_width i_1
209− = let pad_amount = image_width i_0 - image_width i_1
210− in vert_join i_0
211− ( horiz_join i_1
212− ( BGFill pad_amount ( image_height i_1 ) )
213− ( image_width i_0 )
214− ( image_height i_1 )
215− )
216− w h
217−vert_join _ _ _ _ = error "vert_join applied to undefined values."
107+ | w_0 < w_1
108+ = let pad_amount = w_1 - w_0
109+ in VertJoin (HorizJoin i_0 (BGFill pad_amount h_0) w_1 h_0) i_1 w_1 h
110+ | w_0 > w_1
111+ = let pad_amount = w_0 - w_1
112+ in VertJoin i_0 (HorizJoin i_1 (BGFill pad_amount h_1) w_0 h_1) w_0 h
113+ where
114+ w_0 = image_width i_0
115+ w_1 = image_width i_1
116+ h_0 = image_height i_0
117+ h_1 = image_height i_1
118+ h = h_0 + h_1
119+vert_join _ _ = error "vert_join applied to undefined values."
218120
219121-- | An area of the picture's bacground (See Background) of w columns and h rows.
220−background_fill :: Word -> Word -> Image
122+background_fill :: Int -> Int -> Image
221123background_fill w h
222124 | w == 0 = EmptyImage
223125 | h == 0 = EmptyImage
224126 | otherwise = BGFill w h
225127
226−-- | The width of an Image. This is the number display columns the image will occupy.
227−image_width :: Image -> Word
228−image_width HorizText { output_width = w } = w
229−image_width HorizJoin { output_width = w } = w
230−image_width VertJoin { output_width = w } = w
231−image_width BGFill { output_width = w } = w
232−image_width EmptyImage = 0
233−image_width ( Translation v i ) = toEnum $ max 0 $ (fst v +) $ fromEnum $ image_width i
234−image_width ( ImageCrop v i ) = min (image_width i) $ fst v
235−image_width ( ImagePad v i ) = max (image_width i) $ fst v
236−
237−-- | The height of an Image. This is the number of display rows the image will occupy.
238−image_height :: Image -> Word
239−image_height HorizText {} = 1
240−image_height HorizJoin { output_height = r } = r
241−image_height VertJoin { output_height = r } = r
242−image_height BGFill { output_height = r } = r
243−image_height EmptyImage = 0
244−image_height ( Translation v i ) = toEnum $ max 0 $ (snd v +) $ fromEnum $ image_height i
245−image_height ( ImageCrop v i ) = min (image_height i) $ snd v
246−image_height ( ImagePad v i ) = max (image_height i) $ snd v
247−
248−-- | Combines two images side by side.
128+-- | Combines two images horizontally. Alias for horiz_join
249129--
250−-- The result image will have a width equal to the sum of the two images width. And the height will
251−-- equal the largest height of the two images. The area not defined in one image due to a height
252−-- missmatch will be filled with the background pattern.
130+-- infixr 5
253131(<|>) :: Image -> Image -> Image
132+(<|>) = horiz_join
254133
255−-- Two horizontal text spans with the same attributes can be merged.
256−h0@(HorizText attr_0 text_0 ow_0 _) <|> h1@(HorizText attr_1 text_1 ow_1 _)
257− | attr_0 == attr_1 = horiz_text attr_0 (text_0 Seq.>< text_1) (ow_0 + ow_1)
258− | otherwise = horiz_join h0 h1 (ow_0 + ow_1) 1
259−
260−-- Anything placed to the right of a join wil be joined to the right sub image.
261−-- The total columns for the join is the sum of the two arguments columns
262−h0@( HorizJoin {} ) <|> h1
263− = horiz_join ( part_left h0 )
264− ( part_right h0 <|> h1 )
265− ( image_width h0 + image_width h1 )
266− ( max (image_height h0) (image_height h1) )
267−
268−-- Anything but a join placed to the left of a join wil be joined to the left sub image.
269−-- The total columns for the join is the sum of the two arguments columns
270−h0 <|> h1@( HorizJoin {} )
271− = horiz_join ( h0 <|> part_left h1 )
272− ( part_right h1 )
273− ( image_width h0 + image_width h1 )
274− ( max (image_height h0) (image_height h1) )
275−
276−h0 <|> h1
277− = horiz_join h0
278− h1
279− ( image_width h0 + image_width h1 )
280− ( max (image_height h0) (image_height h1) )
281−
282−-- | Combines two images vertically.
283−-- The result image will have a height equal to the sum of the heights of both images.
284−-- The width will equal the largest width of the two images.
285−-- The area not defined in one image due to a width missmatch will be filled with the background
286−-- pattern.
134+-- | Combines two images vertically. Alias for vert_join
135+--
136+-- infixr 4
287137(<->) :: Image -> Image -> Image
288−im_t <-> im_b
289− = vert_join im_t
290− im_b
291− ( max (image_width im_t) (image_width im_b) )
292− ( image_height im_t + image_height im_b )
138+(<->) = vert_join
293139
294140-- | Compose any number of images horizontally.
295141horiz_cat :: [Image] -> Image
@@ -299,12 +145,26 @@ horiz_cat = foldr (<|>) EmptyImage
299145vert_cat :: [Image] -> Image
300146vert_cat = foldr (<->) EmptyImage
301147
148+-- | A Data.Text.Lazy value
149+text :: Attr -> TL.Text -> Image
150+text a txt
151+ | TL.length txt == 0 = EmptyImage
152+ | otherwise = let display_width = safe_wcswidth (TL.unpack txt)
153+ in HorizText a txt display_width (fromIntegral $! TL.length txt)
154+
155+-- | A Data.Text value
156+strict_text :: Attr -> T.Text -> Image
157+strict_text a txt
158+ | T.length txt == 0 = EmptyImage
159+ | otherwise = let display_width = safe_wcswidth (T.unpack txt)
160+ in HorizText a (TL.fromStrict txt) display_width (T.length txt)
161+
302162-- | an image of a single character. This is a standard Haskell 31-bit character assumed to be in
303163-- the ISO-10646 encoding.
304164char :: Attr -> Char -> Image
305−char !a !c =
165+char a c =
306166 let display_width = safe_wcwidth c
307− in HorizText a (Seq.singleton (c, display_width)) display_width 1
167+ in HorizText a (TL.singleton c) display_width 1
308168
309169-- | A string of characters layed out on a single row with the same display attribute. The string is
310170-- assumed to be a sequence of ISO-10646 characters.
@@ -316,9 +176,9 @@ char !a !c =
316176-- directly to iso_10646_string or string.
317177--
318178iso_10646_string :: Attr -> String -> Image
319−iso_10646_string !a !str =
320− let display_text = Seq.fromList $ map (\c -> (c, safe_wcwidth c)) str
321− in horiz_text a display_text (safe_wcswidth str)
179+iso_10646_string a str =
180+ let display_width = safe_wcswidth str
181+ in HorizText a (TL.pack str) display_width (length str)
322182
323183-- | Alias for iso_10646_string. Since the usual case is that a literal string like "foo" is
324184-- represented internally as a list of ISO 10646 31 bit characters.
@@ -329,62 +189,110 @@ iso_10646_string !a !str =
329189string :: Attr -> String -> Image
330190string = iso_10646_string
331191
332−-- | A string of characters layed out on a single row. The string is assumed to be a sequence of
333−-- UTF-8 characters.
192+-- | A string of characters layed out on a single row. The input is assumed to be the bytes for
193+-- UTF-8 encoded text.
334194utf8_string :: Attr -> [Word8] -> Image
335−utf8_string !a !str = string a ( decode str )
195+utf8_string a bytes = utf8_bytestring a (BL.pack bytes)
336196
337−-- XXX: Characters with unknown widths occupy 1 column?
338−--
339−-- Not sure if this is actually correct. I presume there is a replacement character that is output
340−-- by the terminal instead of the character and this replacement character is 1 column wide. If this
341−-- is not true for all terminals then a per-terminal replacement character width needs to be
342−-- implemented.
197+-- | Renders a UTF-8 encoded lazy bytestring.
198+utf8_bytestring :: Attr -> BL.ByteString -> Image
199+utf8_bytestring a bs = text a (TL.decodeUtf8 bs)
343200
344−-- | Returns the display width of a character. Assumes all characters with unknown widths are 1 width
345−safe_wcwidth :: Char -> Word
346−safe_wcwidth c = case wcwidth c of
347− i | i < 0 -> 1
348− | otherwise -> toEnum i
349−
350−-- | Returns the display width of a string. Assumes all characters with unknown widths are 1 width
351−safe_wcswidth :: String -> Word
352−safe_wcswidth str = case wcswidth str of
353− i | i < 0 -> 1
354− | otherwise -> toEnum i
355−
356−-- | Renders a UTF-8 encoded bytestring.
357−utf8_bytestring :: Attr -> BS.ByteString -> Image
358−utf8_bytestring !a !bs = string a (UTF8.toString $ UTF8.fromRep bs)
201+-- | Renders a UTF-8 encoded strict bytestring.
202+utf8_strict_bytestring :: Attr -> B.ByteString -> Image
203+utf8_strict_bytestring a bs = strict_text a (T.decodeUtf8 bs)
359204
360205-- | creates a fill of the specified character. The dimensions are in number of characters wide and
361206-- number of rows high.
362207--
363208-- Unlike the Background fill character this character can have double column display width.
364−char_fill :: Enum d => Attr -> Char -> d -> d -> Image
365−char_fill !a !c w h =
366− vert_cat $ replicate (fromEnum h) $ horiz_cat $ replicate (fromEnum w) $ char a c
209+char_fill :: Integral d => Attr -> Char -> d -> d -> Image
210+char_fill _a _c 0 _h = EmptyImage
211+char_fill _a _c _w 0 = EmptyImage
212+char_fill a c w h =
213+ vert_cat $ replicate (fromIntegral h) $ HorizText a txt display_width char_width
214+ where
215+ txt = TL.replicate (fromIntegral w) (TL.singleton c)
216+ display_width = safe_wcwidth c * (fromIntegral w)
217+ char_width = fromIntegral w
367218
368219-- | The empty image. Useful for fold combinators. These occupy no space nor define any display
369220-- attributes.
370221empty_image :: Image
371222empty_image = EmptyImage
372223
373−-- | Apply the given offset to the image.
374−translate :: (Int, Int) -> Image -> Image
375−translate v i = Translation v i
376−
377−-- | Ensure an image is no larger than the provided size. If the image is larger then crop.
378−crop :: (Word, Word) -> Image -> Image
379−crop (0,_) _ = EmptyImage
380−crop (_,0) _ = EmptyImage
381−crop v (ImageCrop _size i) = ImageCrop (min (fst v) (fst _size), min (snd v) (snd _size)) i
382−crop v i = ImageCrop v i
383−
384−-- | Ensure an image is at least the provided size. If the image is smaller then pad.
385−pad :: (Word, Word) -> Image -> Image
386−pad (0,_) _ = EmptyImage
387−pad (_,0) _ = EmptyImage
388−pad v (ImagePad _size i) = ImagePad (max (fst v) (fst _size), max (snd v) (snd _size)) i
389−pad v i = ImagePad v i
224+-- | pad the given image. This adds background character fills to the left, top, right, bottom.
225+pad :: Int -> Int -> Int -> Int -> Image -> Image
226+pad 0 0 0 0 i = i
227+pad in_l in_t in_r in_b in_image
228+ | in_l < 0 || in_t < 0 || in_r < 0 || in_b < 0 = error "cannot pad by negative amount"
229+ | otherwise = go in_l in_t in_r in_b in_image
230+ where
231+ -- TODO: uh.
232+ go 0 0 0 0 i = i
233+ go 0 0 0 b i = VertJoin i (BGFill w b) w h
234+ where w = image_width i
235+ h = image_height i + b
236+ go 0 0 r b i = go 0 0 0 b $ HorizJoin i (BGFill r h) w h
237+ where w = image_width i + r
238+ h = image_height i
239+ go 0 t r b i = go 0 0 r b $ VertJoin (BGFill w t) i w h
240+ where w = image_width i
241+ h = image_height i + t
242+ go l t r b i = go 0 t r b $ HorizJoin (BGFill l h) i w h
243+ where w = image_width i + l
244+ h = image_height i
245+
246+-- | "translates" an image by padding the top and left.
247+translate :: Int -> Int -> Image -> Image
248+translate x y i = pad x y 0 0 i
249+
250+-- | Ensure an image is no larger than the provided size. If the image is larger then crop the right
251+-- or bottom.
252+--
253+-- This is transformed to a vertical crop from the bottom followed by horizontal crop from the
254+-- right.
255+crop :: Int -> Int -> Image -> Image
256+crop 0 _ _ = EmptyImage
257+crop _ 0 _ = EmptyImage
258+crop w h i = crop_bottom h (crop_right w i)
259+
260+-- | crop the display height. If the image is less than or equal in height then this operation has
261+-- no effect. Otherwise the image is cropped from the bottom.
262+crop_bottom :: Int -> Image -> Image
263+crop_bottom 0 _ = EmptyImage
264+crop_bottom h in_i
265+ | h < 0 = error "cannot crop height to less than zero"
266+ | otherwise = go in_i
267+ where
268+ go EmptyImage = EmptyImage
269+ go (CropBottom {cropped_image, output_width, output_height}) =
270+ CropBottom cropped_image output_width (min h output_height)
271+ go i
272+ | h >= image_height i = i
273+ | otherwise = CropBottom i (image_width i) h
274+
275+crop_right :: Int -> Image -> Image
276+crop_right 0 _ = EmptyImage
277+crop_right w in_i
278+ | w < 0 = error "cannot crop width to less than zero"
279+ | otherwise = go in_i
280+ where
281+ go EmptyImage = EmptyImage
282+ go (CropRight {cropped_image, output_width, output_height}) =
283+ CropRight cropped_image (min w output_width) output_height
284+ go i
285+ | w >= image_width i = i
286+ | otherwise = CropRight i w (image_height i)
287+
288+crop_left :: Int -> Image -> Image
289+crop_left _ _ = error "not implemented"
290+
291+crop_top :: Int -> Image -> Image
292+crop_top _ _ = error "not implemented"
293+
294+-- | Generic resize. Pads and crops as required to assure the given display width and height.
295+-- This is biased to pad the right and bottom.
296+resize :: Int -> Int -> Image -> Image
297+resize _w _h _i = error "not implemented yet"
390298
src/Graphics/Vty/Image/Internal.hsnew file+162
@@ -0,0 +1,162 @@
1+{-# LANGUAGE NamedFieldPuns #-}
2+module Graphics.Vty.Image.Internal where
3+
4+import Graphics.Vty.Attributes
5+
6+import Control.DeepSeq
7+
8+import qualified Data.Text.Lazy as TL
9+
10+-- | A display text is a Data.Text.Lazy
11+--
12+-- TODO(corey): hm. there is an explicit equation for each type which goes to a lazy text. Each
13+-- application probably uses a single type. Perhaps parameterize the entire vty interface by the
14+-- input text type?
15+type DisplayText = TL.Text
16+
17+-- | An image in VTY is
18+--
19+-- * a horizontal line of characters with a single attribute.
20+-- * a fill of the picture's background. (background_fill)
21+-- * a horizontal and vertical crops from both directions.
22+-- * horizontal and vertical combinations
23+data Image =
24+ -- | A horizontal text span is always >= 1 column and has a row height of 1.
25+ HorizText
26+ { attr :: Attr
27+ -- | The text to display. The display width of the text is always output_width.
28+ , display_text :: DisplayText
29+ -- | The number of display columns for the text.
30+ , output_width :: Int
31+ -- | the number of characters in the text.
32+ , char_width :: Int
33+ }
34+ -- | A horizontal join can be constructed between any two images. However a HorizJoin instance is
35+ -- required to be between two images of equal height. The horiz_join constructor adds background
36+ -- filles to the provided images that assure this is true for the HorizJoin value produced.
37+ | HorizJoin
38+ { part_left :: Image
39+ , part_right :: Image
40+ , output_width :: Int -- ^ image_width part_left == image_width part_right. Always > 1
41+ , output_height :: Int -- ^ image_height part_left == image_height part_right. Always > 0
42+ }
43+ -- | A veritical join can be constructed between any two images. However a VertJoin instance is
44+ -- required to be between two images of equal width. The vert_join constructor adds background
45+ -- fills to the provides images that assure this is true for the VertJoin value produced.
46+ | VertJoin
47+ { part_top :: Image
48+ , part_bottom :: Image
49+ , output_width :: Int -- ^ image_width part_top == image_width part_bottom. always > 0
50+ , output_height :: Int -- ^ image_height part_top == image_height part_bottom. always > 1
51+ }
52+ -- | A background fill will be filled with the background char. The background char is
53+ -- defined as a property of the Picture this Image is used to form.
54+ | BGFill
55+ { output_width :: Int -- ^ always > 0
56+ , output_height :: Int -- ^ always > 0
57+ }
58+ -- | Crop an image horizontally to a size by reducing the size from the right.
59+ | CropRight
60+ { cropped_image :: Image
61+ -- | Always < image_width cropped_image > 0
62+ , output_width :: Int
63+ , output_height :: Int -- ^ image_height cropped_image
64+ }
65+ -- | Crop an image horizontally to a size by reducing the size from the left.
66+ | CropLeft
67+ { cropped_image :: Image
68+ -- | Always < image_width cropped_image > 0
69+ , left_skip :: Int
70+ -- | Always < image_width cropped_image > 0
71+ , output_width :: Int
72+ , output_height :: Int
73+ }
74+ -- | Crop an image vertically to a size by reducing the size from the bottom
75+ | CropBottom
76+ { cropped_image :: Image
77+ -- | image_width cropped_image
78+ , output_width :: Int
79+ -- | height image is cropped to. Always < image_height cropped_image > 0
80+ , output_height :: Int
81+ }
82+ -- | Crop an image vertically to a size by reducing the size from the top
83+ | CropTop
84+ { cropped_image :: Image
85+ -- | Always < image_height cropped_image > 0
86+ , top_skip :: Int
87+ -- | image_width cropped_image
88+ , output_width :: Int
89+ -- | Always < image_height cropped_image > 0
90+ , output_height :: Int
91+ }
92+ -- | The empty image
93+ --
94+ -- The combining operators identity constant.
95+ -- EmptyImage <|> a = a
96+ -- EmptyImage <-> a = a
97+ --
98+ -- Any image of zero size equals the empty image.
99+ | EmptyImage
100+ deriving Eq
101+
102+instance Show Image where
103+ show ( HorizText { attr, display_text, output_width, char_width } )
104+ = "HorizText \"" ++ show display_text ++ "\""
105+ ++ "@(" ++ show attr ++ ","
106+ ++ show output_width ++ ","
107+ ++ show char_width ++ ")"
108+ show ( BGFill { output_width, output_height } )
109+ = "BGFill (" ++ show output_width ++ "," ++ show output_height ++ ")"
110+ show ( HorizJoin { part_left = l, part_right = r, output_width = c } )
111+ = "HorizJoin " ++ show c ++ " (" ++ show l ++ " <|> " ++ show r ++ ")"
112+ show ( VertJoin { part_top = t, part_bottom = b, output_width = c, output_height = r } )
113+ = "VertJoin [" ++ show c ++ ", " ++ show r ++ "] (" ++ show t ++ ") <-> (" ++ show b ++ ")"
114+ show ( CropRight { cropped_image, output_width, output_height } )
115+ = "CropRight [" ++ show output_width ++ "," ++ show output_height ++ "]"
116+ ++ " (" ++ show cropped_image ++ ")"
117+ show ( CropLeft { cropped_image, left_skip, output_width, output_height } )
118+ = "CropLeft [" ++ show left_skip ++ "," ++ show output_width ++ "," ++ show output_height ++ "]"
119+ ++ " (" ++ show cropped_image ++ ")"
120+ show ( CropBottom { cropped_image, output_width, output_height } )
121+ = "CropBottom [" ++ show output_width ++ "," ++ show output_height ++ "]"
122+ ++ " (" ++ show cropped_image ++ ")"
123+ show ( CropTop { cropped_image, top_skip, output_width, output_height } )
124+ = "CropTop [" ++ show top_skip ++ "," ++ show output_width ++ "," ++ show output_height ++ "]"
125+ ++ " (" ++ show cropped_image ++ ")"
126+ show ( EmptyImage ) = "EmptyImage"
127+
128+instance NFData Image where
129+ rnf EmptyImage = ()
130+ rnf (CropRight i w h) = i `deepseq` w `seq` h `seq` ()
131+ rnf (CropLeft i s w h) = i `deepseq` s `seq` w `seq` h `seq` ()
132+ rnf (CropBottom i w h) = i `deepseq` w `seq` h `seq` ()
133+ rnf (CropTop i s w h) = i `deepseq` s `seq` w `seq` h `seq` ()
134+ rnf (BGFill w h) = w `seq` h `seq` ()
135+ rnf (VertJoin t b w h) = t `deepseq` b `deepseq` w `seq` h `seq` ()
136+ rnf (HorizJoin l r w h) = l `deepseq` r `deepseq` w `seq` h `seq` ()
137+ rnf (HorizText a s w cw) = a `seq` s `deepseq` w `seq` cw `seq` ()
138+
139+-- | The width of an Image. This is the number display columns the image will occupy.
140+image_width :: Image -> Int
141+image_width HorizText { output_width = w } = w
142+image_width HorizJoin { output_width = w } = w
143+image_width VertJoin { output_width = w } = w
144+image_width BGFill { output_width = w } = w
145+image_width CropRight { output_width = w } = w
146+image_width CropLeft { output_width = w } = w
147+image_width CropBottom { output_width = w } = w
148+image_width CropTop { output_width = w } = w
149+image_width EmptyImage = 0
150+
151+-- | The height of an Image. This is the number of display rows the image will occupy.
152+image_height :: Image -> Int
153+image_height HorizText {} = 1
154+image_height HorizJoin { output_height = h } = h
155+image_height VertJoin { output_height = h } = h
156+image_height BGFill { output_height = h } = h
157+image_height CropRight { output_height = h } = h
158+image_height CropLeft { output_height = h } = h
159+image_height CropBottom { output_height = h } = h
160+image_height CropTop { output_height = h } = h
161+image_height EmptyImage = 0
162+
src/Graphics/Vty/Picture.hs+3−1
@@ -83,8 +83,10 @@ instance NFData Cursor where
8383-- screen to a picture.
8484--
8585-- \todo The background character *must* occupy a single column and no more.
86+--
87+-- \todo background char should be optional
8688data Background = Background
87− { background_char :: Char
89+ { background_char :: Char
8890 , background_attr :: Attr
8991 }
9092
src/Graphics/Vty/Span.hs+129−147
@@ -1,3 +1,5 @@
1+{-# LANGUAGE MultiWayIf #-}
2+{-# LANGUAGE TemplateHaskell #-}
13-- Copyright 2009-2010 Corey O'Connor
24{-# LANGUAGE BangPatterns #-}
35{-# LANGUAGE ExistentialQuantification #-}
@@ -10,28 +12,37 @@ module Graphics.Vty.Span
1012 where
1113
1214import Graphics.Vty.Image
15+import Graphics.Vty.Image.Internal
1316import Graphics.Vty.Picture
1417import Graphics.Vty.DisplayRegion
18+import Graphics.Text.Width
1519
16−import Codec.Binary.UTF8.String ( encode )
17−
20+import Control.Lens
1821import Control.Monad ( forM_ )
22+import Control.Monad.Reader.Strict
23+import Control.Monad.State.Strict
1924import Control.Monad.ST.Strict hiding ( unsafeIOToST )
2025import Control.Monad.ST.Unsafe ( unsafeIOToST )
2126
27+import Data.Monoid
2228import Data.Vector (Vector)
2329import qualified Data.Vector as Vector hiding ( take, replicate )
2430import Data.Vector.Mutable ( MVector(..))
2531import qualified Data.Vector.Mutable as Vector
2632
27−import qualified Data.ByteString as B
28−import qualified Data.ByteString.Internal as BInt
33+import qualified Data.ByteString.Lazy as BL
34+import qualified Data.ByteString.Lazy.Internal as BLInt
2935import qualified Data.Foldable as Foldable
30−import qualified Data.String.UTF8 as UTF8
36+import qualified Data.Text.Lazy as TL
3137import Data.Word
3238
3339import Foreign.Storable ( pokeByteOff )
3440
41+-- | Currently append in the Monoid instance is equivalent to <->.
42+instance Monoid Image where
43+ mempty = EmptyImage
44+ mappend = (<->)
45+
3546{- | A picture is translated into a sequences of state changes and character spans.
3647 - State changes are currently limited to new attribute values. The attribute is applied to all
3748 - following spans. Including spans of the next row. The nth element of the sequence represents the
@@ -96,19 +107,19 @@ data SpanOp =
96107 -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF
97108 -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width
98109 -- TextSpan [output width in columns] [number of characters] [data]
99− | TextSpan !Word !Word (UTF8.UTF8 B.ByteString)
110+ | TextSpan !Int !Int BL.ByteString
100111 deriving Eq
101112
102113-- | The width of a single SpanOp in columns
103−span_op_has_width :: SpanOp -> Maybe (Word, Word)
114+span_op_has_width :: SpanOp -> Maybe (Int, Int)
104115span_op_has_width (TextSpan ow cw _) = Just (cw, ow)
105116span_op_has_width _ = Nothing
106117
107118-- | returns the number of columns to the character at the given position in the span op
108−columns_to_char_offset :: Word -> SpanOp -> Word
119+columns_to_char_offset :: Int -> SpanOp -> Int
109120columns_to_char_offset cx (TextSpan _ _ utf8_str) =
110− let str = UTF8.toString utf8_str
111− in toEnum $! sum $! map wcwidth $! take (fromEnum cx) str
121+ let str = TL.unpack (TL.decodeUtf8 utf8_str)
122+ in wcswidth (take cx str)
112123columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
113124
114125-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the
@@ -116,13 +127,41 @@ columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op
116127spans_for_pic :: Picture -> DisplayRegion -> DisplayOps
117128spans_for_pic pic r = DisplayOps r $ Vector.create (build_spans pic r)
118129
130+-- transform plus clip. More or less.
131+newtype BlitState = BlitState
132+ -- we always snoc to the operation vectors. Thus the column_offset = length of row at row_offset
133+ { _row_offset :: Int
134+ -- clip coordinate space is in image space. Which means it's >= 0 and < image_width.
135+ , _skip_columns :: Int
136+ -- >= 0 and < image_height
137+ , _skip_rows :: Int
138+ -- includes consideration of skip_columns. In display space.
139+ -- The number of columns from the next column to be defined to the end of the display for the
140+ -- row.
141+ , _remaining_columns :: Int
142+ -- includes consideration of skip_rows. In display space.
143+ , _remaining_rows :: Int
144+ }
145+
146+makeLenses ''BlitState
147+
148+newtype BlitEnv s = BlitEnv
149+ { _bg :: Background
150+ , _region :: DisplayRegion
151+ , _mrow_ops :: MRowOps s
152+ }
153+
154+makeLenses ''BlitEnv
155+
156+type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a
157+
119158-- | Builds a vector of row operations that will output the given picture to the terminal.
120159--
121160-- Crops to the given display region.
122161build_spans :: Picture -> DisplayRegion -> ST s (MRowOps s)
123162build_spans pic region = do
124163 -- First we create a mutable vector for each rows output operations.
125− mrow_ops <- Vector.replicate (fromEnum $ region_height region) Vector.empty
164+ mrow_ops <- Vector.replicate (region_height region) Vector.empty
126165 -- \todo I think building the span operations in display order would provide better performance.
127166 -- However, I got stuck trying to implement an algorithm that did this. This will be considered
128167 -- as a possible future optimization.
@@ -140,16 +179,13 @@ build_spans pic region = do
140179 -- display that image. The number of columns remaining in this row before exceeding the
141180 -- bounds is also provided. This is used to clip the span ops produced to the display.
142181 -- The skip dimensions provided do....???
143− _ <- row_ops_for_image mrow_ops
144− (pic_image pic)
145− (pic_background pic)
146− region
147− (0,0)
148− 0
149− (region_width region)
150− (fromEnum $ region_height region)
182+ _ <- runStateT (runReaderT (start_image_build $ pic_image pic)
183+ (BlitEnv (pic_background pic) region mrow_ops)
184+ )
185+ (BlitState 0 0 0 0 (region_width region) (region_height region))
151186 -- Fill in any unspecified columns with the background pattern.
152− forM_ [0 .. (fromEnum $ region_height region - 1)] $! \row -> do
187+ -- todo: If there is no background pattern defined then skip
188+ forM_ [0 .. (region_height region - 1)] $! \row -> do
153189 end_x <- Vector.read mrow_ops row >>= return . span_ops_effected_columns
154190 if end_x < region_width region
155191 then snoc_bg_fill mrow_ops (pic_background pic) (region_width region - end_x) row
@@ -157,31 +193,39 @@ build_spans pic region = do
157193 else return ()
158194 return mrow_ops
159195
160−-- | Add the operations required to build a given image to the current set of row operations.
161−row_ops_for_image :: MRowOps s -> Image -> Background -> DisplayRegion -> (Word, Word) -> Int -> Word -> Int -> ST s (Word, Word)
162−row_ops_for_image mrow_ops -- the image to output the ops to
163− image -- the image to rasterize in column order to mrow_ops
164− bg -- the background fill
165− region -- ???
166− skip_dim@(skip_row,skip_col) -- the number of rows
167− y -- ???
168− remaining_columns -- ???
169− remain_rows
170− | remaining_columns == 0 = return skip_dim
171− | remain_rows == 0 = return skip_dim
172− | y >= fromEnum (region_height region) = return skip_dim
173− | otherwise = case image of
174− EmptyImage -> return skip_dim
175− -- The width provided is the number of columns this text span will occupy when displayed.
176− -- if this is greater than the number of remaining columsn the output has to be produced a
177− -- character at a time.
178− HorizText a text_str _ _ -> do
179− if skip_row > 0
180− then return (skip_row - 1, skip_col)
181− else do
182− skip_col' <- snoc_text_span a text_str mrow_ops skip_col y remaining_columns
183− return (skip_row, skip_col')
184− VertJoin top_image bottom_image _ _ -> do
196+-- | Add the operations required to build a given image to the current set of row operations
197+-- returns the number of columns and rows contributed to the output.
198+start_image_build :: Image -> BlitM s ()
199+start_image_build image = do
200+ out_of_bounds <- is_out_of_bounds image <$> get
201+ if out_of_bounds
202+ then return (0,0)
203+ else add_maybe_clipped image
204+
205+is_out_of_bounds :: Image -> BlitState -> Bool
206+is_out_of_bounds image s
207+ | s ^. remaining_columns <= 0 = True
208+ | s ^. remain_rows <= 0 = True
209+ | otherwise = False
210+
211+add_maybe_clipped :: Image -> BlitM s ()
212+add_maybe_clipped EmptyImage = return ()
213+-- The width provided is the number of columns this text span will occupy when displayed.
214+-- if this is greater than the number of remaining columsn the output has to be produced a
215+-- character at a time.
216+--
217+-- TODO: prove this cannot be called in fully clipped case
218+add_maybe_clipped (HorizText text_str _ow _cw) =
219+ use row_offset >>= snoc_op (AttributeChange a)
220+ left_clip <- use skip_columns
221+ right_clip <- use remaining_columns
222+ let left_clipped = left_clip > 0
223+ right_clipped = (ow - left_clip) > right_clip
224+ if left_clipped || right_clipped
225+ then let text_str' = clip_text left_clip right_clip
226+ in render_unclipped_text_span text_str'
227+ else render_unclipped_text_span text_str
228+process_image (VertJoin top_image bottom_image _ _) = do
185229 (skip_row',skip_col') <- row_ops_for_image mrow_ops
186230 top_image
187231 bg
@@ -190,7 +234,7 @@ row_ops_for_image mrow_ops -- the image to output the ops t
190234 y
191235 remaining_columns
192236 remain_rows
193− let top_height = (fromEnum $! image_height top_image) - (fromEnum $! skip_row - skip_row')
237+ let top_height = (image_height top_image) - (skip_row - skip_row')
194238 (skip_row'',skip_col'') <- row_ops_for_image mrow_ops
195239 bottom_image
196240 bg
@@ -210,9 +254,9 @@ row_ops_for_image mrow_ops -- the image to output the ops t
210254 (skip_row'',skip_col'') <- row_ops_for_image mrow_ops r bg region (skip_row, skip_col') y (remaining_columns - image_width l + (skip_col - skip_col')) remain_rows
211255 return (min skip_row' skip_row'', skip_col'')
212256 BGFill width height -> do
213− let min_height = if y + (fromEnum height) > (fromEnum $! region_height region)
214− then region_height region - (toEnum y)
215− else min height (toEnum remain_rows)
257+ let min_height = if y + height > (region_height region)
258+ then region_height region - y
259+ else min height remain_rows
216260 min_width = min width remaining_columns
217261 actual_height = if skip_row > min_height
218262 then 0
@@ -220,7 +264,7 @@ row_ops_for_image mrow_ops -- the image to output the ops t
220264 actual_width = if skip_col > min_width
221265 then 0
222266 else min_width - skip_col
223− forM_ [y .. y + fromEnum actual_height - 1] $! \y' -> snoc_bg_fill mrow_ops bg actual_width y'
267+ forM_ [y .. y + actual_height - 1] $! \y' -> snoc_bg_fill mrow_ops bg actual_width y'
224268 let skip_row' = if actual_height > skip_row
225269 then 0
226270 else skip_row - min_height
@@ -228,101 +272,37 @@ row_ops_for_image mrow_ops -- the image to output the ops t
228272 then 0
229273 else skip_col - min_width
230274 return (skip_row',skip_col')
231− Translation (dx,dy) i -> do
232− if dx < 0
233− -- Translation left
234− -- Extract the delta and add it to skip_col.
235− then row_ops_for_image mrow_ops (translate (0, dy) i) bg region (skip_row, skip_col + dw) y remaining_columns remain_rows
236− -- Translation right
237− else if dy < 0
238− -- Translation up
239− -- Extract the delta and add it to skip_row.
240− then row_ops_for_image mrow_ops (translate (dx, 0) i) bg region (skip_row + dh, skip_col) y remaining_columns remain_rows
241− -- Translation down
242− -- Pad the start of lines and above the image with a
243− -- background_fill image
244− else row_ops_for_image mrow_ops (background_fill ow dh <-> (background_fill dw ih <|> i)) bg region skip_dim y remaining_columns remain_rows
245− where
246− dw = toEnum $ abs dx
247− dh = toEnum $ abs dy
248− ow = image_width image
249− ih = image_height i
250275 ImageCrop (max_w,max_h) i ->
251− row_ops_for_image mrow_ops i bg region skip_dim y (min remaining_columns max_w) (min remain_rows $ fromEnum max_h)
252− ImagePad (min_w,min_h) i -> do
253− let hpad = if image_width i < min_w
254− then background_fill (min_w - image_width i) (image_height i)
255− else empty_image
256− let vpad = if image_height i < min_h
257− then background_fill (image_width i) (min_h - image_height i)
258− else empty_image
259− row_ops_for_image mrow_ops ((i <|> hpad) <-> vpad) bg region skip_dim y remaining_columns remain_rows
260−
261−snoc_text_span :: Attr -- the display attributes of the text span
262− -> DisplayString -- the text to output
263− -> MRowOps s -- the display operations to add to
264− -> Word -- the number of display columns in the text span to
265− -- skip before outputting
266− -> Int -- the row of the display operations to add to
267− -> Word -- the number of columns from the next column to be
268− -- defined to the end of the display for the row.
269− -> ST s Word
270−snoc_text_span a text_str mrow_ops columns_to_skip y remaining_columns = do
271− {-# SCC "snoc_text_span-pre" #-} snoc_op mrow_ops y $! AttributeChange a
272− -- At most a text span will consist of remaining_columns characters
273− -- we keep track of the position of the next character.
274− let max_len :: Int = fromEnum remaining_columns
275− mspan_chars <- Vector.new max_len
276− ( used_display_columns, display_columns_skipped, used_char_count )
277− <- {-# SCC "snoc_text_span-foldlM" #-} Foldable.foldlM (build_text_span mspan_chars) ( 0, 0, 0 ) text_str
278− -- once all characters have been output to mspan_chars we grab the used head
279− out_text <- Vector.unsafeFreeze $! Vector.take used_char_count mspan_chars
280− -- convert to UTF8 bytestring.
281− -- This could be made faster. Hopefully the optimizer does a fair job at fusing the fold
282− -- contained in fromString with the unfold in toList. No biggy right now then.
283− {-# SCC "snoc_text_span-post" #-} snoc_op mrow_ops y $! TextSpan used_display_columns (toEnum used_char_count)
284− $! UTF8.fromString
285− $! Vector.toList out_text
286− return $ columns_to_skip - display_columns_skipped
287− where
288− build_text_span mspan_chars (!used_display_columns, !display_columns_skipped, !used_char_count)
289− (out_char, char_display_width) = {-# SCC "build_text_span" #-}
290− -- Only valid if the maximum width of a character is 2 display columns.
291− -- XXX: Optimize into a skip pass then clipped fill pass
292− if display_columns_skipped == columns_to_skip
293− then if used_display_columns == remaining_columns
294− then return $! ( used_display_columns, display_columns_skipped, used_char_count )
295− else if ( used_display_columns + char_display_width ) > remaining_columns
296− then do
297− Vector.unsafeWrite mspan_chars used_char_count '…'
298− return $! ( used_display_columns + 1
299− , display_columns_skipped
300− , used_char_count + 1
301− )
302− else do
303− Vector.unsafeWrite mspan_chars used_char_count out_char
304− return $! ( used_display_columns + char_display_width
305− , display_columns_skipped
306− , used_char_count + 1
307− )
308− else if (display_columns_skipped + char_display_width) > columns_to_skip
309− then do
310− Vector.unsafeWrite mspan_chars used_char_count '…'
311− return $! ( used_display_columns + 1
312− , columns_to_skip
313− , used_char_count + 1
314− )
315− else return $ ( used_display_columns
316− , display_columns_skipped + char_display_width
317− , used_char_count
318− )
276+ row_ops_for_image mrow_ops i bg region skip_dim y (min remaining_columns max_w) (min remain_rows max_h)
277+
278+render_clipped_text_span :: DisplayString -> Int -> Int -> BlitM ()
279+render_clipped_text_span txt left_skip right_clip = do
280+ use row_offset >>= snoc_op (AttributeChange a)
281+ -- TODO: store a skip list in HorizText
282+ let (to_drop,pad_prefix) = clip_for_char_width left_skip txt 0
283+ txt' = TL.append (if pad_prefix then TL.singleton '…' else TL.empty) (TL.drop to_drop txt)
284+ (to_take,pad_suffix) = clip_for_char_width right_clip txt' 0
285+ txt'' = TL.append (TL.take to_take txt') (if pad_suffix then TL.singleton '…' else TL.empty)
286+ clip_for_char_width 0 _ n = (n, False)
287+ clip_for_char_width 1 t n
288+ | wcwidth (TL.head t) == 1 = (n+1, False)
289+ | otherwise = (n, True)
290+ clip_for_char_width lc t n
291+ = apply_left_clip (lc - wcwidth (TL.head t)) (TL.rest t) (n + 1)
292+ render_unclipped_text_span a txt''
293+
294+render_unclipped_text_span :: DisplayString -> BlitM ()
295+render_unclipped_text_span txt = do
296+ let op = TextSpan used_display_columns (TL.length txt) (TL.encodeUtf8 txt)
297+ used_display_columns = wcswidth $ TL.unpack txt
298+ use row_offset >>= snoc_op op
319299
320300-- | Add a background fill of the given column width to the row display operations.
321301--
322302-- This has a fast path for background characters that are a single column and a single byte.
323303-- Otherwise this has to compute the width of the background character and replicate a sequence of
324304-- bytes to fill in the required width.
325−snoc_bg_fill :: MRowOps s -> Background -> Word -> Int -> ST s ()
305+snoc_bg_fill :: MRowOps s -> Background -> Int -> Int -> ST s ()
326306snoc_bg_fill _row_ops _bg 0 _row
327307 = return ()
328308snoc_bg_fill mrow_ops (Background c back_attr) fill_length row
@@ -334,21 +314,23 @@ snoc_bg_fill mrow_ops (Background c back_attr) fill_length row
334314 then
335315 let !(c_byte :: Word8) = BInt.c2w c
336316 in unsafeIOToST $ do
337− BInt.create ( fromEnum fill_length )
317+ BInt.create fill_length
338318 $ \ptr -> mapM_ (\i -> pokeByteOff ptr i c_byte)
339− [0 .. fromEnum (fill_length - 1)]
319+ [0 .. (fill_length - 1)]
340320 else
341321 let !(c_bytes :: [Word8]) = encode [c]
342322 in unsafeIOToST $ do
343− BInt.create (fromEnum fill_length * length c_bytes)
323+ BInt.create (fill_length * length c_bytes)
344324 $ \ptr -> mapM_ (\(i,b) -> pokeByteOff ptr i b)
345− $ zip [0 .. fromEnum (fill_length - 1)] (cycle c_bytes)
325+ $ zip [0 .. (fill_length - 1)] (cycle c_bytes)
346326 snoc_op mrow_ops row $ TextSpan fill_length fill_length (UTF8.fromRep utf8_bs)
347327
348328-- | snocs the operation to the operations for the given row.
349−snoc_op :: MRowOps s -> Int -> SpanOp -> ST s ()
350−snoc_op !mrow_ops !row !op = do
351− ops <- Vector.read mrow_ops row
352− let ops' = Vector.snoc ops op
353− Vector.write mrow_ops row ops'
329+snoc_op :: SpanOp -> Int -> BlitM s ()
330+snoc_op !op !row = do
331+ the_mrow_ops <- view mrow_ops
332+ lift $ do
333+ ops <- Vector.read the_mrow_ops row
334+ let ops' = Vector.snoc ops op
335+ Vector.write the_mrow_ops row ops'
354336
test/current_benchmark_results.txtnew file+16
@@ -0,0 +1,16 @@
1+starting no-diff-opt-0
2+user time: 226
3+system time: 13
4+starting render-char-0
5+user time: 411
6+system time: 22
7+starting render-char-1
8+user time: 386
9+system time: 21
10+starting vertical-scroll-0
11+user time: 1930
12+system time: 96
13+starting image-fuzz-0
14+user time: 1225
15+system time: 52
16+[("no-diff-opt-0",226,13),("render-char-0",411,22),("render-char-1",386,21),("vertical-scroll-0",1930,96),("image-fuzz-0",1225,52)]
vty.cabal+16
@@ -43,6 +43,7 @@ library
4343 containers,
4444 deepseq >= 1.1 && < 1.4,
4545 ghc-prim,
46+ lens,
4647 mtl >= 1.1.1.0 && < 2.2,
4748 parallel >= 2.2 && < 3.3,
4849 parsec >= 2 && < 4,
@@ -99,6 +100,7 @@ test-suite verify-attribute-ops
99100 containers,
100101 deepseq >= 1.1 && < 1.4,
101102 ghc-prim,
103+ lens,
102104 mtl >= 1.1.1.0 && < 2.2,
103105 parallel >= 2.2 && < 3.3,
104106 parsec >= 2 && < 4,
@@ -150,6 +152,7 @@ test-suite verify-using-mock-terminal
150152 containers,
151153 deepseq >= 1.1 && < 1.4,
152154 ghc-prim,
155+ lens,
153156 mtl >= 1.1.1.0 && < 2.2,
154157 parallel >= 2.2 && < 3.3,
155158 parsec >= 2 && < 4,
@@ -201,6 +204,7 @@ test-suite verify-display-attributes
201204 containers,
202205 deepseq >= 1.1 && < 1.4,
203206 ghc-prim,
207+ lens,
204208 mtl >= 1.1.1.0 && < 2.2,
205209 parallel >= 2.2 && < 3.3,
206210 parsec >= 2 && < 4,
@@ -229,6 +233,7 @@ test-suite verify-empty-image-props
229233 containers,
230234 deepseq >= 1.1 && < 1.4,
231235 ghc-prim,
236+ lens,
232237 mtl >= 1.1.1.0 && < 2.2,
233238 parallel >= 2.2 && < 3.3,
234239 parsec >= 2 && < 4,
@@ -260,6 +265,7 @@ test-suite verify-eval-terminfo-caps
260265 containers,
261266 deepseq >= 1.1 && < 1.4,
262267 ghc-prim,
268+ lens,
263269 mtl >= 1.1.1.0 && < 2.2,
264270 parallel >= 2.2 && < 3.3,
265271 parsec >= 2 && < 4,
@@ -303,6 +309,7 @@ test-suite verify-image-ops
303309 containers,
304310 deepseq >= 1.1 && < 1.4,
305311 ghc-prim,
312+ lens,
306313 mtl >= 1.1.1.0 && < 2.2,
307314 parallel >= 2.2 && < 3.3,
308315 parsec >= 2 && < 4,
@@ -340,6 +347,7 @@ test-suite verify-image-trans
340347 containers,
341348 deepseq >= 1.1 && < 1.4,
342349 ghc-prim,
350+ lens,
343351 mtl >= 1.1.1.0 && < 2.2,
344352 parallel >= 2.2 && < 3.3,
345353 parsec >= 2 && < 4,
@@ -387,6 +395,7 @@ test-suite verify-inline
387395 containers,
388396 deepseq >= 1.1 && < 1.4,
389397 ghc-prim,
398+ lens,
390399 mtl >= 1.1.1.0 && < 2.2,
391400 parallel >= 2.2 && < 3.3,
392401 parsec >= 2 && < 4,
@@ -418,6 +427,7 @@ test-suite verify-parse-terminfo-caps
418427 containers,
419428 deepseq >= 1.1 && < 1.4,
420429 ghc-prim,
430+ lens,
421431 mtl >= 1.1.1.0 && < 2.2,
422432 parallel >= 2.2 && < 3.3,
423433 parsec >= 2 && < 4,
@@ -448,6 +458,7 @@ test-suite verify-picture-ops
448458 containers,
449459 deepseq >= 1.1 && < 1.4,
450460 ghc-prim,
461+ lens,
451462 mtl >= 1.1.1.0 && < 2.2,
452463 parallel >= 2.2 && < 3.3,
453464 parsec >= 2 && < 4,
@@ -499,6 +510,7 @@ test-suite verify-picture-to-span
499510 containers,
500511 deepseq >= 1.1 && < 1.4,
501512 ghc-prim,
513+ lens,
502514 mtl >= 1.1.1.0 && < 2.2,
503515 parallel >= 2.2 && < 3.3,
504516 parsec >= 2 && < 4,
@@ -550,6 +562,7 @@ test-suite verify-span-ops
550562 containers,
551563 deepseq >= 1.1 && < 1.4,
552564 ghc-prim,
565+ lens,
553566 mtl >= 1.1.1.0 && < 2.2,
554567 parallel >= 2.2 && < 3.3,
555568 parsec >= 2 && < 4,
@@ -584,6 +597,7 @@ test-suite verify-utf8-width
584597 containers,
585598 deepseq >= 1.1 && < 1.4,
586599 ghc-prim,
600+ lens,
587601 mtl >= 1.1.1.0 && < 2.2,
588602 parallel >= 2.2 && < 3.3,
589603 parsec >= 2 && < 4,
@@ -610,6 +624,7 @@ executable vty-interactive-terminal-test
610624 containers,
611625 deepseq >= 1.1 && < 1.4,
612626 ghc-prim,
627+ lens,
613628 mtl >= 1.1.1.0 && < 2.2,
614629 parallel >= 2.2 && < 3.3,
615630 parsec >= 2 && < 4,
@@ -638,6 +653,7 @@ executable vty-benchmark
638653 containers,
639654 deepseq >= 1.1 && < 1.4,
640655 ghc-prim,
656+ lens,
641657 mtl >= 1.1.1.0 && < 2.2,
642658 parallel >= 2.2 && < 3.3,
643659 parsec >= 2 && < 4,