@@ -1,3 +1,4 @@
1+{-# LANGUAGE RankNTypes #-}
1-- Copyright Corey O'Connor
2-- Copyright Corey O'Connor
2{-# LANGUAGE TemplateHaskell #-}
3{-# LANGUAGE TemplateHaskell #-}
3{-# LANGUAGE BangPatterns #-}
4{-# LANGUAGE BangPatterns #-}
@@ -25,6 +26,7 @@ import Graphics.Vty.Picture
25import Graphics.Vty.DisplayRegion
26import Graphics.Vty.DisplayRegion
26import Graphics.Text.Width
27import Graphics.Text.Width
29+import Control.Applicative
28import Control.Lens
30import Control.Lens
29import Control.Monad ( forM_ )
31import Control.Monad ( forM_ )
30import Control.Monad.Reader
32import Control.Monad.Reader
@@ -38,9 +40,12 @@ import qualified Data.Vector as Vector hiding ( take, replicate )
38import Data.Vector.Mutable ( MVector(..))
40import Data.Vector.Mutable ( MVector(..))
39import qualified Data.Vector.Mutable as Vector
41import qualified Data.Vector.Mutable as Vector
43+import qualified Data.ByteString as BS
41import qualified Data.ByteString.Lazy as BL
44import qualified Data.ByteString.Lazy as BL
42−import qualified Data.ByteString.Lazy.Internal as BLInt
45+import qualified Data.ByteString.Lazy.Internal as BL
43import qualified Data.Foldable as Foldable
46import qualified Data.Foldable as Foldable
47+import qualified Data.Text as T
48+import qualified Data.Text.Encoding as T
44import qualified Data.Text.Lazy as TL
49import qualified Data.Text.Lazy as TL
45import qualified Data.Text.Lazy.Encoding as TL
50import qualified Data.Text.Lazy.Encoding as TL
46import Data.Word
51import Data.Word
@@ -58,7 +63,7 @@ data SpanOp =
58 -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF
63 -- | a span of UTF-8 text occupies a specific number of screen space columns. A single UTF
59 -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width
64 -- character does not necessarially represent 1 colunm. See Codec.Binary.UTF8.Width
60 -- TextSpan [output width in columns] [number of characters] [data]
65 -- TextSpan [output width in columns] [number of characters] [data]
61− | TextSpan !Int !Int BL.ByteString
66+ | TextSpan !Int !Int BS.ByteString
62 deriving Eq
67 deriving Eq
64-- | vector of span operations. executed in succession. This represents the operations required to
69-- | vector of span operations. executed in succession. This represents the operations required to
@@ -67,7 +72,6 @@ data SpanOp =
67-- color is changed.
72-- color is changed.
68type SpanOps = Vector SpanOp
73type SpanOps = Vector SpanOp
71data DisplayOps = DisplayOps
75data DisplayOps = DisplayOps
72 { effected_region :: DisplayRegion
76 { effected_region :: DisplayRegion
73 , display_ops :: RowOps
77 , display_ops :: RowOps
@@ -91,7 +95,8 @@ instance Show SpanOp where
91-- transform plus clip. More or less.
95-- transform plus clip. More or less.
92data BlitState = BlitState
96data BlitState = BlitState
93 -- we always snoc to the operation vectors. Thus the column_offset = length of row at row_offset
97 -- we always snoc to the operation vectors. Thus the column_offset = length of row at row_offset
98+ { _column_offset :: Int
94− { _row_offset :: Int
99+ , _row_offset :: Int
95 -- clip coordinate space is in image space. Which means it's >= 0 and < image_width.
100 -- clip coordinate space is in image space. Which means it's >= 0 and < image_width.
96 , _skip_columns :: Int
101 , _skip_columns :: Int
97 -- >= 0 and < image_height
102 -- >= 0 and < image_height
@@ -117,15 +122,15 @@ makeLenses ''BlitEnv
117type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a
122type BlitM s a = ReaderT (BlitEnv s) (StateT BlitState (ST s)) a
119-- | Number of columns the DisplayOps are defined for
124-- | Number of columns the DisplayOps are defined for
120−span_ops_columns :: DisplayOps -> Word
125+span_ops_columns :: DisplayOps -> Int
121span_ops_columns ops = region_width $ effected_region ops
126span_ops_columns ops = region_width $ effected_region ops
123-- | Number of rows the DisplayOps are defined for
128-- | Number of rows the DisplayOps are defined for
124−span_ops_rows :: DisplayOps -> Word
129+span_ops_rows :: DisplayOps -> Int
125span_ops_rows ops = region_height $ effected_region ops
130span_ops_rows ops = region_height $ effected_region ops
127-- | The number of columns a SpanOps effects.
132-- | The number of columns a SpanOps effects.
128−span_ops_effected_columns :: SpanOps -> Word
133+span_ops_effected_columns :: SpanOps -> Int
129span_ops_effected_columns in_ops = Vector.foldl' span_ops_effected_columns' 0 in_ops
134span_ops_effected_columns in_ops = Vector.foldl' span_ops_effected_columns' 0 in_ops
131 span_ops_effected_columns' t (TextSpan w _ _ ) = t + w
136 span_ops_effected_columns' t (TextSpan w _ _ ) = t + w
@@ -139,7 +144,7 @@ span_op_has_width _ = Nothing
139-- | returns the number of columns to the character at the given position in the span op
144-- | returns the number of columns to the character at the given position in the span op
140columns_to_char_offset :: Int -> SpanOp -> Int
145columns_to_char_offset :: Int -> SpanOp -> Int
141columns_to_char_offset cx (TextSpan _ _ utf8_str) =
146columns_to_char_offset cx (TextSpan _ _ utf8_str) =
142− let str = TL.unpack (TL.decodeUtf8 utf8_str)
147+ let str = T.unpack (T.decodeUtf8 utf8_str)
143 in wcswidth (take cx str)
148 in wcswidth (take cx str)
144columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
149columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
@@ -166,24 +171,18 @@ build_spans pic region = do
167 -- The images are made into span operations from left to right. It's possible that this could
172 -- The images are made into span operations from left to right. It's possible that this could
168 -- easily be made to assure top to bottom output as well.
173 -- easily be made to assure top to bottom output as well.
169− if region_height region > 0
174+ when (region_height region > 0 && region_width region > 0) $ do
171− -- The ops builder recursively descends the image and outputs span ops that would
175+ -- The ops builder recursively descends the image and outputs span ops that would
172− -- display that image. The number of columns remaining in this row before exceeding the
176+ -- display that image. The number of columns remaining in this row before exceeding the
173− -- bounds is also provided. This is used to clip the span ops produced to the display.
177+ -- bounds is also provided. This is used to clip the span ops produced to the display.
174− -- The skip dimensions provided do....???
178+ let full_build = do
175− _ <- runStateT (runReaderT (start_image_build $ pic_image pic)
179+ start_image_build $ pic_image pic
176− (BlitEnv (pic_background pic) region mrow_ops)
178− (BlitState 0 0 0 0 (region_width region) (region_height region))
179− -- Fill in any unspecified columns with the background pattern.
180+ -- Fill in any unspecified columns with the background pattern.
180− -- todo: If there is no background pattern defined then skip
181− forM_ [0 .. (region_height region - 1)] $! \row -> do
181+ forM_ [0 .. (region_height region - 1)] add_row_completion
182− end_x <- Vector.read mrow_ops row >>= return . span_ops_effected_columns
182+ init_env = BlitEnv (pic_background pic) region mrow_ops
183− if end_x < region_width region
183+ init_state = BlitState 0 0 0 0 (region_width region) (region_height region)
184− then snoc_bg_fill mrow_ops (pic_background pic) (region_width region - end_x) row
184+ _ <- runStateT (runReaderT full_build init_env) init_state
186− else return ()
185+ return ()
187 return mrow_ops
186 return mrow_ops
189-- | Add the operations required to build a given image to the current set of row operations
188-- | Add the operations required to build a given image to the current set of row operations
@@ -191,14 +190,12 @@ build_spans pic region = do
191start_image_build :: Image -> BlitM s ()
190start_image_build :: Image -> BlitM s ()
192start_image_build image = do
191start_image_build image = do
193 out_of_bounds <- is_out_of_bounds image <$> get
192 out_of_bounds <- is_out_of_bounds image <$> get
196− else add_maybe_clipped image
193+ when (not out_of_bounds) $ add_maybe_clipped image
198is_out_of_bounds :: Image -> BlitState -> Bool
195is_out_of_bounds :: Image -> BlitState -> Bool
199is_out_of_bounds image s
196is_out_of_bounds image s
200 | s ^. remaining_columns <= 0 = True
197 | s ^. remaining_columns <= 0 = True
201− | s ^. remain_rows <= 0 = True
198+ | s ^. remaining_rows <= 0 = True
202 | otherwise = False
199 | otherwise = False
204-- TODO: prove skip_columns, skip_rows == 0
201-- TODO: prove skip_columns, skip_rows == 0
@@ -206,7 +203,7 @@ is_out_of_bounds image s
206-- TODO: prove remaining_rows >= image_height
203-- TODO: prove remaining_rows >= image_height
207add_unclipped :: Image -> BlitM s ()
204add_unclipped :: Image -> BlitM s ()
208add_unclipped EmptyImage = return ()
205add_unclipped EmptyImage = return ()
209−add_unclipped (HorizText text_str _ow _cw) = do
206+add_unclipped (HorizText a text_str _ow _cw) = do
210 use row_offset >>= snoc_op (AttributeChange a)
207 use row_offset >>= snoc_op (AttributeChange a)
211 add_unclipped_text text_str
208 add_unclipped_text text_str
212add_unclipped (HorizJoin part_left part_right _ow _oh) = do
209add_unclipped (HorizJoin part_left part_right _ow _oh) = do
@@ -224,8 +221,10 @@ add_unclipped (VertJoin part_top part_bottom _ow _oh) = do
224add_unclipped (BGFill ow oh) = do
221add_unclipped (BGFill ow oh) = do
225 Background c a <- view bg
222 Background c a <- view bg
226 y <- use row_offset
223 y <- use row_offset
227− let op = TextSpan ow ow (TL.replicate (fromIntegral ow) c)
224+ let op = TextSpan ow ow (T.encodeUtf8 $ T.replicate (fromIntegral ow) (T.singleton c))
228− forM_ [y..y+oh] $ snoc_op op
225+ forM_ [y..y+oh] $ \row -> do
226+ snoc_op (AttributeChange a) row
229-- TODO: we know it's clipped actually, the equations that are exposed that introduce a
228-- TODO: we know it's clipped actually, the equations that are exposed that introduce a
230-- Crop all assure the image exceeds the crop in the relevant direction.
229-- Crop all assure the image exceeds the crop in the relevant direction.
231add_unclipped CropRight {cropped_image, output_width} = do
230add_unclipped CropRight {cropped_image, output_width} = do
@@ -242,9 +241,9 @@ add_unclipped CropTop {cropped_image, top_skip} = do
242 add_maybe_clipped cropped_image
241 add_maybe_clipped cropped_image
244-- TODO: prove this cannot be called in an out of bounds case.
243-- TODO: prove this cannot be called in an out of bounds case.
245−add_maybe_clipped :: Image -> BlitM s ()
244+add_maybe_clipped :: forall s . Image -> BlitM s ()
246add_maybe_clipped EmptyImage = return ()
245add_maybe_clipped EmptyImage = return ()
247−add_maybe_clipped (HorizText text_str ow _cw) = do
246+add_maybe_clipped (HorizText a text_str ow _cw) = do
248 -- TODO: assumption that text spans are only 1 row high.
247 -- TODO: assumption that text spans are only 1 row high.
249 s <- use skip_rows
248 s <- use skip_rows
250 when (s < 1) $ do
249 when (s < 1) $ do
@@ -254,7 +253,7 @@ add_maybe_clipped (HorizText text_str ow _cw) = do
254 let left_clipped = left_clip > 0
253 let left_clipped = left_clip > 0
255 right_clipped = (ow - left_clip) > right_clip
254 right_clipped = (ow - left_clip) > right_clip
256 if left_clipped || right_clipped
255 if left_clipped || right_clipped
257− then let text_str' = clip_text left_clip right_clip
256+ then let text_str' = clip_text text_str left_clip right_clip
258 in add_unclipped_text text_str'
257 in add_unclipped_text text_str'
259 else add_unclipped_text text_str
258 else add_unclipped_text text_str
260add_maybe_clipped (VertJoin top_image bottom_image _ow oh) = do
259add_maybe_clipped (VertJoin top_image bottom_image _ow oh) = do
@@ -264,15 +263,15 @@ add_maybe_clipped (VertJoin top_image bottom_image _ow oh) = do
264 bottom_image
263 bottom_image
266add_maybe_clipped (HorizJoin left_image right_image ow _oh) = do
265add_maybe_clipped (HorizJoin left_image right_image ow _oh) = do
267− add_maybe_clipped_join "horiz_join" skip_columns remaining_columns devoid
266+ add_maybe_clipped_join "horiz_join" skip_columns remaining_columns column_offset
268 (image_width right_image)
267 (image_width right_image)
269 left_image
268 left_image
270 right_image
269 right_image
272add_maybe_clipped BGFill {output_width, output_height} = do
271add_maybe_clipped BGFill {output_width, output_height} = do
274− let output_width' = min (output_width - s.^skip_columns) s.^remaining_columns
273+ let output_width' = min (output_width - s^.skip_columns) (s^.remaining_columns)
275− output_height' = min (output_height - s.^skip_rows ) s.^remaining_rows
274+ output_height' = min (output_height - s^.skip_rows ) (s^.remaining_rows)
276 add_unclipped (BGFill output_width' output_height')
275 add_unclipped (BGFill output_width' output_height')
277add_maybe_clipped CropRight {cropped_image, output_width} = do
276add_maybe_clipped CropRight {cropped_image, output_width} = do
278 remaining_columns .= output_width
277 remaining_columns .= output_width
@@ -287,6 +286,15 @@ add_maybe_clipped CropTop {cropped_image, top_skip} = do
287 skip_rows += top_skip
286 skip_rows += top_skip
288 add_maybe_clipped cropped_image
287 add_maybe_clipped cropped_image
289+add_maybe_clipped_join :: forall s . String
290+ -> Lens BlitState BlitState Int Int
291+ -> Lens BlitState BlitState Int Int
292+ -> Lens BlitState BlitState Int Int
290add_maybe_clipped_join name skip remaining offset i0_dim i0 i1 size = do
298add_maybe_clipped_join name skip remaining offset i0_dim i0 i1 size = do
291 state <- get
299 state <- get
292 when (state^.remaining == 0) $ fail $ name ++ " with remaining == 0"
300 when (state^.remaining == 0) $ fail $ name ++ " with remaining == 0"
@@ -313,7 +321,7 @@ add_maybe_clipped_join name skip remaining offset i0_dim i0 i1 size = do
313-- TODO: represent display strings containing chars that are not 1 column chars as a separate
321-- TODO: represent display strings containing chars that are not 1 column chars as a separate
314-- display string value?
322-- display string value?
315-- TODO: assumes max column width is 2
323-- TODO: assumes max column width is 2
316−clip_text :: DisplayString -> Int -> Int -> DisplayString
324+clip_text :: DisplayText -> Int -> Int -> DisplayText
317clip_text txt left_skip right_clip =
325clip_text txt left_skip right_clip =
318 -- CPS would clarify this I think
326 -- CPS would clarify this I think
319 let (to_drop,pad_prefix) = clip_for_char_width left_skip txt 0
327 let (to_drop,pad_prefix) = clip_for_char_width left_skip txt 0
@@ -324,21 +332,38 @@ clip_text txt left_skip right_clip =
324 clip_for_char_width w t n
332 clip_for_char_width w t n
325 | w < cw = (n, True)
333 | w < cw = (n, True)
326 | w == cw = (n+1, False)
334 | w == cw = (n+1, False)
327− | w > cw = clip_for_char_width (w - cw) (TL.rest t) (n + 1)
335+ | w > cw = clip_for_char_width (w - cw) (TL.tail t) (n + 1)
328 where cw = safe_wcwidth (TL.head t)
336 where cw = safe_wcwidth (TL.head t)
331−add_unclipped_text :: DisplayString -> BlitM ()
339+add_unclipped_text :: DisplayText -> BlitM s ()
332add_unclipped_text txt = do
340add_unclipped_text txt = do
333− let op = TextSpan used_display_columns (TL.length txt) (TL.encodeUtf8 txt)
341+ let op = TextSpan used_display_columns
342+ (fromIntegral $ TL.length txt)
343+ (T.encodeUtf8 $ TL.toStrict txt)
334 used_display_columns = wcswidth $ TL.unpack txt
344 used_display_columns = wcswidth $ TL.unpack txt
335 use row_offset >>= snoc_op op
345 use row_offset >>= snoc_op op
347+-- todo: If there is no background pattern defined then skip to next line.
348+-- todo: assumes background character is 1 column
349+add_row_completion :: Int -> BlitM s ()
350+add_row_completion row = do
351+ all_row_ops <- view mrow_ops
352+ Background c a <- view bg
353+ display_region <- view region
354+ row_ops <- lift $ lift $ Vector.read all_row_ops row
355+ let end_x = span_ops_effected_columns row_ops
356+ when (end_x < region_width display_region) $ do
357+ let ow = region_width display_region - end_x
358+ op = TextSpan ow ow (T.encodeUtf8 $ T.replicate (fromIntegral ow) (T.singleton c))
359+ snoc_op (AttributeChange a) row
337-- | snocs the operation to the operations for the given row.
362-- | snocs the operation to the operations for the given row.
338snoc_op :: SpanOp -> Int -> BlitM s ()
363snoc_op :: SpanOp -> Int -> BlitM s ()
339snoc_op !op !row = do
364snoc_op !op !row = do
340 the_mrow_ops <- view mrow_ops
365 the_mrow_ops <- view mrow_ops
341− lift $ do
366+ lift $ lift $ do
342 ops <- Vector.read the_mrow_ops row
367 ops <- Vector.read the_mrow_ops row
343 let ops' = Vector.snoc ops op
368 let ops' = Vector.snoc ops op
344 Vector.write the_mrow_ops row ops'
369 Vector.write the_mrow_ops row ops'