@@ -22,11 +22,10 @@ import Control.Monad.State.Strict hiding ( state )
22import Control.Monad.ST.Strict hiding ( unsafeIOToST )
22import Control.Monad.ST.Strict hiding ( unsafeIOToST )
24import qualified Data.Vector as Vector hiding ( take, replicate )
24import qualified Data.Vector as Vector hiding ( take, replicate )
25+import Data.Monoid (mappend)
25import Data.Vector.Mutable ( MVector(..))
26import Data.Vector.Mutable ( MVector(..))
26import qualified Data.Vector.Mutable as MVector
27import qualified Data.Vector.Mutable as MVector
28−import qualified Data.Text as T
29−import qualified Data.Text.Encoding as T
30import qualified Data.Text.Lazy as TL
29import qualified Data.Text.Lazy as TL
32type MRowOps s = MVector s SpanOps
31type MRowOps s = MVector s SpanOps
@@ -36,6 +35,9 @@ type MSpanOps s = MVector s SpanOp
36-- transform plus clip. More or less.
35-- transform plus clip. More or less.
37data BlitState = BlitState
36data BlitState = BlitState
38 -- we always snoc to the operation vectors. Thus the column_offset = length of row at row_offset
37 -- we always snoc to the operation vectors. Thus the column_offset = length of row at row_offset
38+ -- although, one possibility is to merge layers right in snoc_op (naming it something else, of
39+ -- course). In which case columnn_offset would be applicable.
40+ -- Right now we need it to exist.
39 { _column_offset :: Int
41 { _column_offset :: Int
40 , _row_offset :: Int
42 , _row_offset :: Int
41 -- clip coordinate space is in image space. Which means it's >= 0 and < image_width.
43 -- clip coordinate space is in image space. Which means it's >= 0 and < image_width.
@@ -85,7 +87,11 @@ combined_ops_for_layers pic r
85 case layer_ops of
87 case layer_ops of
86 [] -> fail "empty picture"
88 [] -> fail "empty picture"
87 [ops] -> substitute_skips (pic_background pic) ops
89 [ops] -> substitute_skips (pic_background pic) ops
88− _ -> fail "TODO: picture with more than one layer not supported"
90+ -- instead of merging ops after generation the merging can be performed as part of
92+ top_ops : lower_ops -> do
93+ ops <- foldM merge_under top_ops lower_ops
94+ substitute_skips (pic_background pic) ops
90substitute_skips :: Background -> MRowOps s -> ST s (MRowOps s)
96substitute_skips :: Background -> MRowOps s -> ST s (MRowOps s)
91substitute_skips ClearBackground ops = do
97substitute_skips ClearBackground ops = do
@@ -120,22 +126,53 @@ substitute_skips (Background {background_char, background_attr}) ops = do
120 MVector.write ops row row_ops'
126 MVector.write ops row row_ops'
121 return ops
127 return ops
129+merge_under :: MRowOps s -> MRowOps s -> ST s (MRowOps s)
130+merge_under upper lower = do
131+ forM_ [0 .. MVector.length upper - 1] $ \row -> do
132+ upper_row_ops <- MVector.read upper row
133+ lower_row_ops <- MVector.read lower row
134+ let row_ops = merge_row_under upper_row_ops lower_row_ops
135+ MVector.write upper row row_ops
139+merge_row_under :: SpanOps -> SpanOps -> SpanOps
140+merge_row_under upper_row_ops lower_row_ops =
141+ on_upper_op Vector.empty (Vector.head upper_row_ops) (Vector.tail upper_row_ops) lower_row_ops
143+ -- H: it will never be the case that we are out of upper ops before lower ops.
144+ on_upper_op :: SpanOps -> SpanOp -> SpanOps -> SpanOps -> SpanOps
145+ on_upper_op out_ops op@(TextSpan _ w _ _) upper_ops lower_ops =
146+ let lower_ops' = drop_ops w lower_ops
147+ out_ops' = Vector.snoc out_ops op
148+ in if Vector.null lower_ops'
150+ else on_upper_op out_ops' (Vector.head upper_ops) (Vector.tail upper_ops) lower_ops'
151+ on_upper_op out_ops (Skip w) upper_ops lower_ops =
152+ let (ops', lower_ops') = split_ops_at w lower_ops
153+ out_ops' = out_ops `mappend` ops'
154+ in if Vector.null lower_ops'
156+ else on_upper_op out_ops' (Vector.head upper_ops) (Vector.tail upper_ops) lower_ops'
157+ on_upper_op _ (RowEnd _) _ _ = error "cannot merge rows containing RowEnd ops"
123swap_skips_for_single_column_char_span :: Char -> Attr -> SpanOps -> SpanOps
160swap_skips_for_single_column_char_span :: Char -> Attr -> SpanOps -> SpanOps
124swap_skips_for_single_column_char_span c a = Vector.map f
161swap_skips_for_single_column_char_span c a = Vector.map f
125− where f (Skip ow) = let txt = T.pack $ replicate ow c
162+ where f (Skip ow) = let txt = TL.pack $ replicate ow c
126− in TextSpan a ow ow (T.encodeUtf8 txt)
163+ in TextSpan a ow ow txt
129swap_skips_for_char_span :: Int -> Char -> Attr -> SpanOps -> SpanOps
166swap_skips_for_char_span :: Int -> Char -> Attr -> SpanOps -> SpanOps
130swap_skips_for_char_span w c a = Vector.map f
167swap_skips_for_char_span w c a = Vector.map f
132 f (Skip ow) = let txt_0_cw = ow `div` w
169 f (Skip ow) = let txt_0_cw = ow `div` w
133− txt_0 = T.pack $ replicate txt_0_cw c
170+ txt_0 = TL.pack $ replicate txt_0_cw c
134 txt_1_cw = ow `mod` w
171 txt_1_cw = ow `mod` w
135− txt_1 = T.pack $ replicate txt_1_cw '…'
172+ txt_1 = TL.pack $ replicate txt_1_cw '…'
136 cw = txt_0_cw + txt_1_cw
173 cw = txt_0_cw + txt_1_cw
137− txt = txt_0 `T.append` txt_1
174+ txt = txt_0 `TL.append` txt_1
138− in TextSpan a ow cw $ T.encodeUtf8 txt
175+ in TextSpan a ow cw txt
141-- | Builds a vector of row operations that will output the given picture to the terminal.
178-- | Builds a vector of row operations that will output the given picture to the terminal.
@@ -279,31 +316,11 @@ add_maybe_clipped_join name skip remaining offset i0_dim i0 i1 size = do
279 add_maybe_clipped i1
316 add_maybe_clipped i1
280 _ -> fail $ name ++ " has unhandled skip class"
317 _ -> fail $ name ++ " has unhandled skip class"
282−-- TODO: store a skip list in HorizText(?)
283−-- TODO: represent display strings containing chars that are not 1 column chars as a separate
284−-- display string value?
285−clip_text :: DisplayText -> Int -> Int -> DisplayText
286−clip_text txt left_skip right_clip =
287− -- CPS would clarify this I think
288− let (to_drop,pad_prefix) = clip_for_char_width left_skip txt 0
289− txt' = if pad_prefix then TL.cons '…' (TL.drop (to_drop+1) txt) else TL.drop to_drop txt
290− (to_take,pad_suffix) = clip_for_char_width right_clip txt' 0
291− txt'' = TL.append (TL.take to_take txt') (if pad_suffix then TL.singleton '…' else TL.empty)
292− clip_for_char_width 0 _ n = (n, False)
293− clip_for_char_width w t n
294− | TL.null t = (n, False)
295− | w < cw = (n, True)
296− | w == cw = (n+1, False)
297− | w > cw = clip_for_char_width (w - cw) (TL.tail t) (n + 1)
298− where cw = safe_wcwidth (TL.head t)
299− clip_for_char_width _ _ _ = error "clip_for_char_width applied to undefined"
302add_unclipped_text :: Attr -> DisplayText -> BlitM s ()
319add_unclipped_text :: Attr -> DisplayText -> BlitM s ()
303add_unclipped_text a txt = do
320add_unclipped_text a txt = do
304 let op = TextSpan a used_display_columns
321 let op = TextSpan a used_display_columns
305 (fromIntegral $ TL.length txt)
322 (fromIntegral $ TL.length txt)
306− (T.encodeUtf8 $ TL.toStrict txt)
323+ txt
307 used_display_columns = wcswidth $ TL.unpack txt
324 used_display_columns = wcswidth $ TL.unpack txt
308 use row_offset >>= snoc_op op
325 use row_offset >>= snoc_op op