Repository: astanin/python-tabulate
Pull request #183 (2022-06-23 07:20:38)

Implements `{heavy,mixed}_{grid,outline}`
Introduces, as suggested in #155, 4 new styles:

* `heavy_grid`:

    ```
    ┏━━━━━━━━┳━━━━━━━┓
    ┃ item   ┃   qty ┃
    ┣━━━━━━━━╋━━━━━━━┫
    ┃ spam   ┃    42 ┃
    ┣━━━━━━━━╋━━━━━━━┫
    ┃ eggs   ┃   451 ┃
    ┣━━━━━━━━╋━━━━━━━┫
    ┃ bacon  ┃     0 ┃
    ┗━━━━━━━━┻━━━━━━━┛
    ```

* `mixed_grid`:

    ```
    ┍━━━━━━━━┯━━━━━━━┑
    │ item   │   qty │
    ┝━━━━━━━━┿━━━━━━━┥
    │ spam   │    42 │
    ├────────┼───────┤
    │ eggs   │   451 │
    ├────────┼───────┤
    │ bacon  │     0 │
    ┕━━━━━━━━┷━━━━━━━┙
    ```

* `heavy_outline`:

    ```
    ┏━━━━━━━━┳━━━━━━━┓
    ┃ item   ┃   qty ┃
    ┣━━━━━━━━╋━━━━━━━┫
    ┃ spam   ┃    42 ┃
    ┃ eggs   ┃   451 ┃
    ┃ bacon  ┃     0 ┃
    ┗━━━━━━━━┻━━━━━━━┛
    ```

* `mixed_outline`:

    ```
    ┍━━━━━━━━┯━━━━━━━┑
    │ item   │   qty │
    ┝━━━━━━━━┿━━━━━━━┥
    │ spam   │    42 │
    │ eggs   │   451 │
    │ bacon  │     0 │
    ┕━━━━━━━━┷━━━━━━━┙
    ```

