diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2024, Mike Ledger
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Mike Ledger nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/char-boxdrawing.cabal b/char-boxdrawing.cabal
new file mode 100644
--- /dev/null
+++ b/char-boxdrawing.cabal
@@ -0,0 +1,20 @@
+cabal-version: 2.0
+name:          char-boxdrawing
+version:       1
+synopsis:      Combinators for unicode or ASCII box drawing characters
+description:
+  Draw and intersect border characters for text-based drawings
+
+license:       BSD3
+license-file:  LICENSE
+author:        Mike Ledger
+maintainer:    mike@quasimal.com
+copyright:     2024
+category:      Text
+build-type:    Simple
+
+library
+  exposed-modules:  Data.Char.BoxDrawing
+  build-depends:    base >=4.17 && <5
+  hs-source-dirs:   src
+  default-language: Haskell2010
diff --git a/src/Data/Char/BoxDrawing.hs b/src/Data/Char/BoxDrawing.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Char/BoxDrawing.hs
@@ -0,0 +1,772 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+
+module Data.Char.BoxDrawing (
+  -- * Box border type
+  Drawing,
+  render,
+  lookup,
+  read,
+  overlay,
+
+  -- ** Box drawing styles
+  BoxDrawingStyle,
+  ascii,
+  unicode,
+
+  -- * Drawings
+
+  -- ** Base primitives
+  -- $primitives
+  up,
+  down,
+  left,
+  right,
+
+  -- ** Combinations
+  -- $combinations
+  horizontal,
+  vertical,
+  cornerTL,
+  cornerTR,
+  cornerBR,
+  cornerBL,
+  intersectFull,
+  intersectL,
+  intersectR,
+  intersectT,
+  intersectB,
+
+  -- ** Box drawing modifiers
+  -- $modifiers
+  heavy,
+  heavyHoriz,
+  heavyVert,
+  dashed2,
+  dashed3,
+  double,
+  doubleHoriz,
+  doubleVert,
+  rounded,
+) where
+
+import Data.Bits
+import Data.Coerce
+import Data.Maybe (fromMaybe)
+import Data.Word (Word8)
+import Text.Printf (printf)
+import Prelude hiding (lookup, read)
+
+--------------------------------------------------------------------------------
+--
+
+-- $primitives
+--
+-- Use the 'Semigroup' instance to combine box 'Drawing'.
+
+up, left, right, down, cornerTL, cornerTR, cornerBR, cornerBL, horizontal, vertical, intersectL, intersectR, intersectT, intersectB, intersectFull :: Drawing
+up = bb BUp
+left = bb BLeft
+right = bb BRight
+down = bb BDown
+
+-- $combinations
+--
+-- These are pre-made combinations of 'up', 'down', 'left', and 'right'.
+
+cornerTL = bb BDown <> bb BRight
+cornerTR = bb BDown <> bb BLeft
+cornerBR = bb BUp <> bb BLeft
+cornerBL = bb BUp <> bb BRight
+horizontal = bb BLeft <> bb BRight
+vertical = bb BUp <> bb BDown
+intersectL = vertical <> right
+intersectR = vertical <> left
+intersectT = horizontal <> down
+intersectB = horizontal <> up
+intersectFull = horizontal <> vertical
+
+-- $modifiers
+--
+-- Combine these using the 'Semigroup' instance with another 'Drawing' to set
+-- its style, such as heavy, dashed, etc. - only relevant for the style
+-- 'unicode' and has no effect for the style 'ascii'.
+
+double, doubleHoriz, doubleVert, heavy, dashed2, dashed3, dashed4, rounded, heavyHoriz, heavyVert :: Drawing
+double = style1 Double
+doubleHoriz = style1 DoubleHoriz
+doubleVert = style1 DoubleVert
+heavy = style1 Heavy
+heavyHoriz = style1 HeavyHoriz
+heavyVert = style1 HeavyVert
+dashed2 = style1 Dashed2
+dashed3 = style1 Dashed3
+dashed4 = style1 Dashed4
+rounded = style1 Rounded
+
+bb :: Drawing1 -> Drawing
+bb = boxBorderBits
+
+--------------------------------------------------------------------------------
+
+-- | A border style such as ASCII or unicode. Describes how to turn a
+-- 'Drawing' into an actual character for rendering, or to lookup a character
+-- from a buffer into a 'Drawing' for combining different border characters
+data BoxDrawingStyle = BoxDrawingStyle
+  { render :: Drawing -> Char
+  -- ^ Render a 'Drawing' given the style
+  , lookup :: Char -> Maybe Drawing
+  -- ^ Lookup a character given the style
+  }
+
+--------------------------------------------------------------------------------
+-- Generated code by using TH and then copy-pasting it back with -ddump-splices
+--
+-- Hopefully, there are no bugs in this ever :)
+--
+
+-- | A basic box-drawing style that uses @|@, @+@, and @-@
+ascii :: BoxDrawingStyle
+ascii =
+  ( \(run :: Word8 -> Char)
+     (runBack :: Char -> Maybe Word8) ->
+        BoxDrawingStyle (coerce run) (coerce runBack)
+  )
+    ( \case
+        3 -> '|'
+        5 -> '+'
+        6 -> '+'
+        7 -> '+'
+        9 -> '+'
+        10 -> '+'
+        11 -> '+'
+        12 -> '-'
+        13 -> '+'
+        14 -> '+'
+        15 -> '+'
+        19 -> '|'
+        21 -> '+'
+        22 -> '+'
+        23 -> '+'
+        25 -> '+'
+        26 -> '+'
+        27 -> '+'
+        28 -> '-'
+        29 -> '+'
+        30 -> '+'
+        31 -> '+'
+        35 -> '|'
+        37 -> '+'
+        38 -> '+'
+        39 -> '+'
+        41 -> '+'
+        42 -> '+'
+        43 -> '+'
+        44 -> '-'
+        45 -> '+'
+        46 -> '+'
+        47 -> '+'
+        51 -> '|'
+        53 -> '+'
+        54 -> '+'
+        55 -> '+'
+        57 -> '+'
+        58 -> '+'
+        59 -> '+'
+        60 -> '-'
+        61 -> '+'
+        62 -> '+'
+        63 -> '+'
+        67 -> '|'
+        69 -> '+'
+        70 -> '+'
+        71 -> '+'
+        73 -> '+'
+        74 -> '+'
+        75 -> '+'
+        76 -> '-'
+        77 -> '+'
+        78 -> '+'
+        79 -> '+'
+        83 -> '|'
+        85 -> '+'
+        86 -> '+'
+        87 -> '+'
+        89 -> '+'
+        90 -> '+'
+        91 -> '+'
+        92 -> '-'
+        93 -> '+'
+        94 -> '+'
+        95 -> '+'
+        99 -> '|'
+        101 -> '+'
+        102 -> '+'
+        103 -> '+'
+        105 -> '+'
+        106 -> '+'
+        107 -> '+'
+        108 -> '-'
+        109 -> '+'
+        110 -> '+'
+        111 -> '+'
+        115 -> '|'
+        117 -> '+'
+        118 -> '+'
+        119 -> '+'
+        121 -> '+'
+        122 -> '+'
+        123 -> '+'
+        124 -> '-'
+        125 -> '+'
+        126 -> '+'
+        127 -> '+'
+        131 -> '|'
+        133 -> '+'
+        134 -> '+'
+        135 -> '+'
+        137 -> '+'
+        138 -> '+'
+        139 -> '+'
+        140 -> '-'
+        141 -> '+'
+        142 -> '+'
+        143 -> '+'
+        147 -> '|'
+        149 -> '+'
+        150 -> '+'
+        151 -> '+'
+        153 -> '+'
+        154 -> '+'
+        155 -> '+'
+        156 -> '-'
+        157 -> '+'
+        158 -> '+'
+        159 -> '+'
+        163 -> '|'
+        165 -> '+'
+        166 -> '+'
+        167 -> '+'
+        169 -> '+'
+        170 -> '+'
+        171 -> '+'
+        172 -> '-'
+        173 -> '+'
+        174 -> '+'
+        175 -> '+'
+        179 -> '|'
+        181 -> '+'
+        182 -> '+'
+        183 -> '+'
+        185 -> '+'
+        186 -> '+'
+        187 -> '+'
+        188 -> '-'
+        189 -> '+'
+        190 -> '+'
+        191 -> '+'
+        195 -> '|'
+        197 -> '+'
+        198 -> '+'
+        199 -> '+'
+        201 -> '+'
+        202 -> '+'
+        203 -> '+'
+        204 -> '-'
+        205 -> '+'
+        206 -> '+'
+        207 -> '+'
+        211 -> '|'
+        213 -> '+'
+        214 -> '+'
+        215 -> '+'
+        217 -> '+'
+        218 -> '+'
+        219 -> '+'
+        220 -> '-'
+        221 -> '+'
+        222 -> '+'
+        223 -> '+'
+        227 -> '|'
+        229 -> '+'
+        230 -> '+'
+        231 -> '+'
+        233 -> '+'
+        234 -> '+'
+        235 -> '+'
+        236 -> '-'
+        237 -> '+'
+        238 -> '+'
+        239 -> '+'
+        243 -> '|'
+        245 -> '+'
+        246 -> '+'
+        247 -> '+'
+        249 -> '+'
+        250 -> '+'
+        251 -> '+'
+        252 -> '-'
+        253 -> '+'
+        254 -> '+'
+        255 -> '+'
+        _ -> ' '
+    )
+    ( \case
+        '+' -> Just 255
+        '-' -> Just 252
+        '|' -> Just 243
+        _ -> Nothing
+    )
+
+-- | An advanced box-drawing style that supports all the drawing primitives
+-- provided in this module
+unicode :: BoxDrawingStyle
+unicode =
+  ( \(run :: Word8 -> Char)
+     (runBack :: Char -> Maybe Word8) ->
+        BoxDrawingStyle (coerce run) (coerce runBack)
+  )
+    ( \case
+        1 -> '\9589'
+        2 -> '\9591'
+        3 -> '\9474'
+        4 -> '\9588'
+        5 -> '\9496'
+        6 -> '\9488'
+        7 -> '\9508'
+        8 -> '\9590'
+        9 -> '\9492'
+        10 -> '\9484'
+        11 -> '\9500'
+        12 -> '\9472'
+        13 -> '\9524'
+        14 -> '\9516'
+        15 -> '\9532'
+        17 -> '\9589'
+        18 -> '\9591'
+        19 -> '\9474'
+        20 -> '\9588'
+        21 -> '\9583'
+        22 -> '\9582'
+        23 -> '\9508'
+        24 -> '\9590'
+        25 -> '\9584'
+        26 -> '\9581'
+        27 -> '\9500'
+        28 -> '\9472'
+        29 -> '\9524'
+        30 -> '\9516'
+        31 -> '\9532'
+        33 -> '\9589'
+        34 -> '\9591'
+        35 -> '\9550'
+        36 -> '\9588'
+        37 -> '\9496'
+        38 -> '\9488'
+        39 -> '\9508'
+        40 -> '\9590'
+        41 -> '\9492'
+        42 -> '\9484'
+        43 -> '\9500'
+        44 -> '\9548'
+        45 -> '\9524'
+        46 -> '\9516'
+        47 -> '\9532'
+        49 -> '\9589'
+        50 -> '\9591'
+        51 -> '\9474'
+        52 -> '\9588'
+        53 -> '\9496'
+        54 -> '\9488'
+        55 -> '\9508'
+        56 -> '\9590'
+        57 -> '\9492'
+        58 -> '\9484'
+        59 -> '\9500'
+        60 -> '\9472'
+        61 -> '\9524'
+        62 -> '\9516'
+        63 -> '\9532'
+        65 -> '\9589'
+        66 -> '\9591'
+        67 -> '\9478'
+        68 -> '\9588'
+        69 -> '\9496'
+        70 -> '\9488'
+        71 -> '\9508'
+        72 -> '\9590'
+        73 -> '\9492'
+        74 -> '\9484'
+        75 -> '\9500'
+        76 -> '\9476'
+        77 -> '\9524'
+        78 -> '\9516'
+        79 -> '\9532'
+        81 -> '\9589'
+        82 -> '\9591'
+        83 -> '\9474'
+        84 -> '\9588'
+        85 -> '\9496'
+        86 -> '\9488'
+        87 -> '\9508'
+        88 -> '\9590'
+        89 -> '\9492'
+        90 -> '\9484'
+        91 -> '\9500'
+        92 -> '\9472'
+        93 -> '\9524'
+        94 -> '\9516'
+        95 -> '\9532'
+        97 -> '\9589'
+        98 -> '\9591'
+        99 -> '\9482'
+        100 -> '\9588'
+        101 -> '\9496'
+        102 -> '\9488'
+        103 -> '\9508'
+        104 -> '\9590'
+        105 -> '\9492'
+        106 -> '\9484'
+        107 -> '\9500'
+        108 -> '\9480'
+        109 -> '\9524'
+        110 -> '\9516'
+        111 -> '\9532'
+        113 -> '\9589'
+        114 -> '\9591'
+        115 -> '\9474'
+        116 -> '\9588'
+        117 -> '\9496'
+        118 -> '\9488'
+        119 -> '\9508'
+        120 -> '\9590'
+        121 -> '\9492'
+        122 -> '\9484'
+        123 -> '\9500'
+        124 -> '\9472'
+        125 -> '\9524'
+        126 -> '\9516'
+        127 -> '\9532'
+        129 -> '\9589'
+        130 -> '\9591'
+        131 -> '\9474'
+        132 -> '\9588'
+        133 -> '\9497'
+        134 -> '\9489'
+        135 -> '\9509'
+        136 -> '\9590'
+        137 -> '\9493'
+        138 -> '\9485'
+        139 -> '\9501'
+        140 -> '\9473'
+        141 -> '\9527'
+        142 -> '\9519'
+        143 -> '\9535'
+        145 -> '\9589'
+        146 -> '\9591'
+        147 -> '\9475'
+        148 -> '\9588'
+        149 -> '\9498'
+        150 -> '\9490'
+        151 -> '\9512'
+        152 -> '\9590'
+        153 -> '\9493'
+        154 -> '\9486'
+        155 -> '\9504'
+        156 -> '\9472'
+        157 -> '\9528'
+        158 -> '\9520'
+        159 -> '\9538'
+        161 -> '\9593'
+        162 -> '\9595'
+        163 -> '\9475'
+        164 -> '\9592'
+        165 -> '\9499'
+        166 -> '\9491'
+        167 -> '\9515'
+        168 -> '\9594'
+        169 -> '\9495'
+        170 -> '\9487'
+        171 -> '\9507'
+        172 -> '\9473'
+        173 -> '\9531'
+        174 -> '\9523'
+        175 -> '\9547'
+        177 -> '\9589'
+        178 -> '\9591'
+        179 -> '\9474'
+        180 -> '\9588'
+        181 -> '\9563'
+        182 -> '\9557'
+        183 -> '\9569'
+        184 -> '\9590'
+        185 -> '\9560'
+        186 -> '\9554'
+        187 -> '\9566'
+        188 -> '\9552'
+        189 -> '\9575'
+        190 -> '\9572'
+        191 -> '\9532'
+        193 -> '\9589'
+        194 -> '\9591'
+        195 -> '\9553'
+        196 -> '\9588'
+        197 -> '\9564'
+        198 -> '\9558'
+        199 -> '\9570'
+        200 -> '\9590'
+        201 -> '\9561'
+        202 -> '\9555'
+        203 -> '\9567'
+        204 -> '\9472'
+        205 -> '\9576'
+        206 -> '\9573'
+        207 -> '\9532'
+        209 -> '\9589'
+        210 -> '\9591'
+        211 -> '\9553'
+        212 -> '\9588'
+        213 -> '\9565'
+        214 -> '\9559'
+        215 -> '\9571'
+        216 -> '\9590'
+        217 -> '\9562'
+        218 -> '\9556'
+        219 -> '\9568'
+        220 -> '\9552'
+        221 -> '\9577'
+        222 -> '\9574'
+        223 -> '\9532'
+        225 -> '\9589'
+        226 -> '\9591'
+        227 -> '\9474'
+        228 -> '\9588'
+        229 -> '\9496'
+        230 -> '\9488'
+        231 -> '\9508'
+        232 -> '\9590'
+        233 -> '\9492'
+        234 -> '\9484'
+        235 -> '\9500'
+        236 -> '\9472'
+        237 -> '\9524'
+        238 -> '\9516'
+        239 -> '\9532'
+        241 -> '\9589'
+        242 -> '\9591'
+        243 -> '\9474'
+        244 -> '\9588'
+        245 -> '\9496'
+        246 -> '\9488'
+        247 -> '\9508'
+        248 -> '\9590'
+        249 -> '\9492'
+        250 -> '\9484'
+        251 -> '\9500'
+        252 -> '\9472'
+        253 -> '\9524'
+        254 -> '\9516'
+        255 -> '\9532'
+        _ -> ' '
+    )
+    ( \case
+        '\9472' -> Just 252
+        '\9473' -> Just 172
+        '\9474' -> Just 243
+        '\9475' -> Just 163
+        '\9476' -> Just 76
+        '\9478' -> Just 67
+        '\9480' -> Just 108
+        '\9482' -> Just 99
+        '\9484' -> Just 250
+        '\9485' -> Just 138
+        '\9486' -> Just 154
+        '\9487' -> Just 170
+        '\9488' -> Just 246
+        '\9489' -> Just 134
+        '\9490' -> Just 150
+        '\9491' -> Just 166
+        '\9492' -> Just 249
+        '\9493' -> Just 153
+        '\9495' -> Just 169
+        '\9496' -> Just 245
+        '\9497' -> Just 133
+        '\9498' -> Just 149
+        '\9499' -> Just 165
+        '\9500' -> Just 251
+        '\9501' -> Just 139
+        '\9504' -> Just 155
+        '\9507' -> Just 171
+        '\9508' -> Just 247
+        '\9509' -> Just 135
+        '\9512' -> Just 151
+        '\9515' -> Just 167
+        '\9516' -> Just 254
+        '\9519' -> Just 142
+        '\9520' -> Just 158
+        '\9523' -> Just 174
+        '\9524' -> Just 253
+        '\9527' -> Just 141
+        '\9528' -> Just 157
+        '\9531' -> Just 173
+        '\9532' -> Just 255
+        '\9535' -> Just 143
+        '\9538' -> Just 159
+        '\9547' -> Just 175
+        '\9548' -> Just 44
+        '\9550' -> Just 35
+        '\9552' -> Just 220
+        '\9553' -> Just 211
+        '\9554' -> Just 186
+        '\9555' -> Just 202
+        '\9556' -> Just 218
+        '\9557' -> Just 182
+        '\9558' -> Just 198
+        '\9559' -> Just 214
+        '\9560' -> Just 185
+        '\9561' -> Just 201
+        '\9562' -> Just 217
+        '\9563' -> Just 181
+        '\9564' -> Just 197
+        '\9565' -> Just 213
+        '\9566' -> Just 187
+        '\9567' -> Just 203
+        '\9568' -> Just 219
+        '\9569' -> Just 183
+        '\9570' -> Just 199
+        '\9571' -> Just 215
+        '\9572' -> Just 190
+        '\9573' -> Just 206
+        '\9574' -> Just 222
+        '\9575' -> Just 189
+        '\9576' -> Just 205
+        '\9577' -> Just 221
+        '\9581' -> Just 26
+        '\9582' -> Just 22
+        '\9583' -> Just 21
+        '\9584' -> Just 25
+        '\9588' -> Just 244
+        '\9589' -> Just 241
+        '\9590' -> Just 248
+        '\9591' -> Just 242
+        '\9592' -> Just 164
+        '\9593' -> Just 161
+        '\9594' -> Just 168
+        '\9595' -> Just 162
+        _ -> Nothing
+    )
+
+--------------------------------------------------------------------------------
+
+-- | A box border which can be rendered to a terminal given a 'BoxDrawingStyle'
+--
+-- Use the 'Monoid' and 'Semigroup' instances to combine box borders.
+--
+-- The internal representation is just a 'Word8'; the least-significant four
+-- bits specify the base directions of the box drawing (up, down, left, or
+-- right), and the most-significant four bits specify the style (see 'rounded',
+-- 'dashed2', etc) which has its own special logic for how styles can be
+-- combined.
+newtype Drawing = Drawing Word8
+  deriving newtype (Eq, Ord)
+
+instance Semigroup Drawing where
+  (<>) a@(Drawing ab) b@(Drawing bb) =
+    Drawing ((ab .|. bb) .&. 0x0f) `setStyle1` (getStyle1 a <> getStyle1 b)
+
+instance Monoid Drawing where
+  mempty = Drawing 0
+
+-- | A single box border component
+data Drawing1
+  = BUp
+  | BDown
+  | BLeft
+  | BRight
+  deriving stock (Show, Read, Eq, Ord, Enum, Bounded)
+
+newtype StyleBits = StyleBits Word8
+
+styleBits :: Drawing -> StyleBits
+styleBits (Drawing fs) = StyleBits (fs `shiftR` 4)
+
+setStyleBits :: Drawing -> StyleBits -> Drawing
+setStyleBits (Drawing fs) (StyleBits s) = Drawing ((fs .&. 0x0f) .|. (s `shiftL` 4))
+
+setStyle1 :: Drawing -> Style1 -> Drawing
+setStyle1 b s = setStyleBits b (fromStyle1 s)
+
+data Style1
+  = Light
+  | Rounded
+  | Dashed2
+  | Dashed2Heavy
+  | Dashed3
+  | Dashed3Heavy
+  | Dashed4
+  | Dashed4Heavy
+  | HeavyHoriz
+  | HeavyVert
+  | Heavy
+  | DoubleHoriz
+  | DoubleVert
+  | Double
+  deriving stock (Show, Eq, Ord, Enum, Bounded)
+
+instance Monoid Style1 where
+  mempty = Light
+
+instance Semigroup Style1 where
+  (<>) DoubleHoriz DoubleVert = Double
+  (<>) DoubleVert DoubleHoriz = Double
+  (<>) HeavyHoriz HeavyVert = Heavy
+  (<>) HeavyVert HeavyHoriz = Heavy
+  (<>) Heavy Dashed2 = Dashed2Heavy
+  (<>) Dashed2 Heavy = Dashed2Heavy
+  (<>) Heavy Dashed3 = Dashed3Heavy
+  (<>) Dashed3 Heavy = Dashed3Heavy
+  (<>) Heavy Dashed4 = Dashed4Heavy
+  (<>) Dashed4 Heavy = Dashed4Heavy
+  (<>) a b = max a b
+
+style1 :: Style1 -> Drawing
+style1 s = setStyleBits mempty (fromStyle1 s)
+
+fromStyle1 :: Style1 -> StyleBits
+fromStyle1 = StyleBits . toEnum . fromEnum
+
+dropStyleBits :: Drawing -> Drawing
+dropStyleBits (Drawing w) = Drawing (w .&. 0x0f)
+
+getStyle1 :: Drawing -> Style1
+getStyle1 (Drawing w)
+  | i >= 0 && i <= fromEnum (maxBound @Style1) = toEnum i
+  | otherwise = mempty
+  where
+    !i = fromIntegral (w `shiftR` 4)
+
+getBorderBitField :: Drawing -> Word8
+getBorderBitField (Drawing fs) = fs
+
+bitsToDrawing :: Drawing -> [Drawing1]
+bitsToDrawing (Drawing fs) = concat do
+  !b1 <- [minBound .. maxBound @Drawing1]
+  pure [b1 | testBit fs (fromEnum b1)]
+
+boxBorderBits :: Drawing1 -> Drawing
+boxBorderBits = Drawing . bit . fromEnum
+
+-- | Given a box drawing style and a character, determine what 'Drawing' the
+-- character is. On error, returns an empty 'Drawing'
+read :: BoxDrawingStyle -> Char -> Drawing
+read style ch = fromMaybe mempty (lookup style ch)
+
+-- | Get the character that best represents visually overlaying the given box
+-- drawing and a character.
+overlay :: BoxDrawingStyle -> Drawing -> Char -> Char
+overlay style next cur = render style (read style cur <> next)
