    -- | Text.Tabular.AsciiArt from tabular-0.2.2.7, modified to treat
    -- wide characters as double width.

    module Text.Tabular.AsciiWide where

    import Data.List (intersperse, transpose)
    import Text.Tabular
    import Hledger.Utils.String

    -- | for simplicity, we assume that each cell is rendered
    --   on a single line
    render :: Bool -- ^ pretty tables
           -> (rh -> String)
           -> (ch -> String)
           -> (a -> String)
           -> Table rh ch a
           -> String
    render pretty fr fc f (Table rh ch cells) =
      unlines $ [ bar SingleLine   -- +--------------------------------------+
                , renderColumns pretty sizes ch2
                , bar DoubleLine   -- +======================================+
                ] ++
                (renderRs $ fmap renderR $ zipHeader [] cells $ fmap fr rh) ++
                [ bar SingleLine ] -- +--------------------------------------+
     where
      bar = concat . renderHLine pretty sizes ch2
      -- ch2 and cell2 include the row and column labels
      ch2 = Group DoubleLine [Header "", fmap fc ch]
      cells2 = headerContents ch2
             : zipWith (\h cs -> h : map f cs) rhStrings cells
      --
      renderR (cs,h) = renderColumns pretty sizes $ Group DoubleLine
                        [ Header h
                        , fmap fst $ zipHeader "" (map f cs) ch]
      rhStrings = map fr $ headerContents rh
      -- maximum width for each column
      sizes   = map (maximum . map strWidth) . transpose $ cells2
      renderRs (Header s)   = [s]
      renderRs (Group p hs) = concat . intersperse sep . map renderRs $ hs
        where sep = renderHLine pretty sizes ch2 p

    verticalBar :: Bool -> Char
    verticalBar pretty = if pretty then '│' else '|'

    leftBar :: Bool -> String
    leftBar pretty = verticalBar pretty : " "

    rightBar :: Bool -> String
    rightBar pretty = " " ++ [verticalBar pretty]

    midBar :: Bool -> String
    midBar pretty = " " ++ verticalBar pretty : " "

    doubleMidBar :: Bool -> String
    doubleMidBar pretty = if pretty then " ║ " else " || "

    horizontalBar :: Bool -> Char
    horizontalBar pretty = if pretty then '─' else '-'

    doubleHorizontalBar :: Bool -> Char
    doubleHorizontalBar pretty = if pretty then '═' else '='

    -- | We stop rendering on the shortest list!
    renderColumns :: Bool -- ^ pretty
                  -> [Int] -- ^ max width for each column
                  -> Header String
                  -> String
    renderColumns pretty is h = leftBar pretty ++ coreLine ++ rightBar pretty
     where
      coreLine = concatMap helper $ flattenHeader $ zipHeader 0 is h
      helper = either hsep (uncurry padLeftWide)
      hsep :: Properties -> String
      hsep NoLine     = "  "
      hsep SingleLine = midBar pretty
      hsep DoubleLine = doubleMidBar pretty

    renderHLine :: Bool -- ^ pretty
                -> [Int] -- ^ width specifications
                -> Header String
                -> Properties
                -> [String]
    renderHLine _ _ _ NoLine = []
    renderHLine pretty w h SingleLine = [renderHLine' pretty SingleLine w (horizontalBar pretty) h]
    renderHLine pretty w h DoubleLine = [renderHLine' pretty DoubleLine w (doubleHorizontalBar pretty) h]

    doubleCross :: Bool -> String
    doubleCross pretty = if pretty then "╬" else "++"

    doubleVerticalCross :: Bool -> String
    doubleVerticalCross pretty = if pretty then "╫" else "++"

    cross :: Bool -> Char
    cross pretty = if pretty then '┼' else '+'

    renderHLine' :: Bool -> Properties -> [Int] -> Char -> Header String -> String
    renderHLine' pretty prop is sep h = [ cross pretty, sep ] ++ coreLine ++ [sep, cross pretty]
     where
      coreLine        = concatMap helper $ flattenHeader $ zipHeader 0 is h
      helper          = either vsep dashes
      dashes (i,_)    = replicate i sep
      vsep NoLine     = replicate 2 sep  -- match the double space sep in renderColumns
      vsep SingleLine = sep : cross pretty : [sep]
      vsep DoubleLine = sep : cross' ++ [sep]
      cross' = case prop of
         DoubleLine -> doubleCross pretty
         _ -> doubleVerticalCross pretty

    -- padLeft :: Int -> String -> String
    -- padLeft l s = padding ++ s
    --  where padding = replicate (l - length s) ' '