diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,5 @@
+# Revision history for harfbuzz-pure
+
+## 0.1.0.0  -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/Data/Text/Glyphize.hs b/Data/Text/Glyphize.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/Glyphize.hs
@@ -0,0 +1,86 @@
+module Data.Text.Glyphize where
+
+import Data.Text.Glyphize.Buffer
+import Data.Text.Glyphize.Font
+
+import Data.Word
+
+import Foreign.Ptr
+import Foreign.ForeignPtr
+import Foreign.Marshal.Alloc
+import Foreign.Storable
+import Foreign.C.String
+import Control.Monad (forM)
+import Control.Concurrent.QSem
+import System.IO.Unsafe (unsafePerformIO)
+
+foreign import ccall "hb_shape" hb_shape :: Font_ -> Buffer_ -> Ptr Feature -> Int -> IO ()
+
+-- | Compute which glyphs from the provided font should be rendered where to
+-- depict the given buffer of text.
+shape :: Font -> Buffer -> [(GlyphInfo, GlyphPos)]
+shape font buf = shapeWithFeatures font buf []
+
+-- FIXME Certain input text can trigger a segfault. I'm not sure how to debug this.
+-- Thought for a moment I fixed it with a semaphore
+-- (seems related to number of threads), but appears not...
+
+data Feature = Feature {
+    tag :: String,
+    value :: Word32,
+    start :: Word,
+    end :: Word
+}
+instance Storable Feature where
+    sizeOf _ = sizeOf (undefined :: Word32) * 2 + sizeOf (undefined :: Word) * 2
+    alignment _ = alignment (undefined :: Word32)
+    peek p = do
+        let q = castPtr p
+        tag' <- peek q
+        val' <- peekElemOff q 1
+        let r = castPtr $ plusPtr p (sizeOf (undefined :: Word32) * 2)
+        start' <- peek r
+        end' <- peekElemOff r 1
+        return $ Feature (hb_tag_to_string tag') val' start' end'
+
+    poke p (Feature tag' val' start' end') = do
+        let q = castPtr p
+        poke q $ hb_tag_from_string tag'
+        pokeElemOff q 1 val'
+        let r = castPtr $ plusPtr p (sizeOf (undefined :: Word32) * 2)
+        poke r start'
+        pokeElemOff r 1 end'
+
+-- | Variant of `shape` specifying OpenType features to apply.
+-- If two features have the same tag but overlapping ranges, the one with a
+-- higher index takes precedance.
+shapeWithFeatures :: Font -> Buffer -> [Feature] -> [(GlyphInfo, GlyphPos)]
+shapeWithFeatures font buf feats = unsafePerformIO $ do
+    waitQSem shapingSem
+    buf_ <- freeze' buf
+    allocaBytes (sizeOf (undefined :: Feature) * length feats) $ \arr' -> do
+        forM (zip [0..] feats) $ \(i, feat) -> pokeElemOff arr' i feat
+        withForeignPtr font $ \font' -> withForeignPtr buf_ $ \buf' ->
+            hb_shape font' buf' arr' $ length feats
+    infos <- glyphInfos' buf_
+    pos <- glyphsPos' buf_
+    signalQSem shapingSem
+    return $ zip infos pos
+
+-- | Used to avoid segfaults...
+{-# NOINLINE shapingSem #-}
+shapingSem = unsafePerformIO $ newQSem 25
+
+foreign import ccall "hb_version" hb_version :: Ptr Int -> Ptr Int -> Ptr Int -> IO ()
+version :: (Int, Int, Int)
+version = unsafePerformIO $
+    alloca $ \a' -> alloca $ \b' -> alloca $ \c' -> do
+        hb_version a' b' c'
+        a <- peek a'
+        b <- peek b'
+        c <- peek c'
+        return (a, b, c)
+foreign import ccall "hb_version_atleast" versionAtLeast :: Int -> Int -> Int -> Bool
+foreign import ccall "hb_version_string" hb_version_string :: CString
+versionString :: String
+versionString = unsafePerformIO $ peekCString hb_version_string
diff --git a/Data/Text/Glyphize/Buffer.hs b/Data/Text/Glyphize/Buffer.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/Glyphize/Buffer.hs
@@ -0,0 +1,376 @@
+module Data.Text.Glyphize.Buffer where
+
+import Data.Text.Lazy as Lazy hiding (toUpper, toLower)
+import Data.ByteString.Lazy as Lazy hiding (toUpper, toLower)
+import Data.ByteString.Lazy as LBS
+import Data.Int
+
+import Foreign.ForeignPtr
+import Foreign.Ptr
+import Foreign.Storable
+import Foreign.Marshal.Alloc
+import Foreign.C.Types
+import Foreign.C.String
+import Data.Word
+import System.IO.Unsafe (unsafePerformIO, unsafeInterleaveIO)
+
+import Data.Text.Lazy.Encoding
+import Data.ByteString.Lazy.Internal as Lazy
+import Data.ByteString.Internal as Strict hiding (w2c, c2w)
+import Data.ByteString.Short.Internal as Strict hiding (w2c, c2w)
+import Data.Bits ((.|.), (.&.), shiftR, shiftL, testBit)
+import Data.Char (ord, chr, toUpper, toLower)
+
+import Control.Monad (forM)
+import Codec.Binary.UTF8.Light (encodeUTF8, w2c, c2w)
+
+data Buffer = Buffer {
+    text :: Either Lazy.Text Lazy.ByteString,
+    -- ^ The Unicode text, in visual order, for HarfBuzz to convert into glyphs.
+    contentType :: Maybe ContentType,
+    -- ^ What the bytes of the ByteString contents represents,
+    -- namely unicode characters (before shaping) or glyphs (result of shaping).
+    -- Typically callers should leave this as `Just ContentTypeUnicode`.
+    direction :: Maybe Direction,
+    -- ^ The text flow direction of the buffer.
+    -- No shaping can happen without setting buffer direction, and it controls
+    -- the visual direction for the output glyphs; for RTL direction the glyphs
+    -- will be reversed. Many layout features depend on the proper setting of
+    -- the direction, for example, reversing RTL text before shaping,
+    -- then shaping with LTR direction is not the same as keeping the text in
+    -- logical order and shaping with RTL direction.
+    script :: Maybe String,
+    -- ^ Script is crucial for choosing the proper shaping behaviour for scripts
+    -- that require it (e.g. Arabic) and the which OpenType features defined in
+    -- the font to be applied.
+    language :: Maybe String,
+    -- ^ Languages are crucial for selecting which OpenType feature to apply to
+    -- the buffer which can result in applying language-specific behaviour.
+    -- Languages are orthogonal to the scripts, and though they are related,
+    -- they are different concepts and should not be confused with each other.
+    beginsText :: Bool,
+    -- ^ special handling of the beginning of text paragraph can be applied to
+    -- this buffer. Should usually be set, unless you are passing to the buffer
+    -- only part of the text without the full context.
+    endsText :: Bool,
+    -- ^ special handling of the end of text paragraph can be applied to this buffer.
+    preserveDefaultIgnorables :: Bool,
+    -- ^ character with Default_Ignorable Unicode property should use the
+    -- corresponding glyph from the font, instead of hiding them (done by
+    -- replacing them with the space glyph and zeroing the advance width.)
+    -- Takes precedance over `removeDefaultIgnorables`.
+    removeDefaultIgnorables :: Bool,
+    -- ^ character with Default_Ignorable Unicode property should be removed
+    -- from glyph string instead of hiding them (done by replacing them with
+    -- the space glyph and zeroing the advance width.)
+    don'tInsertDottedCircle :: Bool,
+    -- ^ a dotted circle should not be inserted in the rendering of incorrect
+    -- character sequences (such at <0905 093E>).
+    clusterLevel :: ClusterLevel,
+    -- ^ dictates one aspect of how HarfBuzz will treat non-base characters
+    -- during shaping.
+    invisibleGlyph :: Char,
+    -- ^ The glyph number that replaces invisible characters in the
+    -- shaping result. If set to zero (default), the glyph for the U+0020
+    -- SPACE character is used. Otherwise, this value is used verbatim.
+    replacementCodepoint :: Char
+    -- ^ the hb_codepoint_t that replaces invalid entries for a given encoding
+    -- when adding text to buffer .
+} deriving (Eq, Show, Read)
+
+-- | An empty buffer with sensible default properties.
+defaultBuffer = Buffer {
+        text = Right LBS.empty,
+        contentType = Just ContentTypeUnicode,
+        direction = Nothing,
+        script = Nothing,
+        language = Nothing,
+        beginsText = True,
+        endsText = True,
+        preserveDefaultIgnorables = False,
+        removeDefaultIgnorables = False,
+        don'tInsertDottedCircle = False,
+        clusterLevel = ClusterMonotoneGraphemes,
+        invisibleGlyph = '\0',
+        replacementCodepoint = '\xFFFD'
+    }
+
+data ContentType = ContentTypeUnicode | ContentTypeGlyphs deriving (Eq, Show, Read)
+data Direction = DirLTR | DirRTL | DirTTB | DirBTT deriving (Eq, Show, Read)
+data ClusterLevel = ClusterMonotoneGraphemes | ClusterMonotoneChars | ClusterChars deriving (Eq, Show, Read)
+
+data GlyphInfo = GlyphInfo {
+    codepoint :: Word32,
+    -- ^ Glyph index (or unicode codepoint)
+    cluster :: Word32
+    -- ^ The index of the character in the original text that corresponds to
+    -- this `GlyphInfo`. More than one `GlyphInfo` may have the same `cluster`
+    -- value if they resulted from the same character, & when more than one
+    -- character gets merged into the same glyph `GlyphInfo` will have the
+    -- smallest cluster value of them.
+    -- By default some characters are merged into the same cluster even when
+    -- they are seperate glyphs, `Buffer`'s `clusterLevel` property allows
+    -- selecting more fine grained cluster handling.
+} deriving (Show, Read, Eq)
+instance Storable GlyphInfo where
+    sizeOf _ = 2 * sizeOf (undefined :: Word32)
+    alignment _ = alignment (undefined :: Word32)
+    peek p = do
+        codepoint' <- peek $ castPtr p
+        cluster' <- peekElemOff (castPtr p) 1
+        return $ GlyphInfo codepoint' cluster'
+    poke p (GlyphInfo a b) = do
+        q <- return $ castPtr p
+        poke q a
+        pokeElemOff q 1 b
+
+data GlyphPos = GlyphPos {
+    x_advance :: Int32,
+    -- ^ How much the line advances after drawing this glyph when setting text
+    -- in horizontal direction.
+    y_advance :: Int32,
+    -- ^ How much the line advances after drawing this glyph when setting text
+    -- in vertical direction.
+    x_offset :: Int32,
+    -- ^ How much the glyph moves on the X-axis before drawing it, this should
+    -- not effect how much the line advances.
+    y_offset :: Int32
+    -- ^ How much the glyph moves on the Y-axis before drawing it, this should
+    -- not effect how much the line advances.
+} deriving (Show, Read, Eq)
+instance Storable GlyphPos where
+    sizeOf _ = 4 * sizeOf (undefined :: Int32)
+    alignment _ = alignment (undefined :: Int32)
+    peek p = do
+        q <- return $ castPtr p
+        xa <- peek q
+        ya <- peekElemOff q 1
+        xoff <- peekElemOff q 2
+        yoff <- peekElemOff q 3
+        return $ GlyphPos xa ya xoff yoff
+    poke p (GlyphPos xa ya xoff yoff) = do
+        q <- return $ castPtr p
+        poke q xa
+        pokeElemOff q 1 ya
+        pokeElemOff q 2 xoff
+        pokeElemOff q 3 yoff
+
+guessSegmentProperties :: Buffer -> Buffer
+guessSegmentProperties = thaw . freeze
+scriptHorizontalDir :: String -> Maybe Direction
+scriptHorizontalDir script =
+    int2dir $ hb_script_get_horizontal_direction $ hb_script_from_string script
+
+int2dir 4 = Just DirLTR
+int2dir 5 = Just DirRTL
+int2dir 6 = Just DirTTB
+int2dir 7 = Just DirBTT
+int2dir _ = Nothing
+dir2int Nothing = 0
+dir2int (Just DirLTR) = 4
+dir2int (Just DirRTL) = 5
+dir2int (Just DirTTB) = 6
+dir2int (Just DirBTT) = 7
+
+dirReverse DirLTR = DirRTL
+dirReverse DirRTL = DirLTR
+dirReverse DirTTB = DirBTT
+dirReverse DirBTT = DirTTB
+dirBackward dir = dir `Prelude.elem` [DirRTL, DirBTT]
+dirForward dir = dir `Prelude.elem` [DirLTR, DirTTB]
+dirHorizontal dir = dir `Prelude.elem` [DirLTR, DirRTL]
+dirVertical dir = dir `Prelude.elem` [DirTTB, DirBTT]
+
+---
+
+type Buffer' = ForeignPtr Buffer''
+data Buffer''
+type Buffer_ = Ptr Buffer''
+
+-- | Converts from a Haskell `Buffer` representation into a C representation used internally.
+freeze = unsafePerformIO . freeze'
+-- | Variant of `freeze` for use in IO code.
+freeze' buf = do
+    buffer <- hb_buffer_create
+    case text buf of
+        Right bs -> hb_buffer_add_bytestring buffer bs
+        -- Convert text to bytestring for now due to the text 2.0 UTF-8 transition.
+        -- Unfortunately this may prevent Harfbuzz from reading opening context
+        -- So for correctness we'll eventually want to depend on text>2.0
+        Left txt -> hb_buffer_add_bytestring buffer $ encodeUtf8 txt
+    hb_buffer_set_content_type buffer $ case contentType buf of
+        Nothing -> 0
+        Just ContentTypeUnicode -> 1
+        Just ContentTypeGlyphs -> 2
+    hb_buffer_set_direction buffer $ dir2int $ direction buf
+    case script buf of
+        Just script' -> hb_buffer_set_script buffer $ hb_script_from_string script'
+        Nothing -> return ()
+    case language buf of
+        Just lang' -> hb_buffer_set_language buffer =<< hb_language_from_string lang'
+        Nothing -> return ()
+    hb_buffer_set_flags buffer $ Prelude.foldl (.|.) 0 [
+        if beginsText buf then 1 else 0,
+        if endsText buf then 2 else 0,
+        if preserveDefaultIgnorables buf then 4 else 0,
+        if removeDefaultIgnorables buf then 8 else 0,
+        if don'tInsertDottedCircle buf then 16 else 0
+      ]
+    hb_buffer_set_cluster_level buffer $ case clusterLevel buf of
+        ClusterMonotoneGraphemes -> 0
+        ClusterMonotoneChars -> 1
+        ClusterChars -> 2
+    hb_buffer_set_invisible_glyph buffer $ c2w $ invisibleGlyph buf
+    hb_buffer_set_replacement_codepoint buffer $ c2w $ replacementCodepoint buf
+    case (contentType buf, direction buf, script buf, language buf) of
+        (Just ContentTypeUnicode, Nothing, _, _) -> hb_buffer_guess_segment_properties buffer
+        (Just ContentTypeUnicode, _, Nothing, _) -> hb_buffer_guess_segment_properties buffer
+        (Just ContentTypeUnicode, _, _, Nothing) -> hb_buffer_guess_segment_properties buffer
+    newForeignPtr hb_buffer_destroy buffer
+
+-- | The Buffer's glyph information list.
+glyphInfos :: Buffer' -> [GlyphInfo]
+glyphInfos = unsafePerformIO . glyphInfos'
+-- | Variant of `glyphInfos` for use in IO code.
+glyphInfos' :: Buffer' -> IO [GlyphInfo]
+glyphInfos' buf' = alloca $ \length' -> do
+    arr <- withForeignPtr buf' $ \buf'' -> hb_buffer_get_glyph_infos buf'' length'
+    length <- peek length'
+    if length == 0 || arr == nullPtr
+    then return []
+    else forM [0..fromEnum length - 1] $ peekElemOff arr
+-- | The Buffer's glyph position list. If not already computed defaults to all 0s.
+glyphsPos :: Buffer' -> [GlyphPos]
+glyphsPos = unsafePerformIO . glyphsPos'
+-- | Variant of `glyphsPos` for use in IO code.
+glyphsPos' :: Buffer' -> IO [GlyphPos]
+glyphsPos' buf' = alloca $ \length' -> do
+      arr <- withForeignPtr buf' $ \buf'' -> hb_buffer_get_glyph_positions buf'' length'
+      length <- peek length'
+      if length == 0 || arr == nullPtr
+      then return []
+      else forM [0..fromEnum length-1] $ peekElemOff arr
+
+-- | Converts from the C representation of a Buffer used internally back into a
+-- pure-Haskell representation.
+thaw :: Buffer' -> Buffer
+thaw = unsafePerformIO . thaw'
+-- | Variant of `thaw` for use in IO code.
+thaw' buf' = do
+    let getter cb = unsafeInterleaveIO $ withForeignPtr buf' cb
+    glyphInfos' <- glyphInfos' buf'
+    contentType' <- getter hb_buffer_get_content_type
+    direction' <- getter hb_buffer_get_direction
+    script' <- getter hb_buffer_get_script
+    language' <- unsafeInterleaveIO $ do
+        lang <- withForeignPtr buf' $ \buf'' -> hb_buffer_get_language buf''
+        peekCString $ hb_language_to_string lang
+    flags' <- getter hb_buffer_get_flags
+    clusterLevel' <- getter hb_buffer_get_cluster_level
+    invisibleGlyph' <- getter hb_buffer_get_invisible_glyph
+    replacementCodepoint' <- getter hb_buffer_get_replacement_codepoint
+    return Buffer {
+        text = Right $ LBS.fromStrict $ encodeUTF8 $ Prelude.map codepoint glyphInfos',
+        contentType = case contentType' of
+            1 -> Just ContentTypeUnicode
+            2 -> Just ContentTypeGlyphs
+            _ -> Nothing,
+        direction = int2dir direction',
+        language = Just language',
+        script = Just $ hb_tag_to_string script',
+        beginsText = testBit flags' 0, endsText = testBit flags' 1,
+        preserveDefaultIgnorables = testBit flags' 2,
+        removeDefaultIgnorables = testBit flags' 3,
+        don'tInsertDottedCircle = testBit flags' 4,
+        clusterLevel = case clusterLevel' of
+            1 -> ClusterMonotoneChars
+            2 -> ClusterChars
+            _ -> ClusterMonotoneGraphemes,
+        invisibleGlyph = w2c invisibleGlyph', replacementCodepoint = w2c replacementCodepoint'
+      }
+
+foreign import ccall "hb_buffer_create" hb_buffer_create :: IO Buffer_
+foreign import ccall "&hb_buffer_destroy" hb_buffer_destroy :: FunPtr (Buffer_ -> IO ())
+foreign import ccall "hb_buffer_add_utf8" hb_buffer_add_utf8
+    :: Buffer_ -> Ptr Word8 -> Int -> Int -> Int -> IO ()
+hb_buffer_add_bytestring _ Lazy.Empty = return ()
+hb_buffer_add_bytestring buf (Lazy.Chunk (Strict.PS ptr offset length) next) = do
+    withForeignPtr ptr $ \ptr' -> hb_buffer_add_utf8 buf ptr' length offset (length - offset)
+    hb_buffer_add_bytestring buf next
+foreign import ccall "hb_buffer_set_content_type" hb_buffer_set_content_type
+    :: Buffer_ -> Int -> IO ()
+foreign import ccall "hb_buffer_set_direction" hb_buffer_set_direction
+    :: Buffer_ -> Int -> IO ()
+hb_tag_from_string :: String -> Word32
+hb_tag_from_string str = case str ++ Prelude.repeat '\0' of
+    c1:c2:c3:c4:_ -> Prelude.foldl (.|.) 0 [
+        shiftL (c2w c1 .&. 0x7) 24,
+        shiftL (c2w c2 .&. 0x7) 16,
+        shiftL (c2w c3 .&. 0x7) 8,
+        shiftL (c2w c4 .&. 0x7) 0
+      ]
+    _ -> 0
+titlecase :: String -> String
+titlecase "" = ""
+titlecase (c:cs) = toUpper c : Prelude.map toLower cs
+hb_script_from_string str = hb_tag_from_string $ case titlecase str of
+    'Q':'a':'a':'i':_ -> "Zinh"
+    'Q':'a':'a':'c':_ -> "Copt"
+
+    'A':'r':'a':'n':_ -> "Arab"
+    'C':'y':'r':'s':_ -> "Cyrl"
+    'G':'e':'o':'k':_ -> "Geor"
+    'H':'a':'n':'s':_ -> "Hani"
+    'H':'a':'n':'t':_ -> "Hani"
+    'J':'a':'m':'o':_ -> "Hang"
+    'L':'a':'t':'f':_ -> "Latn"
+    'L':'a':'t':'g':_ -> "Latn"
+    'S':'y':'r':'e':_ -> "Syrc"
+    'S':'y':'r':'j':_ -> "Syrc"
+    'S':'y':'r':'n':_ -> "Syrc"
+foreign import ccall "hb_buffer_set_script" hb_buffer_set_script
+    :: Buffer_ -> Word32 -> IO ()
+foreign import ccall "hb_script_get_horizontal_direction" hb_script_get_horizontal_direction
+    :: Word32 -> Int
+foreign import ccall "hb_language_from_string" hb_language_from_string'
+    :: CString -> Int -> IO Int
+hb_language_from_string :: String -> IO Int
+hb_language_from_string str =
+    withCString str $ \str' -> hb_language_from_string' str' (-1)
+foreign import ccall "hb_buffer_set_language" hb_buffer_set_language
+    :: Buffer_ -> Int -> IO ()
+foreign import ccall "hb_buffer_set_flags" hb_buffer_set_flags :: Buffer_ -> Int -> IO ()
+foreign import ccall "hb_buffer_set_cluster_level" hb_buffer_set_cluster_level
+    :: Buffer_ -> Int -> IO ()
+foreign import ccall "hb_buffer_set_invisible_glyph" hb_buffer_set_invisible_glyph
+    :: Buffer_ -> Word32 -> IO ()
+foreign import ccall "hb_buffer_set_replacement_codepoint" hb_buffer_set_replacement_codepoint
+    :: Buffer_ -> Word32 -> IO ()
+foreign import ccall "hb_buffer_guess_segment_properties" hb_buffer_guess_segment_properties
+    :: Buffer_ -> IO ()
+
+
+foreign import ccall "hb_buffer_get_content_type" hb_buffer_get_content_type
+    :: Buffer_ -> IO Int
+foreign import ccall "hb_buffer_get_direction" hb_buffer_get_direction :: Buffer_ -> IO Int
+foreign import ccall "hb_buffer_get_script" hb_buffer_get_script :: Buffer_ -> IO Word32
+hb_tag_to_string :: Word32 -> String
+hb_tag_to_string tag = [
+    w2c (shiftR tag 24 .&. 0x7),
+    w2c (shiftR tag 16 .&. 0x7),
+    w2c (shiftR tag 8 .&. 0x7),
+    w2c (shiftR tag 0 .&. 0x7)
+  ]
+foreign import ccall "hb_buffer_get_language" hb_buffer_get_language :: Buffer_ -> IO (Ptr ())
+foreign import ccall "hb_language_to_string" hb_language_to_string :: Ptr () -> CString
+foreign import ccall "hb_buffer_get_flags" hb_buffer_get_flags :: Buffer_ -> IO Int
+foreign import ccall "hb_buffer_get_cluster_level" hb_buffer_get_cluster_level
+    :: Buffer_ -> IO Int
+foreign import ccall "hb_buffer_get_glyph_infos" hb_buffer_get_glyph_infos
+    :: Buffer_ -> Ptr Word -> IO (Ptr GlyphInfo)
+foreign import ccall "hb_buffer_get_glyph_positions" hb_buffer_get_glyph_positions
+    :: Buffer_ -> Ptr Word -> IO (Ptr GlyphPos)
+foreign import ccall "hb_buffer_get_invisible_glyph" hb_buffer_get_invisible_glyph
+    :: Buffer_ -> IO Word32
+foreign import ccall "hb_buffer_get_replacement_codepoint" hb_buffer_get_replacement_codepoint
+    :: Buffer_ -> IO Word32
diff --git a/Data/Text/Glyphize/Font.hs b/Data/Text/Glyphize/Font.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/Glyphize/Font.hs
@@ -0,0 +1,515 @@
+{-# LANGUAGE PackageImports #-}
+module Data.Text.Glyphize.Font where
+
+import Data.ByteString
+import FreeType.Core.Base
+import Data.Text.Glyphize.Buffer (Direction(..), dir2int, hb_tag_to_string)
+
+import System.IO.Unsafe (unsafePerformIO)
+import Foreign.Ptr
+import Foreign.StablePtr
+import Foreign.ForeignPtr
+import qualified Foreign.Concurrent as Conc
+import Foreign.Marshal.Alloc
+import Foreign.Storable
+import Foreign.C.String
+
+import Data.Maybe (fromMaybe)
+import Data.Word
+import Data.Int
+import Data.ByteString.Internal hiding (c2w)
+import Codec.Binary.UTF8.Light (c2w)
+import Control.Monad (forM)
+
+type Face = ForeignPtr Face'
+type Face_ = Ptr Face'
+data Face'
+
+foreign import ccall "hb_face_count" hb_face_count :: Blob_ -> IO Word
+countFace :: ByteString -> Word
+countFace bytes = unsafePerformIO $ do
+    blob <- bs2blob bytes
+    withForeignPtr blob hb_face_count
+
+foreign import ccall "hb_face_create" hb_face_create :: Blob_ -> Word -> IO Face_
+foreign import ccall "hb_face_destroy" hb_face_destroy :: Face_ -> IO ()
+foreign import ccall "&hb_face_destroy" hb_face_destroy' :: FunPtr (Face_ -> IO ())
+createFace :: ByteString -> Word -> Face
+createFace bytes index = unsafePerformIO $ do
+    blob <- bs2blob bytes
+    face <- withForeignPtr blob $ flip hb_face_create index
+    Conc.newForeignPtr face $ hb_face_destroy face
+
+foreign import ccall "hb_ft_face_create_referenced" hb_ft_face_create_referenced
+    :: FT_Face -> Face_
+ftCreateFace :: FT_Face -> Face
+ftCreateFace =
+    unsafePerformIO . newForeignPtr hb_face_destroy' . hb_ft_face_create_referenced
+
+foreign import ccall "hb_face_get_empty" hb_face_get_empty :: Face_
+emptyFace :: Face
+emptyFace = unsafePerformIO $ newForeignPtr hb_face_destroy' hb_face_get_empty
+
+foreign import ccall "hb_face_get_table_tags" hb_face_get_table_tags
+    :: Face_ -> Word -> Ptr Word -> Ptr Word32 -> IO Word
+faceTableTags :: Face -> Word -> Word -> (Word, [String])
+faceTableTags fce offs cnt = unsafePerformIO $ withForeignPtr fce $ \fce' -> do
+    alloca $ \cnt' -> allocaBytes (fromEnum cnt * 4) $ \arr' -> do
+        poke cnt' cnt
+        length <- hb_face_get_table_tags fce' offs cnt' arr'
+        cnt_ <- peek cnt'
+        arr <- forM [0..fromEnum cnt_-1] $ peekElemOff arr'
+        return (length, Prelude.map hb_tag_to_string arr)
+
+foreign import ccall "hb_face_get_glyph_count" hb_face_get_glyph_count :: Face_ -> Word
+faceGlyphCount :: Face -> Word
+faceGlyphCount = faceFunc hb_face_get_glyph_count
+
+foreign import ccall "hb_face_collect_unicodes" hb_face_collect_unicodes
+    :: Face_ -> Set_ -> IO ()
+faceCollectUnicodes :: Face -> [Word32]
+faceCollectUnicodes = faceCollectFunc hb_face_collect_unicodes
+
+foreign import ccall "hb_face_collect_variation_selectors"
+    hb_face_collect_variation_selectors :: Face_ -> Set_ -> IO ()
+faceCollectVarSels :: Face -> [Word32]
+faceCollectVarSels = faceCollectFunc hb_face_collect_variation_selectors
+
+foreign import ccall "hb_face_collect_variation_unicodes"
+    hb_face_collect_variation_unicodes :: Face_ -> Word32 -> Set_ -> IO ()
+faceCollectVarUnicodes :: Face -> Word32 -> [Word32]
+faceCollectVarUnicodes fce varSel = unsafePerformIO $ withForeignPtr fce $ \fce' -> do
+    set <- createSet
+    withForeignPtr set $ hb_face_collect_variation_unicodes fce' varSel
+    set2list set
+
+foreign import ccall "hb_face_get_index" hb_face_get_index :: Face_ -> Word
+faceIndex :: Face -> Word
+faceIndex = faceFunc hb_face_get_index
+
+foreign import ccall "hb_face_get_upem" hb_face_get_upem :: Face_ -> Word
+-- | units-per-em
+faceUpem :: Face -> Word
+faceUpem = faceFunc hb_face_get_upem
+
+
+-- Defer implementation of other functions...
+
+---
+
+type Font = ForeignPtr Font'
+type Font_ = Ptr Font'
+data Font'
+
+foreign import ccall "hb_font_create" hb_font_create :: Face_ -> IO Font_
+foreign import ccall "hb_font_make_immutable" hb_font_make_immutable :: Font_ -> IO ()
+foreign import ccall "hb_font_destroy" hb_font_destroy :: Font_ -> IO ()
+foreign import ccall "&hb_font_destroy" hb_font_destroy' :: FunPtr (Font_ -> IO ())
+createFont :: Face -> Font
+createFont fce = unsafePerformIO $ do
+    font <- withForeignPtr fce $ hb_font_create
+    hb_font_make_immutable font
+    Conc.newForeignPtr font $ hb_font_destroy font
+
+foreign import ccall "hb_ft_font_create_referenced" hb_ft_font_create_referenced
+    :: FT_Face -> IO Font_
+ftCreateFont :: FT_Face -> IO Font
+ftCreateFont fce = do
+    font <- hb_ft_font_create_referenced fce
+    hb_font_make_immutable font
+    newForeignPtr hb_font_destroy' font
+
+foreign import ccall "hb_font_get_empty" hb_font_get_empty :: Font_
+emptyFont :: Font
+emptyFont = unsafePerformIO $ newForeignPtr hb_font_destroy' hb_font_get_empty
+
+foreign import ccall "hb_font_get_glyph" hb_font_get_glyph
+    :: Font_ -> Word32 -> Word32 -> Ptr Word32 -> IO Bool
+fontGlyph :: Font -> Char -> Maybe Char -> Maybe Word32
+fontGlyph font char var =
+    unsafePerformIO $ withForeignPtr font $ \font' -> alloca $ \ret -> do
+        success <- hb_font_get_glyph font' (c2w char) (c2w $ fromMaybe '\0' var) ret
+        if success then return . Just =<< peek ret else return Nothing
+
+foreign import ccall "hb_font_get_glyph_advance_for_direction"
+    hb_font_get_glyph_advance_for_direction
+        :: Font_ -> Word32 -> Int -> Ptr Int32 -> Ptr Int32 -> IO ()
+fontGlyphAdvance :: Font -> Word32 -> Maybe Direction -> (Int32, Int32)
+fontGlyphAdvance font glyph dir = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do
+        hb_font_get_glyph_advance_for_direction font' glyph (dir2int dir) x' y'
+        x <- peek x'
+        y <- peek y'
+        return (x, y)
+
+foreign import ccall "hb_font_get_glyph_contour_point" hb_font_get_glyph_contour_point
+    :: Font_ -> Word32 -> Int -> Ptr Int32 -> Ptr Int32 -> IO Bool
+fontGlyphContourPoint :: Font -> Word32 -> Int -> Maybe (Int32, Int32)
+fontGlyphContourPoint font glyph index = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do
+        success <- hb_font_get_glyph_contour_point font' glyph index x' y'
+        if success
+        then do
+            x <- peek x'
+            y <- peek y'
+            return $ Just (x, y)
+        else return Nothing
+
+foreign import ccall "hb_font_get_glyph_contour_point_for_origin"
+    hb_font_get_glyph_contour_point_for_origin
+        :: Font_ -> Word32 -> Int -> Int -> Ptr Int32 -> Ptr Int32 -> IO Bool
+fontGlyphContourPointForOrigin :: Font -> Word32 -> Int -> Maybe Direction -> Maybe (Int32, Int32)
+fontGlyphContourPointForOrigin font glyph index dir = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do
+        success <- hb_font_get_glyph_contour_point_for_origin font' glyph index
+                (dir2int dir) x' y'
+        if success
+        then do
+            x <- peek x'
+            y <- peek y'
+            return $ Just (x, y)
+        else return Nothing
+
+data GlyphExtents = GlyphExtents {
+    xBearing :: Word32, yBearing :: Word32,
+    width :: Word32, height :: Word32
+}
+instance Storable GlyphExtents where
+    sizeOf _ = 4 * sizeOf (undefined :: Word32)
+    alignment _ = alignment (undefined :: Word32)
+    peek p = do
+        q <- return $ castPtr p
+        x <- peek q
+        y <- peekElemOff q 1
+        width <- peekElemOff q 2
+        height <- peekElemOff q 3
+        return $ GlyphExtents x y width height
+    poke p (GlyphExtents x y width height) = do
+        q <- return $ castPtr p
+        poke q x
+        pokeElemOff q 1 y
+        pokeElemOff q 2 width
+        pokeElemOff q 3 height
+
+foreign import ccall "hb_font_get_glyph_extents" hb_font_get_glyph_extents
+    :: Font_ -> Word32 -> Ptr GlyphExtents -> IO Bool
+fontGlyphExtents :: Font -> Word32 -> Maybe GlyphExtents
+fontGlyphExtents font glyph = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \ret -> do
+        success <- hb_font_get_glyph_extents font' glyph ret
+        if success
+        then return . Just =<< peek ret
+        else return Nothing
+
+foreign import ccall "hb_font_get_glyph_extents_for_origin"
+    hb_font_get_glyph_extents_for_origin
+        :: Font_ -> Word32 -> Int -> Ptr GlyphExtents -> IO Bool
+fontGlyphExtentsForOrigin :: Font -> Word32 -> Maybe Direction -> Maybe GlyphExtents
+fontGlyphExtentsForOrigin font glyph dir = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \ret -> do
+        ok <- hb_font_get_glyph_extents_for_origin font' glyph (dir2int dir) ret
+        if ok
+        then return . Just =<< peek ret
+        else return Nothing
+
+foreign import ccall "hb_font_get_glyph_from_name" hb_font_get_glyph_from_name
+    :: Font_ -> CString -> Int -> Ptr Word32 -> IO Bool
+fontGlyphFromName :: Font -> String -> Maybe Word32
+fontGlyphFromName font name = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \ret -> do
+        success <- withCString name $ \name' ->
+            hb_font_get_glyph_from_name font' name' (-1) ret
+        if success
+        then return . Just =<< peek ret
+        else return Nothing
+
+foreign import ccall "hb_font_get_glyph_h_advance" hb_font_get_glyph_h_advance
+    :: Font_ -> Word32 -> Int32
+fontGlyphHAdvance :: Font -> Word32 -> Int32
+fontGlyphHAdvance = fontFunc hb_font_get_glyph_h_advance
+
+foreign import ccall "hb_font_get_glyph_h_kerning" hb_font_get_glyph_h_kerning
+    :: Font_ -> Word32 -> Word32 -> Int32
+fontGlyphHKerning :: Font -> Word32 -> Word32 -> Int32
+fontGlyphHKerning = fontFunc hb_font_get_glyph_h_kerning
+
+foreign import ccall "hb_font_get_glyph_h_origin" hb_font_get_glyph_h_origin
+    :: Font_ -> Word32 -> Ptr Int32 -> Ptr Int32 -> IO Bool
+fontGlyphHOrigin :: Font -> Word32 -> Maybe (Int32, Int32)
+fontGlyphHOrigin font glyph = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do
+        success <- hb_font_get_glyph_h_origin font' glyph x' y'
+        if success
+        then do
+            x <- peek x'
+            y <- peek y'
+            return $ Just (x, y)
+        else return Nothing
+
+foreign import ccall "hb_font_get_glyph_kerning_for_direction"
+    hb_font_get_glyph_kerning_for_direction
+        :: Font_ -> Word32 -> Word32 -> Int -> Ptr Int32 -> Ptr Int32 -> IO ()
+fontGlyphKerningForDir :: Font -> Word32 -> Word32 -> Maybe Direction -> (Int32, Int32)
+fontGlyphKerningForDir font glyph1 glyph2 dir = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do
+        hb_font_get_glyph_kerning_for_direction font' glyph1 glyph2 (dir2int dir) x' y'
+        x <- peek x'
+        y <- peek y'
+        return (x, y)
+
+foreign import ccall "hb_font_get_glyph_name" hb_font_get_glyph_name
+    :: Font_ -> Word32 -> CString -> Word -> IO Bool
+fontGlyphName :: Font -> Word32 -> Word -> Maybe String
+fontGlyphName font glyph length = let lengthi = fromEnum length in unsafePerformIO $
+    withForeignPtr font $ \font' -> allocaBytes lengthi $ \ret -> do
+        success <- hb_font_get_glyph_name font' glyph ret length
+        if success
+        then return . Just =<< peekCString ret
+        else return Nothing
+
+foreign import ccall "hb_font_get_glyph_origin_for_direction"
+    hb_font_get_glyph_origin_for_direction
+        :: Font_ -> Word32 -> Int -> Ptr Int32 -> Ptr Int32 -> IO ()
+fontGlyphOriginForDir :: Font -> Word32 -> Maybe Direction -> (Int32, Int32)
+fontGlyphOriginForDir font glyph dir = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do
+        hb_font_get_glyph_origin_for_direction font' glyph (dir2int dir) x' y'
+        x <- peek x'
+        y <- peek y'
+        return (x, y)
+
+foreign import ccall "hb_font_get_glyph_v_advance"
+    hb_font_get_glyph_v_advance :: Font_ -> Word32 -> Int32
+fontGlyphVAdvance :: Font -> Word32 -> Int32
+fontGlyphVAdvance = fontFunc hb_font_get_glyph_v_advance
+
+foreign import ccall "hb_font_get_glyph_v_origin" hb_font_get_glyph_v_origin
+    :: Font_ -> Word32 -> Ptr Int32 -> Ptr Int32 -> IO Bool
+fontGlyphVOrigin :: Font -> Word32 -> Maybe (Int32, Int32)
+fontGlyphVOrigin font glyph = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do
+        success <- hb_font_get_glyph_v_origin font' glyph x' y'
+        if success
+        then do
+            x <- peek x'
+            y <- peek y'
+            return $ Just (x, y)
+        else return Nothing
+
+foreign import ccall "hb_font_get_nominal_glyph" hb_font_get_nominal_glyph
+    :: Font_ -> Char -> Ptr Word32 -> IO Bool
+fontNominalGlyph :: Font -> Char -> Maybe Word32
+fontNominalGlyph font char = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \ret -> do
+        success <- hb_font_get_nominal_glyph font' char ret
+        if success
+        then return . Just =<< peek ret
+        else return Nothing
+
+foreign import ccall "hb_font_get_ppem" hb_font_get_ppem
+    :: Font_ -> Ptr Word -> Ptr Word -> IO ()
+fontPPEm :: Font -> (Word, Word)
+fontPPEm font = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do
+        hb_font_get_ppem font' x' y'
+        x_ppem <- peek x'
+        y_ppem <- peek y'
+        return (x_ppem, y_ppem)
+
+foreign import ccall "hb_font_get_ptem" hb_font_get_ptem :: Font_ -> Float
+fontPtEm :: Font -> Float
+fontPtEm = fontFunc hb_font_get_ptem
+
+foreign import ccall "hb_font_get_scale" hb_font_get_scale
+    :: Font_ -> Ptr Int -> Ptr Int -> IO ()
+fontScale :: Font -> (Int, Int)
+fontScale font = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do
+        hb_font_get_scale font' x' y'
+        x <- peek x'
+        y <- peek y'
+        return (x, y)
+
+foreign import ccall "hb_font_get_variation_glyph" hb_font_get_variation_glyph
+    :: Font_ -> Word32 -> Word32 -> Ptr Word32 -> IO Bool
+fontVarGlyph :: Font -> Word32 -> Word32 -> Maybe Word32
+fontVarGlyph font unicode varSel = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \glyph' -> do
+        success <- hb_font_get_variation_glyph font' unicode varSel glyph'
+        if success
+        then return . Just =<< peek glyph'
+        else return Nothing
+
+foreign import ccall "hb_font_get_var_coords_normalized"
+    hb_font_get_var_coords_normalized :: Font_ -> Ptr Word -> IO (Ptr Int)
+fontVarCoordsNormalized :: Font -> [Int]
+fontVarCoordsNormalized font = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \length' -> do
+        arr <- hb_font_get_var_coords_normalized font' length'
+        length <- peek length'
+        forM [0..fromEnum length-1] $ peekElemOff arr
+
+foreign import ccall "hb_font_glyph_from_string" hb_font_glyph_from_string
+    :: Font_ -> CString -> Int -> Ptr Word32 -> IO Bool
+fontTxt2Glyph :: Font -> String -> Maybe Word32
+fontTxt2Glyph font str = unsafePerformIO $
+    withForeignPtr font $ \font' -> alloca $ \ret -> do
+        ok <- withCString str $ \str' ->
+            hb_font_glyph_from_string font' str' (-1) ret
+        if ok
+        then return . Just =<< peek ret
+        else return Nothing
+
+foreign import ccall "hb_font_glyph_to_string" hb_font_glyph_to_string
+    :: Font_ -> Word32 -> CString -> Int -> IO ()
+fontGlyph2Str :: Font -> Word32 -> Int -> String
+fontGlyph2Str font glyph length = unsafePerformIO $
+    withForeignPtr font $ \font' -> allocaBytes length $ \ret -> do
+        hb_font_glyph_to_string font' glyph ret length
+        peekCString ret
+
+data FontExtents = FontExtents {
+    ascender :: Int32,
+    descender :: Int32,
+    lineGap :: Int32
+}
+
+instance Storable FontExtents where
+    sizeOf _ = sizeOf (undefined :: Int32) * 3
+    alignment _ = alignment (undefined :: Int32)
+    peek p = do
+        let q = castPtr p
+        asc <- peek q
+        desc <- peekElemOff q 1
+        gap <- peekElemOff q 2
+        return $ FontExtents asc desc gap
+    poke p (FontExtents asc desc gap) = do
+        let q = castPtr p
+        poke q asc
+        pokeElemOff q 1 desc
+        pokeElemOff q 2 gap
+
+foreign import ccall "hb_font_get_extents_for_direction"
+    hb_font_get_extents_for_direction :: Font_ -> Int -> Ptr FontExtents -> IO ()
+fontExtentsForDir :: Font -> Maybe Direction -> FontExtents
+fontExtentsForDir font dir = unsafePerformIO $ alloca $ \ret -> do
+    withForeignPtr font $ \font' ->
+        hb_font_get_extents_for_direction font' (dir2int dir) ret
+    peek ret
+
+foreign import ccall "hb_font_get_h_extents" hb_font_get_h_extents
+    :: Font_ -> Ptr FontExtents -> IO Bool
+fontHExtents font = unsafePerformIO $ alloca $ \ret -> do
+    ok <- withForeignPtr font $ \font' -> hb_font_get_h_extents font' ret
+    if ok
+    then return . Just =<< peek ret
+    else return Nothing
+
+foreign import ccall "hb_font_get_v_extents" hb_font_get_v_extents
+    :: Font_ -> Ptr FontExtents -> IO Bool
+fontVExtents font = unsafePerformIO $ alloca $ \ret -> do
+    ok <- withForeignPtr font $ \font' -> hb_font_get_v_extents font' ret
+    if ok
+    then return . Just =<< peek ret
+    else return Nothing
+
+---
+
+data FontOptions = FontOptions {
+    optionPPEm :: Maybe (Word, Word),
+    optionPtEm :: Maybe Float,
+    optionScale :: Maybe (Int, Int)
+}
+defaultFontOptions = FontOptions {
+    optionPPEm = Nothing, optionPtEm = Nothing,
+    optionScale = Nothing
+}
+
+_setFontOptions font opts = do
+    case optionPPEm opts of
+        Just (x, y) -> hb_font_set_ppem font x y
+        Nothing -> return ()
+    case optionPtEm opts of
+        Just ptem -> hb_font_set_ptem font ptem
+        Nothing -> return ()
+    case optionScale opts of
+        Just (x, y) -> hb_font_set_scale font x y
+        Nothing -> return ()
+
+createFontWithOptions :: FontOptions -> Face -> Font
+createFontWithOptions opts fce = unsafePerformIO $ do
+    font <- withForeignPtr fce $ hb_font_create
+    _setFontOptions font opts
+    hb_font_make_immutable font
+    Conc.newForeignPtr font $ hb_font_destroy font
+
+ftCreateFontWithOptions :: FontOptions -> FT_Face -> Font
+ftCreateFontWithOptions opts fce = unsafePerformIO $ do
+    font <- hb_ft_font_create_referenced fce
+    _setFontOptions font opts
+    hb_font_make_immutable font
+    newForeignPtr hb_font_destroy' font
+
+foreign import ccall "hb_font_set_ppem" hb_font_set_ppem :: Font_ -> Word -> Word -> IO ()
+foreign import ccall "hb_font_set_ptem" hb_font_set_ptem :: Font_ -> Float -> IO ()
+foreign import ccall "hb_font_set_scale" hb_font_set_scale :: Font_ -> Int -> Int -> IO ()
+
+-- Defer implementation of other functions...
+
+---
+
+type Blob = ForeignPtr Blob'
+data Blob'
+type Blob_ = Ptr Blob'
+
+foreign import ccall "hb_blob_create" hb_blob_create :: Ptr Word8 -> Int -> Int
+    -> StablePtr ByteString -> FunPtr (StablePtr ByteString -> IO ()) -> IO Blob_
+hb_MEMORY_MODE_READONLY = 1
+foreign import ccall "hb_blob_destroy" hb_blob_destroy :: Blob_ -> IO ()
+foreign import ccall "wrapper" hs_destructor
+    :: (StablePtr a -> IO ()) -> IO (FunPtr (StablePtr a -> IO ()))
+
+bs2blob bytes@(PS ptr offset length) = do
+    bytes' <- newStablePtr bytes
+    destructor <- hs_destructor freeStablePtr
+    blob <- withForeignPtr ptr $ \ptr' ->
+        hb_blob_create (plusPtr ptr' offset) (length - offset)
+            hb_MEMORY_MODE_READONLY bytes' destructor
+    Conc.newForeignPtr blob $ hb_blob_destroy blob
+
+faceFunc :: (Face_ -> a) -> (Face -> a)
+faceFunc cb fce = unsafePerformIO $ withForeignPtr fce $ return . cb
+
+fontFunc :: (Font_ -> a) -> (Font -> a)
+fontFunc cb fnt = unsafePerformIO $ withForeignPtr fnt $ return . cb
+
+data Set'
+type Set = ForeignPtr Set'
+type Set_ = Ptr Set'
+foreign import ccall "hb_set_create" hb_set_create :: IO Set_
+foreign import ccall "&hb_set_destroy" hb_set_destroy :: FunPtr (Set_ -> IO ())
+
+createSet :: IO Set
+createSet = do
+    ret <- hb_set_create
+    newForeignPtr hb_set_destroy ret
+
+foreign import ccall "hb_set_next" hb_set_next :: Set_ -> Ptr Word32 -> IO Bool
+setNext :: Set -> Word32 -> Maybe Word32
+setNext set iter = unsafePerformIO $ withForeignPtr set $ \set' -> alloca $ \iter' -> do
+    poke iter' iter
+    success <- hb_set_next set' iter'
+    if success
+    then return . Just =<< peek iter'
+    else return Nothing
+set2list :: Set -> IO [Word32]
+set2list set = return $ inner maxBound
+  where
+    inner iter | Just x <- setNext set iter = x : inner x
+        | otherwise = []
+
+faceCollectFunc :: (Face_ -> Set_ -> IO ()) -> (Face -> [Word32])
+faceCollectFunc cb fce = unsafePerformIO $ withForeignPtr fce $ \fce' -> do
+    set <- createSet
+    withForeignPtr set $ cb fce'
+    set2list set
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2022 Adrian Cochrane
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE PackageImports #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import "harfbuzz-pure" Data.Text.Glyphize
+import "harfbuzz-pure" Data.Text.Glyphize.Buffer
+import "harfbuzz-pure" Data.Text.Glyphize.Font
+
+import Control.Parallel.Strategies (parMap, rpar)
+
+import System.Environment
+import Data.ByteString.Lazy as LBS
+import Data.ByteString as BS
+import Data.ByteString.Char8 as UTF8
+
+shapeStr font word = shape font $ defaultBuffer {
+    text = Right $ LBS.fromStrict $ UTF8.pack word
+  }
+
+main :: IO ()
+main = do
+    print versionString
+    words <- getArgs
+    if Prelude.null words
+    then print $ guessSegmentProperties $ defaultBuffer { text = Right "Testing, testing"}
+    else do
+      blob <- BS.readFile "assets/Lora-Regular.ttf"
+      let font = createFont $ createFace blob 0
+      print $ parMap rpar (shapeStr font) words
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/harfbuzz-pure.cabal b/harfbuzz-pure.cabal
new file mode 100644
--- /dev/null
+++ b/harfbuzz-pure.cabal
@@ -0,0 +1,86 @@
+-- Initial harfbuzz-pure.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+-- The name of the package.
+name:                harfbuzz-pure
+
+-- The package version.  See the Haskell package versioning policy (PVP) 
+-- for standards guiding when and how versions should be incremented.
+-- https://wiki.haskell.org/Package_versioning_policy
+-- PVP summary:      +-+------- breaking API changes
+--                   | | +----- non-breaking API additions
+--                   | | | +--- code changes with no API change
+version:             0.1.0.0
+
+-- A short (one-line) description of the package.
+synopsis:            Pure-functional Harfbuzz language bindings
+
+-- A longer description of the package.
+description:         HarfBuzz is a text shaping library. Using the HarfBuzz library allows programs to convert a sequence of Unicode input into properly formatted and positioned glyph output—for any writing system and language.
+
+-- URL for the project homepage or repository.
+homepage:            https://harfbuzz.github.io/
+
+-- The license under which the package is released.
+license:             MIT
+
+-- The file containing the license text.
+license-file:        LICENSE
+
+-- The package author(s).
+author:              Adrian Cochrane
+
+-- An email address to which users can send suggestions, bug reports, and 
+-- patches.
+maintainer:          opensource@openwork.nz
+
+-- A copyright notice.
+-- copyright:           
+
+category:            Text
+
+build-type:          Simple
+
+-- Extra files to be distributed with the package, such as examples or a 
+-- README.
+extra-source-files:  ChangeLog.md
+
+-- Constraint on the version of Cabal needed to build this package.
+cabal-version:       >=1.10
+
+
+library
+  -- Modules exported by the library.
+  exposed-modules:     Data.Text.Glyphize, Data.Text.Glyphize.Buffer, Data.Text.Glyphize.Font
+  
+  -- Modules included in this library but not exported.
+  -- other-modules:       
+  
+  -- LANGUAGE extensions used by modules in this package.
+  -- other-extensions:    
+  
+  -- Other library packages from which modules are imported.
+  build-depends:       base >=4.9 && <5, freetype2 >= 0.2,
+                       bytestring >= 0.10.8.2 && < 0.12, text >= 1 && <3, utf8-light >= 0.3 && < 1
+  pkgconfig-depends:   harfbuzz
+  
+  -- Directories containing source files.
+  -- hs-source-dirs:      
+  
+  -- Base language which the package is written in.
+  default-language:    Haskell2010
+
+executable shape-text
+  -- .hs file containing the Main module
+  main-is:             Main.hs
+
+  -- Other library packages from which modules are imported
+  build-depends:       base >=4.9 && <5, harfbuzz-pure, parallel  >= 2.2 && < 4, bytestring
+
+  -- Directories containing source files.
+  hs-source-dirs:      .
+
+  -- Base language which the package is written in.
+  default-language:    Haskell2010
+
+  ghc-options: -threaded