Notes:
* I choose `heavy_` as a prefix instead of `bold_` or `thick_` as [*heavy* is the qualifier under which the character set is labelled in unicode](https://unicode-table.com/en/2503/) specification.
* The `mixed_` family use both thin (light) and thick (heavy) lines the same way `fancy_` does with single and double lines.

Closes #155.



Patch:
diff --git a/README.md b/README.md
index ba65384..1475d75 100644
--- a/README.md
+++ b/README.md
@@ -157,11 +157,15 @@ Supported table formats are:
 -   "grid"
 -   "simple\_grid"
 -   "rounded\_grid"
+-   "heavy\_grid"
+-   "mixed\_grid"
 -   "double\_grid"
 -   "fancy\_grid"
 -   "outline"
 -   "simple\_outline"
 -   "rounded\_outline"
+-   "heavy\_outline"
+-   "mixed\_outline"
 -   "double\_outline"
 -   "fancy\_outline"
 -   "pipe"
@@ -263,6 +267,32 @@ corresponds to the `pipe` format without alignment colons:
     │ bacon  │     0 │
     ╰────────┴───────╯
 
+`heavy_grid` draws a grid using bold (thick) single-line box-drawing characters:
+
+    >>> print(tabulate(table, headers, tablefmt="heavy_grid"))
+    ┏━━━━━━━━┳━━━━━━━┓
+    ┃ item   ┃   qty ┃
+    ┣━━━━━━━━╋━━━━━━━┫
+    ┃ spam   ┃    42 ┃
+    ┣━━━━━━━━╋━━━━━━━┫
+    ┃ eggs   ┃   451 ┃
+    ┣━━━━━━━━╋━━━━━━━┫
+    ┃ bacon  ┃     0 ┃
+    ┗━━━━━━━━┻━━━━━━━┛
+
+`mixed_grid` draws a grid using a mix of light (thin) and heavy (thick) lines box-drawing characters:
+
+    >>> print(tabulate(table, headers, tablefmt="mixed_grid"))
+    ┍━━━━━━━━┯━━━━━━━┑
+    │ item   │   qty │
+    ┝━━━━━━━━┿━━━━━━━┥
+    │ spam   │    42 │
+    ├────────┼───────┤
+    │ eggs   │   451 │
+    ├────────┼───────┤
+    │ bacon  │     0 │
+    ┕━━━━━━━━┷━━━━━━━┙
+
 `double_grid` draws a grid using double-line box-drawing characters:
 
     >>> print(tabulate(table, headers, tablefmt="double_grid"))
@@ -325,6 +355,28 @@ corresponds to the `pipe` format without alignment colons:
     │ bacon  │     0 │
     ╰────────┴───────╯
 
+`heavy_outline` is the same as the `heavy_grid` format but doesn't draw lines between rows:
+
+    >>> print(tabulate(table, headers, tablefmt="heavy_outline"))
+    ┏━━━━━━━━┳━━━━━━━┓
+    ┃ item   ┃   qty ┃
+    ┣━━━━━━━━╋━━━━━━━┫
+    ┃ spam   ┃    42 ┃
+    ┃ eggs   ┃   451 ┃
+    ┃ bacon  ┃     0 ┃
+    ┗━━━━━━━━┻━━━━━━━┛
+
+`mixed_outline` is the same as the `mixed_grid` format but doesn't draw lines between rows:
+
+    >>> print(tabulate(table, headers, tablefmt="mixed_outline"))
+    ┍━━━━━━━━┯━━━━━━━┑
+    │ item   │   qty │
+    ┝━━━━━━━━┿━━━━━━━┥
+    │ spam   │    42 │
+    │ eggs   │   451 │
+    │ bacon  │     0 │
+    ┕━━━━━━━━┷━━━━━━━┙
+
 `double_outline` is the same as the `double_grid` format but doesn't draw lines between rows:
 
     >>> print(tabulate(table, headers, tablefmt="double_outline"))
diff --git a/tabulate.py b/tabulate.py
index a611658..ff61339 100644
--- a/tabulate.py
+++ b/tabulate.py
@@ -303,6 +303,26 @@ _table_formats = {
         padding=1,
         with_header_hide=None,
     ),
+    "heavy_grid": TableFormat(
+        lineabove=Line("┏", "━", "┳", "┓"),
+        linebelowheader=Line("┣", "━", "╋", "┫"),
+        linebetweenrows=Line("┣", "━", "╋", "┫"),
+        linebelow=Line("┗", "━", "┻", "┛"),
+        headerrow=DataRow("┃", "┃", "┃"),
+        datarow=DataRow("┃", "┃", "┃"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "mixed_grid": TableFormat(
+        lineabove=Line("┍", "━", "┯", "┑"),
+        linebelowheader=Line("┝", "━", "┿", "┥"),
+        linebetweenrows=Line("├", "─", "┼", "┤"),
+        linebelow=Line("┕", "━", "┷", "┙"),
+        headerrow=DataRow("│", "│", "│"),
+        datarow=DataRow("│", "│", "│"),
+        padding=1,
+        with_header_hide=None,
+    ),
     "double_grid": TableFormat(
         lineabove=Line("╔", "═", "╦", "╗"),
         linebelowheader=Line("╠", "═", "╬", "╣"),
@@ -353,6 +373,26 @@ _table_formats = {
         padding=1,
         with_header_hide=None,
     ),
+    "heavy_outline": TableFormat(
+        lineabove=Line("┏", "━", "┳", "┓"),
+        linebelowheader=Line("┣", "━", "╋", "┫"),
+        linebetweenrows=None,
+        linebelow=Line("┗", "━", "┻", "┛"),
+        headerrow=DataRow("┃", "┃", "┃"),
+        datarow=DataRow("┃", "┃", "┃"),
+        padding=1,
+        with_header_hide=None,
+    ),
+    "mixed_outline": TableFormat(
+        lineabove=Line("┍", "━", "┯", "┑"),
+        linebelowheader=Line("┝", "━", "┿", "┥"),
+        linebetweenrows=None,
+        linebelow=Line("┕", "━", "┷", "┙"),
+        headerrow=DataRow("│", "│", "│"),
+        datarow=DataRow("│", "│", "│"),
+        padding=1,
+        with_header_hide=None,
+    ),
     "double_outline": TableFormat(
         lineabove=Line("╔", "═", "╦", "╗"),
         linebelowheader=Line("╠", "═", "╬", "╣"),
@@ -582,6 +622,8 @@ multiline_formats = {
     "grid": "grid",
     "simple_grid": "simple_grid",
     "rounded_grid": "rounded_grid",
+    "heavy_grid": "heavy_grid",
+    "mixed_grid": "mixed_grid",
     "double_grid": "double_grid",
     "fancy_grid": "fancy_grid",
     "pipe": "pipe",
@@ -1552,6 +1594,32 @@ def tabulate(
     │ eggs      │  451      │
     ╰───────────┴───────────╯
 
+    "heavy_grid" draws a grid using bold (thick) single-line box-drawing
+    characters:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                ["strings", "numbers"], "heavy_grid"))
+    ┏━━━━━━━━━━━┳━━━━━━━━━━━┓
+    ┃ strings   ┃   numbers ┃
+    ┣━━━━━━━━━━━╋━━━━━━━━━━━┫
+    ┃ spam      ┃   41.9999 ┃
+    ┣━━━━━━━━━━━╋━━━━━━━━━━━┫
+    ┃ eggs      ┃  451      ┃
+    ┗━━━━━━━━━━━┻━━━━━━━━━━━┛
+
+    "mixed_grid" draws a grid using a mix of light (thin) and heavy (thick) lines box-drawing characters:
+    characters:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                ["strings", "numbers"], "mixed_grid"))
+    ┍━━━━━━━━━━━┯━━━━━━━━━━━┑
+    │ strings   │   numbers │
+    ┝━━━━━━━━━━━┿━━━━━━━━━━━┥
+    │ spam      │   41.9999 │
+    ├───────────┼───────────┤
+    │ eggs      │  451      │
+    ┕━━━━━━━━━━━┷━━━━━━━━━━━┙
+
     "double_grid" draws a grid using double-line box-drawing
     characters:
 
@@ -1617,6 +1685,28 @@ def tabulate(
     │ eggs      │  451      │
     ╰───────────┴───────────╯
 
+    "heavy_outline" is the same as the "heavy_grid" format but doesn't draw lines between rows:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                ["strings", "numbers"], "heavy_outline"))
+    ┏━━━━━━━━━━━┳━━━━━━━━━━━┓
+    ┃ strings   ┃   numbers ┃
+    ┣━━━━━━━━━━━╋━━━━━━━━━━━┫
+    ┃ spam      ┃   41.9999 ┃
+    ┃ eggs      ┃  451      ┃
+    ┗━━━━━━━━━━━┻━━━━━━━━━━━┛
+
+    "mixed_outline" is the same as the "mixed_grid" format but doesn't draw lines between rows:
+
+    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+    ...                ["strings", "numbers"], "mixed_outline"))
+    ┍━━━━━━━━━━━┯━━━━━━━━━━━┑
+    │ strings   │   numbers │
+    ┝━━━━━━━━━━━┿━━━━━━━━━━━┥
+    │ spam      │   41.9999 │
+    │ eggs      │  451      │
+    ┕━━━━━━━━━━━┷━━━━━━━━━━━┙
+
     "double_outline" is the same as the "double_grid" format but doesn't draw lines between rows:
 
     >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],


Test patch:
diff --git a/test/test_output.py b/test/test_output.py
index 82ecfb7..cd8e016 100644
--- a/test/test_output.py
+++ b/test/test_output.py
@@ -843,6 +843,276 @@ def test_rounded_grid_multiline_with_empty_cells_headerless():
     assert_equal(expected, result)
 
 
+def test_heavy_grid():
+    "Output: heavy_grid with headers"
+    expected = "\n".join(
+        [
+            "┏━━━━━━━━━━━┳━━━━━━━━━━━┓",
+            "┃ strings   ┃   numbers ┃",
+            "┣━━━━━━━━━━━╋━━━━━━━━━━━┫",
+            "┃ spam      ┃   41.9999 ┃",
+            "┣━━━━━━━━━━━╋━━━━━━━━━━━┫",
+            "┃ eggs      ┃  451      ┃",
+            "┗━━━━━━━━━━━┻━━━━━━━━━━━┛",
+        ]
+    )
+    result = tabulate(_test_table, _test_table_headers, tablefmt="heavy_grid")
+    assert_equal(expected, result)
+
+
+def test_heavy_grid_wide_characters():
+    "Output: heavy_grid with wide characters in headers"
+    try:
+        import wcwidth  # noqa
+    except ImportError:
+        skip("test_heavy_grid_wide_characters is skipped")
+    headers = list(_test_table_headers)
+    headers[1] = "配列"
+    expected = "\n".join(
+        [
+            "┏━━━━━━━━━━━┳━━━━━━━━━━━┓",
+            "┃ strings   ┃      配列 ┃",
+            "┣━━━━━━━━━━━╋━━━━━━━━━━━┫",
+            "┃ spam      ┃   41.9999 ┃",
+            "┣━━━━━━━━━━━╋━━━━━━━━━━━┫",
+            "┃ eggs      ┃  451      ┃",
+            "┗━━━━━━━━━━━┻━━━━━━━━━━━┛",
+        ]
+    )
+    result = tabulate(_test_table, headers, tablefmt="heavy_grid")
+    assert_equal(expected, result)
+
+
+def test_heavy_grid_headerless():
+    "Output: heavy_grid without headers"
+    expected = "\n".join(
+        [
+            "┏━━━━━━┳━━━━━━━━━━┓",
+            "┃ spam ┃  41.9999 ┃",
+            "┣━━━━━━╋━━━━━━━━━━┫",
+            "┃ eggs ┃ 451      ┃",
+            "┗━━━━━━┻━━━━━━━━━━┛",
+        ]
+    )
+    result = tabulate(_test_table, tablefmt="heavy_grid")
+    assert_equal(expected, result)
+
+
+def test_heavy_grid_multiline_headerless():
+    "Output: heavy_grid with multiline cells without headers"
+    table = [["foo bar\nbaz\nbau", "hello"], ["", "multiline\nworld"]]
+    expected = "\n".join(
+        [
+            "┏━━━━━━━━━┳━━━━━━━━━━━┓",
+            "┃ foo bar ┃   hello   ┃",
+            "┃   baz   ┃           ┃",
+            "┃   bau   ┃           ┃",
+            "┣━━━━━━━━━╋━━━━━━━━━━━┫",
+            "┃         ┃ multiline ┃",
+            "┃         ┃   world   ┃",
+            "┗━━━━━━━━━┻━━━━━━━━━━━┛",
+        ]
+    )
+    result = tabulate(table, stralign="center", tablefmt="heavy_grid")
+    assert_equal(expected, result)
+
+
+def test_heavy_grid_multiline():
+    "Output: heavy_grid with multiline cells with headers"
+    table = [[2, "foo\nbar"]]
+    headers = ("more\nspam \x1b[31meggs\x1b[0m", "more spam\n& eggs")
+    expected = "\n".join(
+        [
+            "┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓",
+            "┃        more ┃ more spam   ┃",
+            "┃   spam \x1b[31meggs\x1b[0m ┃ & eggs      ┃",
+            "┣━━━━━━━━━━━━━╋━━━━━━━━━━━━━┫",
+            "┃           2 ┃ foo         ┃",
+            "┃             ┃ bar         ┃",
+            "┗━━━━━━━━━━━━━┻━━━━━━━━━━━━━┛",
+        ]
+    )
+    result = tabulate(table, headers, tablefmt="heavy_grid")
+    assert_equal(expected, result)
+
+
+def test_heavy_grid_multiline_with_empty_cells():
+    "Output: heavy_grid with multiline cells and empty cells with headers"
+    table = [
+        ["hdr", "data", "fold"],
+        ["1", "", ""],
+        ["2", "very long data", "fold\nthis"],
+    ]
+    expected = "\n".join(
+        [
+            "┏━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━┓",
+            "┃   hdr ┃ data           ┃ fold   ┃",
+            "┣━━━━━━━╋━━━━━━━━━━━━━━━━╋━━━━━━━━┫",
+            "┃     1 ┃                ┃        ┃",
+            "┣━━━━━━━╋━━━━━━━━━━━━━━━━╋━━━━━━━━┫",
+            "┃     2 ┃ very long data ┃ fold   ┃",
+            "┃       ┃                ┃ this   ┃",
+            "┗━━━━━━━┻━━━━━━━━━━━━━━━━┻━━━━━━━━┛",
+        ]
+    )
+    result = tabulate(table, headers="firstrow", tablefmt="heavy_grid")
+    assert_equal(expected, result)
+
+
+def test_heavy_grid_multiline_with_empty_cells_headerless():
+    "Output: heavy_grid with multiline cells and empty cells without headers"
+    table = [["0", "", ""], ["1", "", ""], ["2", "very long data", "fold\nthis"]]
+    expected = "\n".join(
+        [
+            "┏━━━┳━━━━━━━━━━━━━━━━┳━━━━━━┓",
+            "┃ 0 ┃                ┃      ┃",
+            "┣━━━╋━━━━━━━━━━━━━━━━╋━━━━━━┫",
+            "┃ 1 ┃                ┃      ┃",
+            "┣━━━╋━━━━━━━━━━━━━━━━╋━━━━━━┫",
+            "┃ 2 ┃ very long data ┃ fold ┃",
+            "┃   ┃                ┃ this ┃",
+            "┗━━━┻━━━━━━━━━━━━━━━━┻━━━━━━┛",
+        ]
+    )
+    result = tabulate(table, tablefmt="heavy_grid")
+    assert_equal(expected, result)
+
+
+def test_mixed_grid():
+    "Output: mixed_grid with headers"
+    expected = "\n".join(
+        [
+            "┍━━━━━━━━━━━┯━━━━━━━━━━━┑",
+            "│ strings   │   numbers │",
+            "┝━━━━━━━━━━━┿━━━━━━━━━━━┥",
+            "│ spam      │   41.9999 │",
+            "├───────────┼───────────┤",
+            "│ eggs      │  451      │",
+            "┕━━━━━━━━━━━┷━━━━━━━━━━━┙",
+        ]
+    )
+    result = tabulate(_test_table, _test_table_headers, tablefmt="mixed_grid")
+    assert_equal(expected, result)
+
+
+def test_mixed_grid_wide_characters():
+    "Output: mixed_grid with wide characters in headers"
+    try:
+        import wcwidth  # noqa
+    except ImportError:
+        skip("test_mixed_grid_wide_characters is skipped")
+    headers = list(_test_table_headers)
+    headers[1] = "配列"
+    expected = "\n".join(
+        [
+            "┍━━━━━━━━━━━┯━━━━━━━━━━━┑",
+            "│ strings   │      配列 │",
+            "┝━━━━━━━━━━━┿━━━━━━━━━━━┥",
+            "│ spam      │   41.9999 │",
+            "├───────────┼───────────┤",
+            "│ eggs      │  451      │",
+            "┕━━━━━━━━━━━┷━━━━━━━━━━━┙",
+        ]
+    )
+    result = tabulate(_test_table, headers, tablefmt="mixed_grid")
+    assert_equal(expected, result)
+
+
+def test_mixed_grid_headerless():
+    "Output: mixed_grid without headers"
+    expected = "\n".join(
+        [
+            "┍━━━━━━┯━━━━━━━━━━┑",
+            "│ spam │  41.9999 │",
+            "├──────┼──────────┤",
+            "│ eggs │ 451      │",
+            "┕━━━━━━┷━━━━━━━━━━┙",
+        ]
+    )
+    result = tabulate(_test_table, tablefmt="mixed_grid")
+    assert_equal(expected, result)
+
+
+def test_mixed_grid_multiline_headerless():
+    "Output: mixed_grid with multiline cells without headers"
+    table = [["foo bar\nbaz\nbau", "hello"], ["", "multiline\nworld"]]
+    expected = "\n".join(
+        [
+            "┍━━━━━━━━━┯━━━━━━━━━━━┑",
+            "│ foo bar │   hello   │",
+            "│   baz   │           │",
+            "│   bau   │           │",
+            "├─────────┼───────────┤",
+            "│         │ multiline │",
+            "│         │   world   │",
+            "┕━━━━━━━━━┷━━━━━━━━━━━┙",
+        ]
+    )
+    result = tabulate(table, stralign="center", tablefmt="mixed_grid")
+    assert_equal(expected, result)
+
+
+def test_mixed_grid_multiline():
+    "Output: mixed_grid with multiline cells with headers"
+    table = [[2, "foo\nbar"]]
+    headers = ("more\nspam \x1b[31meggs\x1b[0m", "more spam\n& eggs")
+    expected = "\n".join(
+        [
+            "┍━━━━━━━━━━━━━┯━━━━━━━━━━━━━┑",
+            "│        more │ more spam   │",
+            "│   spam \x1b[31meggs\x1b[0m │ & eggs      │",
+            "┝━━━━━━━━━━━━━┿━━━━━━━━━━━━━┥",
+            "│           2 │ foo         │",
+            "│             │ bar         │",
+            "┕━━━━━━━━━━━━━┷━━━━━━━━━━━━━┙",
+        ]
+    )
+    result = tabulate(table, headers, tablefmt="mixed_grid")
+    assert_equal(expected, result)
+
+
+def test_mixed_grid_multiline_with_empty_cells():
+    "Output: mixed_grid with multiline cells and empty cells with headers"
+    table = [
+        ["hdr", "data", "fold"],
+        ["1", "", ""],
+        ["2", "very long data", "fold\nthis"],
+    ]
+    expected = "\n".join(
+        [
+            "┍━━━━━━━┯━━━━━━━━━━━━━━━━┯━━━━━━━━┑",
+            "│   hdr │ data           │ fold   │",
+            "┝━━━━━━━┿━━━━━━━━━━━━━━━━┿━━━━━━━━┥",
+            "│     1 │                │        │",
+            "├───────┼────────────────┼────────┤",
+            "│     2 │ very long data │ fold   │",
+            "│       │                │ this   │",
+            "┕━━━━━━━┷━━━━━━━━━━━━━━━━┷━━━━━━━━┙",
+        ]
+    )
+    result = tabulate(table, headers="firstrow", tablefmt="mixed_grid")
+    assert_equal(expected, result)
+
+
+def test_mixed_grid_multiline_with_empty_cells_headerless():
+    "Output: mixed_grid with multiline cells and empty cells without headers"
+    table = [["0", "", ""], ["1", "", ""], ["2", "very long data", "fold\nthis"]]
+    expected = "\n".join(
+        [
+            "┍━━━┯━━━━━━━━━━━━━━━━┯━━━━━━┑",
+            "│ 0 │                │      │",
+            "├───┼────────────────┼──────┤",
+            "│ 1 │                │      │",
+            "├───┼────────────────┼──────┤",
+            "│ 2 │ very long data │ fold │",
+            "│   │                │ this │",
+            "┕━━━┷━━━━━━━━━━━━━━━━┷━━━━━━┙",
+        ]
+    )
+    result = tabulate(table, tablefmt="mixed_grid")
+    assert_equal(expected, result)
+
+
 def test_double_grid():
     "Output: double_grid with headers"
     expected = "\n".join(
@@ -1300,6 +1570,110 @@ def test_rounded_outline_headerless():
     assert_equal(expected, result)
 
 
+def test_heavy_outline():
+    "Output: heavy_outline with headers"
+    expected = "\n".join(
+        [
+            "┏━━━━━━━━━━━┳━━━━━━━━━━━┓",
+            "┃ strings   ┃   numbers ┃",
+            "┣━━━━━━━━━━━╋━━━━━━━━━━━┫",
+            "┃ spam      ┃   41.9999 ┃",
+            "┃ eggs      ┃  451      ┃",
+            "┗━━━━━━━━━━━┻━━━━━━━━━━━┛",
+        ]
+    )
+    result = tabulate(_test_table, _test_table_headers, tablefmt="heavy_outline")
+    assert_equal(expected, result)
+
+
+def test_heavy_outline_wide_characters():
+    "Output: heavy_outline with wide characters in headers"
+    try:
+        import wcwidth  # noqa
+    except ImportError:
+        skip("test_heavy_outline_wide_characters is skipped")
+    headers = list(_test_table_headers)
+    headers[1] = "配列"
+    expected = "\n".join(
+        [
+            "┏━━━━━━━━━━━┳━━━━━━━━━━━┓",
+            "┃ strings   ┃      配列 ┃",
+            "┣━━━━━━━━━━━╋━━━━━━━━━━━┫",
+            "┃ spam      ┃   41.9999 ┃",
+            "┃ eggs      ┃  451      ┃",
+            "┗━━━━━━━━━━━┻━━━━━━━━━━━┛",
+        ]
+    )
+    result = tabulate(_test_table, headers, tablefmt="heavy_outline")
+    assert_equal(expected, result)
+
+
+def test_heavy_outline_headerless():
+    "Output: heavy_outline without headers"
+    expected = "\n".join(
+        [
+            "┏━━━━━━┳━━━━━━━━━━┓",
+            "┃ spam ┃  41.9999 ┃",
+            "┃ eggs ┃ 451      ┃",
+            "┗━━━━━━┻━━━━━━━━━━┛",
+        ]
+    )
+    result = tabulate(_test_table, tablefmt="heavy_outline")
+    assert_equal(expected, result)
+
+
+def test_mixed_outline():
+    "Output: mixed_outline with headers"
+    expected = "\n".join(
+        [
+            "┍━━━━━━━━━━━┯━━━━━━━━━━━┑",
+            "│ strings   │   numbers │",
+            "┝━━━━━━━━━━━┿━━━━━━━━━━━┥",
+            "│ spam      │   41.9999 │",
+            "│ eggs      │  451      │",
+            "┕━━━━━━━━━━━┷━━━━━━━━━━━┙",
+        ]
+    )
+    result = tabulate(_test_table, _test_table_headers, tablefmt="mixed_outline")
+    assert_equal(expected, result)
+
+
+def test_mixed_outline_wide_characters():
+    "Output: mixed_outline with wide characters in headers"
+    try:
+        import wcwidth  # noqa
+    except ImportError:
+        skip("test_mixed_outline_wide_characters is skipped")
+    headers = list(_test_table_headers)
+    headers[1] = "配列"
+    expected = "\n".join(
+        [
+            "┍━━━━━━━━━━━┯━━━━━━━━━━━┑",
+            "│ strings   │      配列 │",
+            "┝━━━━━━━━━━━┿━━━━━━━━━━━┥",
+            "│ spam      │   41.9999 │",
+            "│ eggs      │  451      │",
+            "┕━━━━━━━━━━━┷━━━━━━━━━━━┙",
+        ]
+    )
+    result = tabulate(_test_table, headers, tablefmt="mixed_outline")
+    assert_equal(expected, result)
+
+
+def test_mixed_outline_headerless():
+    "Output: mixed_outline without headers"
+    expected = "\n".join(
+        [
+            "┍━━━━━━┯━━━━━━━━━━┑",
+            "│ spam │  41.9999 │",
+            "│ eggs │ 451      │",
+            "┕━━━━━━┷━━━━━━━━━━┙",
+        ]
+    )
+    result = tabulate(_test_table, tablefmt="mixed_outline")
+    assert_equal(expected, result)
+
+
 def test_double_outline():
     "Output: double_outline with headers"
     expected = "\n".join(