diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright © 2022, Albert Krewinkel, John MacFarlane.
+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.
+
+    * The names of the contributors may not 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
+HOLDER 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+# skylighting-format-context
+
+This package provides functions to render syntax-highlighting
+with ConTeXt commands.
+
diff --git a/skylighting-format-context.cabal b/skylighting-format-context.cabal
new file mode 100644
--- /dev/null
+++ b/skylighting-format-context.cabal
@@ -0,0 +1,37 @@
+cabal-version:       >=1.10
+name:                skylighting-format-context
+version:             0.1
+synopsis:            ConTeXt formatter for skylighting syntax highlighting
+                     library
+description:         This module allows tokens produced by skylighting-core
+                     to be rendered as ConTeXt commands.
+homepage:            https://github.com/jgm/skylighting
+license:             BSD3
+license-file:        LICENSE
+author:              Albert Krewinkel
+maintainer:          albert@tarleb.com
+copyright:           © Albert Krewinkel, John MacFarlane
+category:            Text
+build-type:          Simple
+extra-source-files:  README.md
+
+source-repository head
+  type:                git
+  location:            https://github.com/jgm/skylighting.git
+  subdir:              skylighting-format-context
+
+library
+  exposed-modules:     Skylighting.Format.ConTeXt
+  other-extensions:    CPP
+  build-depends:       base >= 4.8 && < 5.0,
+                       skylighting-core,
+                       text,
+                       containers
+  hs-source-dirs:      src
+  ghc-prof-options:    -fprof-auto-exported
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+  if impl(ghc >= 8.4)
+    ghc-options:       -fhide-source-paths
+  if impl(ghc >= 8.10)
+    ghc-options:       -Wunused-packages
diff --git a/src/Skylighting/Format/ConTeXt.hs b/src/Skylighting/Format/ConTeXt.hs
new file mode 100644
--- /dev/null
+++ b/src/Skylighting/Format/ConTeXt.hs
@@ -0,0 +1,125 @@
+{-# LANGUAGE CPP                 #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module Skylighting.Format.ConTeXt
+  ( formatConTeXtInline
+  , formatConTeXtBlock
+  , styleToConTeXt
+  ) where
+
+import Control.Monad (mplus)
+import Data.Char (isSpace)
+import Data.List (sort)
+import qualified Data.Map as Map
+import Data.Maybe (fromMaybe)
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Skylighting.Types
+import Text.Printf
+#if !MIN_VERSION_base(4,11,0)
+import Data.Semigroup
+#endif
+
+formatConTeXt :: [SourceLine] -> Text
+formatConTeXt = Text.intercalate (Text.singleton '\n')
+                       . map sourceLineToConTeXt
+
+-- | Formats tokens as ConTeXt using custom commands inside a @\type{}@.
+-- A @KeywordTok@ is rendered using @\\KeywordTok{..}@, and so on.
+formatConTeXtInline :: FormatOptions -> [SourceLine] -> Text
+formatConTeXtInline _opts ls =
+  "\\highlight{" <> formatConTeXt ls <> "}"
+
+sourceLineToConTeXt :: SourceLine -> Text
+sourceLineToConTeXt =
+  Text.replace "/ETEX/BTEX" "" .
+  Text.replace "/ETEX /BTEX" " " .
+  mconcat . map tokenToConTeXt
+
+tokenToConTeXt :: Token -> Text
+tokenToConTeXt (NormalTok, txt)
+  | Text.all isSpace txt = escapeConTeXt txt
+tokenToConTeXt (toktype, txt)   = "/BTEX\\" <>
+  (Text.pack (show toktype) <> "{" <> escapeConTeXt txt <> "}/ETEX")
+
+escapeConTeXt :: Text -> Text
+escapeConTeXt = Text.concatMap escapeConTeXtChar
+  where escapeConTeXtChar c =
+         case c of
+           '\\' -> "\\letterbackslash "
+           '{'  -> "\\letteropenbrace "
+           '}'  -> "\\letterclosebrace "
+           '|'  -> "\\letterbar "
+           '$'  -> "\\letterdollar "
+           '_'  -> "\\letterunderscore "
+           '%'  -> "\\letterpercent "
+           '#'  -> "\\letterhash "
+           '/'  -> "\\letterslash "
+           '~'  -> "\\lettertilde "
+           _    -> Text.singleton c
+
+-- ConTeXt
+
+-- | Format tokens as a ConTeXt @highlighting@ typing environment. The
+-- @highlighting@ environemnt is defined by the macros produced by
+-- 'styleToConTeXt'; it is a @typing@ environment with default escaping
+-- enabled, i.e., @/@ is the escape character.
+formatConTeXtBlock :: FormatOptions -> [SourceLine] -> Text
+formatConTeXtBlock opts ls = Text.unlines
+  [ "\\starthighlighting" <>
+    (if numberLines opts
+     then "[numbering=line]"
+     else Text.empty)
+  , formatConTeXt ls
+  , "\\stophighlighting"
+  ]
+
+-- | Converts a 'Style' to a set of ConTeXt command definitions,
+-- which should be placed in the document's preamble.
+styleToConTeXt :: Style -> Text
+styleToConTeXt f = Text.unlines $
+  ( case backgroundColor f of
+         Nothing          -> id
+         Just (RGB r g b) -> (:)
+           (Text.pack $ printf "\\definecolor[shadecolor][x=%x%x%x]" r g b)
+  ) $
+  [ "\\defineframedtext [shaded]"
+  , "  [backgroundcolor=shadecolor,"
+  , "   background=color,"
+  , "   frame=off,"
+  , "   offset=0pt,"
+  , "   width=local]"
+  , "\\definetyping [highlighting]"
+  , "  [escape=yes,"
+  , "   before={\\startshaded},"
+  , "   after={\\stopshaded}]"
+  , "\\definetype [highlight]"
+  , "  [escape=yes]"
+  ] ++
+
+  sort (map (macrodef (defaultColor f) (Map.toList (tokenStyles f)))
+            (enumFromTo KeywordTok NormalTok))
+
+macrodef :: Maybe Color -> [(TokenType, TokenStyle)] -> TokenType -> Text
+macrodef defaultcol tokstyles tokt = "\\define[1]\\"
+  <> Text.pack (show tokt)
+  <> "{"
+  <> Text.pack (co . ul . bf . it $ "#1")
+  <> "}"
+  where tokf = fromMaybe defStyle $ lookup tokt tokstyles
+        ul x = if tokenUnderline tokf
+                  then "\\underbar{" <> x <> "}"
+                  else x
+        it x = if tokenItalic tokf
+                  then "\\em " <> x
+                  else x
+        bf x = if tokenBold tokf
+                  then "\\bf " <> x
+                  else x
+        col  = fromColor `fmap` (tokenColor tokf `mplus` defaultcol)
+                 :: Maybe (Double, Double, Double)
+        co x = case col of
+                 Nothing        -> x
+                 Just (r, g, b) ->
+                   printf "\\colored[r=%0.2f,g=%0.2f,b=%0.2f]{%s}" r g b x
+
