@@ -45,7 +45,10 @@ import Data.Word
45infixr 5 <|>
45infixr 5 <|>
46infixr 4 <->
46infixr 4 <->
48+-- | We pair each character with it's display length. This way we only compute the length once per
50+-- * Though currently the width of some strings is still compute multiple times.
48−type StringSeq = Seq.Seq Char
51+type DisplayString = Seq.Seq (Char, Word)
50-- | An image in VTY defines:
53-- | An image in VTY defines:
@@ -69,8 +72,7 @@ data Image =
70 { attr :: !Attr
73 { attr :: !Attr
71 -- All character data is stored as Char sequences with the ISO-10646 encoding.
74 -- All character data is stored as Char sequences with the ISO-10646 encoding.
72− , text :: StringSeq
75+ , text :: DisplayString
74 , output_width :: !Word -- >= 0
76 , output_width :: !Word -- >= 0
75 , char_width :: !Word -- >= 1
77 , char_width :: !Word -- >= 1
@@ -113,7 +115,7 @@ data Image =
114instance Show Image where
116instance Show Image where
115 show ( HorizText { output_width = ow, text = txt } )
117 show ( HorizText { output_width = ow, text = txt } )
116− = "HorizText [" ++ show ow ++ "] (" ++ show txt ++ ")"
118+ = "HorizText [" ++ show ow ++ "] (" ++ show (fmap fst txt) ++ ")"
117 show ( BGFill { output_width = c, output_height = r } )
119 show ( BGFill { output_width = c, output_height = r } )
118 = "BGFill (" ++ show c ++ "," ++ show r ++ ")"
120 = "BGFill (" ++ show c ++ "," ++ show r ++ ")"
119 show ( HorizJoin { part_left = l, part_right = r, output_width = c } )
121 show ( HorizJoin { part_left = l, part_right = r, output_width = c } )
@@ -128,14 +130,13 @@ instance Show Image where
128 = "ImagePad " ++ show size ++ " ( " ++ show i ++ " )"
130 = "ImagePad " ++ show size ++ " ( " ++ show i ++ " )"
129 show ( EmptyImage ) = "EmptyImage"
131 show ( EmptyImage ) = "EmptyImage"
131−-- | Currently append in the Monoid instance is equivalent to <->. Future versions will just stack
133+-- | Currently append in the Monoid instance is equivalent to <->.
133instance Monoid Image where
134instance Monoid Image where
134 mempty = empty_image
135 mempty = empty_image
135 mappend = (<->)
136 mappend = (<->)
137-- A horizontal text image of 0 characters in width simplifies to the EmptyImage
138-- A horizontal text image of 0 characters in width simplifies to the EmptyImage
138−horiz_text :: Attr -> StringSeq -> Word -> Image
139+horiz_text :: Attr -> DisplayString -> Word -> Image
139horiz_text a txt ow
140horiz_text a txt ow
140 | ow == 0 = EmptyImage
141 | ow == 0 = EmptyImage
141 | otherwise = HorizText a txt ow (toEnum $ Seq.length txt)
142 | otherwise = HorizText a txt ow (toEnum $ Seq.length txt)
@@ -288,7 +289,9 @@ vert_cat = foldr (<->) EmptyImage
288-- | an image of a single character. This is a standard Haskell 31-bit character assumed to be in
289-- | an image of a single character. This is a standard Haskell 31-bit character assumed to be in
289-- the ISO-10646 encoding.
290-- the ISO-10646 encoding.
290char :: Attr -> Char -> Image
291char :: Attr -> Char -> Image
293+ let display_width = safe_wcwidth c
291−char !a !c = HorizText a (Seq.singleton c) (safe_wcwidth c) 1
294+ in HorizText a (Seq.singleton (c, display_width)) display_width 1
293-- | A string of characters layed out on a single row with the same display attribute. The string is
296-- | A string of characters layed out on a single row with the same display attribute. The string is
294-- assumed to be a sequence of ISO-10646 characters.
297-- assumed to be a sequence of ISO-10646 characters.
@@ -300,7 +303,9 @@ char !a !c = HorizText a (Seq.singleton c) (safe_wcwidth c) 1
300-- directly to iso_10646_string or string.
303-- directly to iso_10646_string or string.
302iso_10646_string :: Attr -> String -> Image
305iso_10646_string :: Attr -> String -> Image
303−iso_10646_string !a !str = horiz_text a (Seq.fromList str) (safe_wcswidth str)
306+iso_10646_string !a !str =
307+ let display_text = Seq.fromList $ map (\c -> (c, safe_wcwidth c)) str
308+ in horiz_text a display_text (safe_wcswidth str)
305-- | Alias for iso_10646_string. Since the usual case is that a literal string like "foo" is
310-- | Alias for iso_10646_string. Since the usual case is that a literal string like "foo" is
306-- represented internally as a list of ISO 10646 31 bit characters.
311-- represented internally as a list of ISO 10646 31 bit characters.
@@ -316,14 +321,16 @@ string = iso_10646_string
316utf8_string :: Attr -> [Word8] -> Image
321utf8_string :: Attr -> [Word8] -> Image
317utf8_string !a !str = string a ( decode str )
322utf8_string !a !str = string a ( decode str )
324+-- | Returns the display width of a character. Assumes all characters with unknown widths are 0 width
319safe_wcwidth :: Char -> Word
325safe_wcwidth :: Char -> Word
320safe_wcwidth c = case wcwidth c of
326safe_wcwidth c = case wcwidth c of
321− i | i < 0 -> 0 -- error "negative wcwidth"
327+ i | i < 0 -> 0
322 | otherwise -> toEnum i
328 | otherwise -> toEnum i
330+-- | Returns the display width of a string. Assumes all characters with unknown widths are 0 width
324safe_wcswidth :: String -> Word
331safe_wcswidth :: String -> Word
325safe_wcswidth str = case wcswidth str of
332safe_wcswidth str = case wcswidth str of
326− i | i < 0 -> 0 -- error "negative wcswidth"
333+ i | i < 0 -> 0
327 | otherwise -> toEnum i
334 | otherwise -> toEnum i
329-- | Renders a UTF-8 encoded bytestring.
336-- | Renders a UTF-8 encoded bytestring.