@@ -18,18 +18,19 @@ import Codec.Binary.UTF8.String ( encode )
18import Control.Monad ( forM_ )
18import Control.Monad ( forM_ )
19import Control.Monad.ST.Strict
19import Control.Monad.ST.Strict
21−import Data.Array.ST
21+import Data.Vector (Vector)
22+import qualified Data.Vector as Vector hiding ( take, replicate )
23+import Data.Vector.Mutable ( MVector(..))
24+import qualified Data.Vector.Mutable as Vector
22import qualified Data.ByteString as B
26import qualified Data.ByteString as B
23import qualified Data.ByteString.Internal as BInt
27import qualified Data.ByteString.Internal as BInt
24import qualified Data.Foldable as Foldable
28import qualified Data.Foldable as Foldable
26import qualified Data.String.UTF8 as UTF8
29import qualified Data.String.UTF8 as UTF8
27import Data.Word
30import Data.Word
29import Foreign.Storable ( pokeByteOff )
32import Foreign.Storable ( pokeByteOff )
33{- | A picture is translated into a sequences of state changes and character spans.
34{- | A picture is translated into a sequences of state changes and character spans.
34 - State changes are currently limited to new attribute values. The attribute is applied to all
35 - State changes are currently limited to new attribute values. The attribute is applied to all
35 - following spans. Including spans of the next row. The nth element of the sequence represents the
36 - following spans. Including spans of the next row. The nth element of the sequence represents the
@@ -37,39 +38,45 @@ import GHC.Arr
38 - A span op sequence will be defined for all rows and columns (and no more) of the region provided
39 - A span op sequence will be defined for all rows and columns (and no more) of the region provided
39 - with the picture to spans_for_pic.
40 - with the picture to spans_for_pic.
42 - todo: Partition attribute changes into multiple categories according to the serialized
42 - todo: Partition attribute changes into multiple categories according to the serialized
43 - representation of the various attributes.
43 - representation of the various attributes.
45−data SpanOpSequence = SpanOpSequence
46− { effected_region :: DisplayRegion
46+data DisplayOps = DisplayOps
47+ { effected_region :: DisplayRegion
48+ , display_ops :: RowOps
51+-- vector of span operation vectors. One per row of the screen.
50−type RowOps = Array Word SpanOps
52+type RowOps = Vector SpanOps
51−type SpanOps = [SpanOp]
53+
54+type MRowOps s = MVector s SpanOps
56+-- vector of span operations. executed in succession
57+type SpanOps = Vector SpanOp
59+type MSpanOps s = MVector s SpanOp
53−instance Show SpanOpSequence where
61+instance Show DisplayOps where
54− show (SpanOpSequence _ the_row_ops)
62+ show (DisplayOps _ the_row_ops)
55− = concat $ ["{ "] ++ map (\ops -> show ops ++ "; " ) (elems the_row_ops) ++ [" }"]
63+ = "{ " ++ (show $ Vector.map (\ops -> show ops ++ "; " ) the_row_ops) ++ " }"
57instance Show SpanOp where
65instance Show SpanOp where
58 show (AttributeChange attr) = show attr
66 show (AttributeChange attr) = show attr
59 show (TextSpan ow cw _) = "TextSpan " ++ show ow ++ " " ++ show cw
67 show (TextSpan ow cw _) = "TextSpan " ++ show ow ++ " " ++ show cw
61−span_ops_columns :: SpanOpSequence -> Word
69+span_ops_columns :: DisplayOps -> Word
62span_ops_columns ops = region_width $ effected_region ops
70span_ops_columns ops = region_width $ effected_region ops
64−span_ops_rows :: SpanOpSequence -> Word
72+span_ops_rows :: DisplayOps -> Word
65span_ops_rows ops = region_height $ effected_region ops
73span_ops_rows ops = region_height $ effected_region ops
67−span_ops_effected_columns :: [SpanOp] -> Word
75+span_ops_effected_columns :: SpanOps -> Word
68−span_ops_effected_columns in_ops = span_ops_effected_columns' 0 in_ops
76+span_ops_effected_columns in_ops = Vector.foldl' span_ops_effected_columns' 0 in_ops
70− span_ops_effected_columns' t [] = t
78+ span_ops_effected_columns' t (TextSpan w _ _ ) = t + w
71− span_ops_effected_columns' t (TextSpan w _ _ : r) = span_ops_effected_columns' (t + w) r
72− span_ops_effected_columns' t (_ : r) = span_ops_effected_columns' t r
79+ span_ops_effected_columns' t _ = t
@@ -84,54 +91,70 @@ data SpanOp =
84 | TextSpan !Word !Word (UTF8.UTF8 B.ByteString)
91 | TextSpan !Word !Word (UTF8.UTF8 B.ByteString)
85 deriving Eq
92 deriving Eq
94+-- used to determine the width of a span operation , if it has one.
87span_op_has_width :: SpanOp -> Maybe (Word, Word)
95span_op_has_width :: SpanOp -> Maybe (Word, Word)
88span_op_has_width (TextSpan ow cw _) = Just (cw, ow)
96span_op_has_width (TextSpan ow cw _) = Just (cw, ow)
89span_op_has_width _ = Nothing
97span_op_has_width _ = Nothing
99+-- returns the number of columns to the character at the given position in the span op
91columns_to_char_offset :: Word -> SpanOp -> Word
100columns_to_char_offset :: Word -> SpanOp -> Word
92columns_to_char_offset cx (TextSpan _ _ utf8_str) =
101columns_to_char_offset cx (TextSpan _ _ utf8_str) =
93 let str = UTF8.toString utf8_str
102 let str = UTF8.toString utf8_str
94− in toEnum $ sum $ map wcwidth $ take (fromEnum cx) str
103+ in toEnum $! sum $! map wcwidth $! take (fromEnum cx) str
95columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
104columns_to_char_offset _cx _ = error "columns_to_char_offset applied to span op without width"
97-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the
106-- | Produces the span ops that will render the given picture, possibly cropped or padded, into the
98-- specified region.
107-- specified region.
99−spans_for_pic :: Picture -> DisplayRegion -> SpanOpSequence
108+spans_for_pic :: Picture -> DisplayRegion -> DisplayOps
100−spans_for_pic pic r = SpanOpSequence r $ runSTArray (build_spans pic r)
109+spans_for_pic pic r = DisplayOps r $ Vector.create (build_spans pic r)
102−build_spans :: Picture -> DisplayRegion -> ST s (STArray s Word [SpanOp])
111+build_spans :: Picture -> DisplayRegion -> ST s (MRowOps s)
103build_spans pic region = do
112build_spans pic region = do
104− -- m for mutable
113+ -- m for mutable! ;-)
105− mrow_ops <- newSTArray (0, region_height region - 1) []
114+ mrow_ops <- Vector.replicate (fromEnum $ region_height region) Vector.empty
106− -- I think this can be better optimized if the build produced span operations in display order.
115+ -- XXX: I think building the span operations in display order would provide better performance.
107 -- However, I got stuck trying to implement an algorithm that did this. This will be considered
116 -- However, I got stuck trying to implement an algorithm that did this. This will be considered
108 -- as a possible future optimization.
117 -- as a possible future optimization.
110− -- A depth first traversal of the image is performed.
119+ -- A depth first traversal of the image is performed. ordered according to the column range
111− -- ordered according to the column range defined by the image from least to greatest.
120+ -- defined by the image from least to greatest. The output row ops will at least have the
112− -- The output row ops will at least have the region of the image specified. Iterate over all
113− -- output rows and output background fills for all unspecified columns.
121+ -- region of the image specified. Iterate over all output rows and output background fills for
122+ -- all unspecified columns.
124+ -- The images are made into span operations from left to right. It's possible that this could
125+ -- easily be made to assure top to bottom output as well.
114 if region_height region > 0
126 if region_height region > 0
116 -- The ops builder recursively descends the image and outputs span ops that would
128 -- The ops builder recursively descends the image and outputs span ops that would
117 -- display that image. The number of columns remaining in this row before exceeding the
129 -- display that image. The number of columns remaining in this row before exceeding the
118− -- bounds is also provided. This is used to clip the span ops produced.
130+ -- bounds is also provided. This is used to clip the span ops produced to the display.
119− _ <- ops_for_row mrow_ops (pic_background pic) region (pic_image pic) (0,0) 0 (region_width region)
131+ -- The skip dimensions provided do....???
132+ _ <- row_ops_for_image mrow_ops
134+ (pic_background pic)
138+ (region_width region)
120 -- Fill in any unspecified columns with the background pattern.
139 -- Fill in any unspecified columns with the background pattern.
121− forM_ [0 .. region_height region - 1] $ \row -> do
140+ forM_ [0 .. (fromEnum $ region_height region - 1)] $! \row -> do
122− end_x <- readSTArray mrow_ops row >>= return . span_ops_effected_columns
141+ end_x <- Vector.read mrow_ops row >>= return . span_ops_effected_columns
123 if end_x < region_width region
142 if end_x < region_width region
124 then snoc_bg_fill mrow_ops (pic_background pic) (region_width region - end_x) row
143 then snoc_bg_fill mrow_ops (pic_background pic) (region_width region - end_x) row
125 else return ()
144 else return ()
126 else return ()
145 else return ()
127 return mrow_ops
146 return mrow_ops
129−type MRowOps s = STArray s Word SpanOps
131−ops_for_row :: MRowOps s -> Background -> DisplayRegion -> Image -> (Word, Word) -> Word -> Word -> ST s (Word, Word)
148+row_ops_for_image :: MRowOps s -> Image -> Background -> DisplayRegion -> (Word, Word) -> Int -> Word -> ST s (Word, Word)
149+row_ops_for_image mrow_ops -- the image to output the ops to
150+ image -- the image to rasterize in column order to mrow_ops
151+ bg -- the background fill
132−ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_columns
153+ skip_dim@(skip_row,skip_col) -- the number of rows
155+ remaining_columns -- ???
133 | remaining_columns == 0 = return skip_dim
156 | remaining_columns == 0 = return skip_dim
134− | y >= region_height region = return skip_dim
157+ | y >= fromEnum (region_height region) = return skip_dim
135 | otherwise = case image of
158 | otherwise = case image of
136 EmptyImage -> return skip_dim
159 EmptyImage -> return skip_dim
137 -- The width provided is the number of columns this text span will occupy when displayed.
160 -- The width provided is the number of columns this text span will occupy when displayed.
@@ -143,22 +166,34 @@ ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_co
144 skip_col' <- snoc_text_span a text_str mrow_ops skip_col y remaining_columns
167 skip_col' <- snoc_text_span a text_str mrow_ops skip_col y remaining_columns
145 return (skip_row, skip_col')
168 return (skip_row, skip_col')
146− VertJoin t b _ _ -> do
169+ VertJoin top_image bottom_image _ _ -> do
147− (skip_row',skip_col') <- ops_for_row mrow_ops bg region t skip_dim y remaining_columns
170+ (skip_row',skip_col') <- row_ops_for_image mrow_ops
148− (skip_row'',skip_col'') <- ops_for_row mrow_ops bg region b (skip_row', skip_col) (y + image_height t - (skip_row - skip_row')) remaining_columns
171+ top_image
177+ (skip_row'',skip_col'') <- row_ops_for_image mrow_ops
181+ (skip_row', skip_col)
182+ (y + (fromEnum $! image_height top_image) - (fromEnum $! skip_row - skip_row'))
149 return (skip_row'', min skip_col' skip_col'')
184 return (skip_row'', min skip_col' skip_col'')
150 HorizJoin l r _ _ -> do
185 HorizJoin l r _ _ -> do
151− (skip_row',skip_col') <- ops_for_row mrow_ops bg region l skip_dim y remaining_columns
186+ (skip_row',skip_col') <- row_ops_for_image mrow_ops l bg region skip_dim y remaining_columns
152 -- Don't output the right part unless there is at least a single column left after
187 -- Don't output the right part unless there is at least a single column left after
153 -- outputting the left part.
188 -- outputting the left part.
154 if image_width l - (skip_col - skip_col') > remaining_columns
189 if image_width l - (skip_col - skip_col') > remaining_columns
155 then return (skip_row,skip_col')
190 then return (skip_row,skip_col')
157− (skip_row'',skip_col'') <- ops_for_row mrow_ops bg region r (skip_row, skip_col') y (remaining_columns - image_width l + (skip_col - skip_col'))
192+ (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'))
158 return (min skip_row' skip_row'', skip_col'')
193 return (min skip_row' skip_row'', skip_col'')
159 BGFill width height -> do
194 BGFill width height -> do
160− let min_height = if y + height > region_height region
195+ let min_height = if y + (fromEnum height) > (fromEnum $! region_height region)
161− then region_height region - y
196+ then region_height region - (toEnum y)
162 else height
197 else height
163 min_width = if width > remaining_columns
198 min_width = if width > remaining_columns
164 then remaining_columns
199 then remaining_columns
@@ -169,7 +204,7 @@ ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_co
169 actual_width = if skip_col > min_width
204 actual_width = if skip_col > min_width
171 else min_width - skip_col
206 else min_width - skip_col
172− forM_ [y .. y + actual_height - 1] $ \y' -> snoc_bg_fill mrow_ops bg actual_width y'
207+ forM_ [y .. y + fromEnum actual_height - 1] $! \y' -> snoc_bg_fill mrow_ops bg actual_width y'
173 let skip_row' = if actual_height > skip_row
208 let skip_row' = if actual_height > skip_row
175 else skip_row - min_height
210 else skip_row - min_height
@@ -181,25 +216,25 @@ ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_co
181 if dx < 0
216 if dx < 0
182 -- Translation left
217 -- Translation left
183 -- Extract the delta and add it to skip_col.
218 -- Extract the delta and add it to skip_col.
184− then ops_for_row mrow_ops bg region (translate (0, dy) i) (skip_row, skip_col + dw) y remaining_columns
219+ then row_ops_for_image mrow_ops (translate (0, dy) i) bg region (skip_row, skip_col + dw) y remaining_columns
185 -- Translation right
220 -- Translation right
186 else if dy < 0
221 else if dy < 0
187 -- Translation up
222 -- Translation up
188 -- Extract the delta and add it to skip_row.
223 -- Extract the delta and add it to skip_row.
189− then ops_for_row mrow_ops bg region (translate (dx, 0) i) (skip_row + dh, skip_col) y remaining_columns
224+ then row_ops_for_image mrow_ops (translate (dx, 0) i) bg region (skip_row + dh, skip_col) y remaining_columns
190 -- Translation down
225 -- Translation down
191 -- Pad the start of lines and above the image with a
226 -- Pad the start of lines and above the image with a
192 -- background_fill image.
227 -- background_fill image.
193− else ops_for_row mrow_ops bg region (background_fill ow dh <-> (background_fill dw ih <|> i)) skip_dim y remaining_columns
228+ else row_ops_for_image mrow_ops (background_fill ow dh <-> (background_fill dw ih <|> i)) bg region skip_dim y remaining_columns
195 dw = toEnum $ abs dx
230 dw = toEnum $ abs dx
196 dh = toEnum $ abs dy
231 dh = toEnum $ abs dy
197 ow = image_width image
232 ow = image_width image
198 ih = image_height i
233 ih = image_height i
199 ImageCrop (max_w,max_h) i ->
234 ImageCrop (max_w,max_h) i ->
200− if y >= max_h
235+ if y >= fromEnum max_h
201 then return skip_dim
236 then return skip_dim
202− else ops_for_row mrow_ops bg region i skip_dim y (min remaining_columns max_w)
237+ else row_ops_for_image mrow_ops i bg region skip_dim y (min remaining_columns max_w)
203 ImagePad (min_w,min_h) i -> do
238 ImagePad (min_w,min_h) i -> do
204 let hpad = if image_width i < min_w
239 let hpad = if image_width i < min_w
205 then background_fill (min_w - image_width i) (image_height i)
240 then background_fill (min_w - image_width i) (image_height i)
@@ -207,41 +242,68 @@ ops_for_row mrow_ops bg region image skip_dim@(skip_row,skip_col) y remaining_co
207 let vpad = if image_height i < min_h
242 let vpad = if image_height i < min_h
208 then background_fill (image_width i) (min_h - image_height i)
243 then background_fill (image_width i) (min_h - image_height i)
209 else empty_image
244 else empty_image
210− ops_for_row mrow_ops bg region ((i <|> hpad) <-> vpad) skip_dim y remaining_columns
245+ row_ops_for_image mrow_ops ((i <|> hpad) <-> vpad) bg region skip_dim y remaining_columns
212−snoc_text_span :: Attr
247+snoc_text_span :: Attr -- the display attributes of the text span
213− -> DisplayString
248+ -> DisplayString -- the text to output
214− -> MRowOps s
249+ -> MRowOps s -- the display operations to add to
215− -> Word
250+ -> Word -- the number of display columns in the text span to
216− -> Word
251+ -- skip before outputting
217− -> Word
252+ -> Int -- the row of the display operations to add to
253+ -> Word -- the number of columns from the next column to be
254+ -- defined to the end of the display for the row.
218 -> ST s Word
255 -> ST s Word
219−snoc_text_span a text_str mrow_ops skip_col y remaining_columns = do
256+snoc_text_span a text_str mrow_ops columns_to_skip y remaining_columns = do
220 snoc_op mrow_ops y $ AttributeChange a
257 snoc_op mrow_ops y $ AttributeChange a
221− let (ow', dw', cw', txt) = Foldable.foldl'
258+ -- At most a text span will consist of remaining_columns characters
259+ -- we keep track of the position of the next character.
260+ let max_len :: Int = fromEnum remaining_columns
261+ mspan_chars <- Vector.new max_len
262+ ( used_display_columns, display_columns_skipped, used_char_count )
222− build_text_span
263+ <- Foldable.foldlM (build_text_span mspan_chars) ( 0, 0, 0 ) text_str
223− ( 0, 0, 0, B.empty )
264+ -- once all characters have been output to mspan_chars we grab the used head
224− text_str
265+ out_text <- Vector.unsafeFreeze $! Vector.take used_char_count mspan_chars
266+ -- convert to UTF8 bytestring.
267+ -- This could be made faster. Hopefully the optimizer does a fair job at fusing the fold
268+ -- contained in fromString with the unfold in toList. No biggy right now then.
225− snoc_op mrow_ops y $ TextSpan ow' cw' (UTF8.fromRep txt)
269+ snoc_op mrow_ops y $! TextSpan used_display_columns (toEnum used_char_count)
226− return $ skip_col - dw'
270+ $! UTF8.fromString
271+ $! Vector.toList out_text
272+ return $ columns_to_skip - display_columns_skipped
228− build_text_span (ow', dw', char_count', b0) (c,w) = {-# SCC "build_text_span" #-}
274+ build_text_span mspan_chars (used_display_columns, display_columns_skipped, used_char_count)
229− if dw' == skip_col
275+ (out_char, char_display_width) = {-# SCC "build_text_span" #-}
276+ -- Only valid if the maximum width of a character is 2 display columns.
277+ -- XXX: Optimize into a skip pass then clipped fill pass
278+ if display_columns_skipped == columns_to_skip
230− then if ow' == remaining_columns
279+ then if used_display_columns == remaining_columns
231− then ( ow', dw', char_count', b0 )
280+ then return $! ( used_display_columns, display_columns_skipped, used_char_count )
232− else if (w + ow') > remaining_columns
281+ else if ( used_display_columns + char_display_width ) > remaining_columns
233− then ( remaining_columns, dw', char_count' + ooverflow, B.append b0 oelipses )
282+ then do
234− else ( ow' + w, dw', char_count' + 1, B.append b0 $ B.pack $ encode [c] )
283+ Vector.write mspan_chars used_char_count '…'
235− else if (w + dw') > skip_col
284+ return $! ( used_display_columns
236− then ( doverflow, skip_col, doverflow, B.append b0 delipses )
285+ , display_columns_skipped
237− else ( ow', w + dw', char_count', b0 )
286+ , used_char_count + 1
239− doverflow = skip_col - dw'
288+ else do
240− delipses = B.pack $ encode $ genericReplicate doverflow '…'
289+ Vector.write mspan_chars used_char_count out_char
241− ooverflow = remaining_columns - ow'
290+ return $! ( used_display_columns + char_display_width
242− oelipses = B.pack $ encode $ genericReplicate ooverflow '…'
291+ , display_columns_skipped
292+ , used_char_count + 1
294+ else if (display_columns_skipped + char_display_width) > columns_to_skip
296+ Vector.write mspan_chars used_char_count '…'
297+ return $! ( used_display_columns + 1
299+ , used_char_count + 1
301+ else return $ ( used_display_columns
302+ , display_columns_skipped + char_display_width
244−snoc_bg_fill :: MRowOps s -> Background -> Word -> Word -> ST s ()
306+snoc_bg_fill :: MRowOps s -> Background -> Word -> Int -> ST s ()
245snoc_bg_fill _row_ops _bg 0 _row
307snoc_bg_fill _row_ops _bg 0 _row
246 = return ()
308 = return ()
247snoc_bg_fill mrow_ops (Background c back_attr) fill_length row
309snoc_bg_fill mrow_ops (Background c back_attr) fill_length row
@@ -264,10 +326,9 @@ snoc_bg_fill mrow_ops (Background c back_attr) fill_length row
264 $ zip [0 .. fromEnum (fill_length - 1)] (cycle c_bytes)
326 $ zip [0 .. fromEnum (fill_length - 1)] (cycle c_bytes)
265 snoc_op mrow_ops row $ TextSpan fill_length fill_length (UTF8.fromRep utf8_bs)
327 snoc_op mrow_ops row $ TextSpan fill_length fill_length (UTF8.fromRep utf8_bs)
267−snoc_op :: MRowOps s -> Word -> SpanOp -> ST s ()
329+snoc_op :: MRowOps s -> Int -> SpanOp -> ST s ()
268snoc_op !mrow_ops !row !op = do
330snoc_op !mrow_ops !row !op = do
269− ops <- readSTArray mrow_ops row
331+ ops <- Vector.read mrow_ops row
270− let ops' = ops ++ [op]
332+ let ops' = Vector.snoc ops op
271− writeSTArray mrow_ops row ops'
333+ Vector.write mrow_ops row ops'