{-# LANGUAGE DeriveGeneric #-} module Data.Text.Glyphize.Font where import Data.ByteString.Internal (ByteString(..)) import Data.ByteString (packCStringLen) import Data.Word (Word8, Word32) import Data.Int (Int32) import FreeType.Core.Base (FT_Face) import Data.Text.Glyphize.Buffer (tag_to_string, tag_from_string, Direction, dir2int) import Control.Monad (forM) import Codec.Binary.UTF8.Light (w2c, c2w) import Data.Maybe (fromMaybe) import System.IO.Unsafe (unsafePerformIO) import Foreign.ForeignPtr (ForeignPtr(..), withForeignPtr, newForeignPtr, newForeignPtr_) import Foreign.Ptr (Ptr(..), FunPtr(..), nullPtr, nullFunPtr, castPtr) import Foreign.Marshal.Alloc (alloca, allocaBytes) import Foreign.Storable (Storable(..)) import Foreign.Storable.Generic (GStorable(..)) import GHC.Generics (Generic(..)) import Foreign.C.String (CString, withCString, withCStringLen, peekCString, peekCStringLen) ------ --- Features & Variants ------ data Feature = Feature { featTag' :: Word32, featValue :: Word32, featStart :: Word, featEnd :: Word } deriving (Read, Show, Generic) instance GStorable Feature parseFeature :: String -> Maybe Feature parseFeature str = unsafePerformIO $ withCStringLen str $ \(str', len) -> alloca $ \ret' -> do success <- hb_feature_from_string str' len ret' if success then Just <$> peek ret' else return Nothing foreign import ccall "hb_feature_from_string" hb_feature_from_string :: CString -> Int -> Ptr Feature -> IO Bool unparseFeature :: Feature -> String unparseFeature feature = unsafePerformIO $ alloca $ \feature' -> allocaBytes 128 $ \ret' -> do feature' `poke` feature hb_feature_to_string feature' ret' 128 peekCString ret' foreign import ccall "hb_feature_to_string" hb_feature_to_string :: Ptr Feature -> CString -> Word -> IO () data Variation = Variation { varTag' :: Word32, varValue :: Float } deriving (Read, Show, Generic) instance GStorable Variation parseVariation :: String -> Maybe Variation parseVariation str = unsafePerformIO $ withCStringLen str $ \(str', len) -> alloca $ \ret' -> do success <- hb_variation_from_string str' len ret' if success then Just <$> peek ret' else return Nothing foreign import ccall "hb_variation_from_string" hb_variation_from_string :: CString -> Int -> Ptr Variation -> IO Bool unparseVariation var = unsafePerformIO $ alloca $ \var' -> allocaBytes 128 $ \ret' -> do var' `poke` var hb_variation_to_string var' ret' 128 peekCString ret' foreign import ccall "hb_variation_to_string" hb_variation_to_string :: Ptr Variation -> CString -> Word -> IO () featTag = tag_to_string . featTag' varTag = tag_to_string . varTag' globalStart, globalEnd :: Word globalStart = 0 globalEnd = maxBound ------ --- Faces ------ countFace :: ByteString -> Word countFace bytes = unsafePerformIO $ do blob <- bs2blob bytes withForeignPtr blob hb_face_count foreign import ccall "hb_face_count" hb_face_count :: Blob_ -> IO Word type Face = ForeignPtr Face' type Face_ = Ptr Face' data Face' createFace :: ByteString -> Word -> Face createFace bytes index = unsafePerformIO $ do blob <- bs2blob bytes face <- withForeignPtr blob $ flip hb_face_create index hb_face_make_immutable face newForeignPtr hb_face_destroy face foreign import ccall "hb_face_create" hb_face_create :: Blob_ -> Word -> IO Face_ foreign import ccall "hb_face_make_immutable" hb_face_make_immutable :: Face_ -> IO () foreign import ccall "&hb_face_destroy" hb_face_destroy :: FunPtr (Face_ -> IO ()) ftCreateFace :: FT_Face -> Face ftCreateFace = unsafePerformIO . newForeignPtr hb_face_destroy . hb_ft_face_create_referenced foreign import ccall "hb_ft_face_create_referenced" hb_ft_face_create_referenced :: FT_Face -> Face_ emptyFace :: Face emptyFace = unsafePerformIO $ newForeignPtr hb_face_destroy hb_face_get_empty foreign import ccall "hb_face_get_empty" hb_face_get_empty :: Face_ 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 tag_to_string arr) foreign import ccall "hb_face_get_table_tags" hb_face_get_table_tags :: Face_ -> Word -> Ptr Word -> Ptr Word32 -> IO Word faceGlyphCount :: Face -> Word faceGlyphCount = faceFunc hb_face_get_glyph_count foreign import ccall "hb_face_get_glyph_count" hb_face_get_glyph_count :: Face_ -> Word faceCollectUnicodes :: Face -> [Word32] faceCollectUnicodes = faceCollectFunc hb_face_collect_unicodes foreign import ccall "hb_face_collect_unicodes" hb_face_collect_unicodes :: Face_ -> Set_ -> IO () faceCollectVarSels :: Face -> [Word32] faceCollectVarSels = faceCollectFunc hb_face_collect_variation_selectors foreign import ccall "hb_face_collect_variation_selectors" hb_face_collect_variation_selectors :: Face_ -> Set_ -> IO () faceCollectVarUnicodes :: Face -> Word32 -> [Word32] faceCollectVarUnicodes fce varSel = (faceCollectFunc inner) fce where inner a b = hb_face_collect_variation_unicodes a varSel b foreign import ccall "hb_face_collect_variation_unicodes" hb_face_collect_variation_unicodes :: Face_ -> Word32 -> Set_ -> IO () faceIndex :: Face -> Word faceIndex = faceFunc hb_face_get_index foreign import ccall "hb_face_get_index" hb_face_get_index :: Face_ -> Word -- | units-per-em faceUpem :: Face -> Word faceUpem = faceFunc hb_face_get_upem foreign import ccall "hb_face_get_upem" hb_face_get_upem :: Face_ -> Word faceBlob :: Face -> ByteString faceBlob = blob2bs . faceFunc hb_face_reference_blob foreign import ccall "hb_face_reference_blob" hb_face_reference_blob :: Face_ -> Blob_ faceTable :: Face -> String -> ByteString faceTable face tag = blob2bs $ unsafePerformIO $ withForeignPtr face $ \fce' -> do hb_face_reference_table fce' $ tag_from_string tag foreign import ccall "hb_face_reference_table" hb_face_reference_table :: Face_ -> Word32 -> IO Blob_ -- TODO Do we want setters? How to expose those? -- TODO Face builders? ------ --- Fonts ------ type Font = ForeignPtr Font' type Font_ = Ptr Font' data Font' createFont :: Face -> Font createFont fce = unsafePerformIO $ do font <- withForeignPtr fce $ hb_font_create hb_font_make_immutable font newForeignPtr hb_font_destroy 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 :: FunPtr (Font_ -> IO ()) 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_ft_font_create_referenced" hb_ft_font_create_referenced :: FT_Face -> IO Font_ emptyFont :: Font emptyFont = unsafePerformIO $ newForeignPtr hb_font_destroy hb_font_get_empty foreign import ccall "hb_font_get_empty" hb_font_get_empty :: Font_ fontFace :: Font -> Face fontFace font = unsafePerformIO $ withForeignPtr font $ \font' -> do face' <- hb_font_get_face font' newForeignPtr_ face' -- FIXME: Keep the font alive... foreign import ccall "hb_font_get_face" hb_font_get_face :: Font_ -> IO Face_ 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" hb_font_get_glyph :: Font_ -> Word32 -> Word32 -> Ptr Word32 -> IO Bool 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_advance_for_direction" hb_font_get_glyph_advance_for_direction :: Font_ -> Word32 -> Int -> Ptr Int32 -> Ptr Int32 -> IO () 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" hb_font_get_glyph_contour_point :: Font_ -> Word32 -> 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 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 data GlyphExtents = GlyphExtents { xBearing :: Word32, yBearing :: Word32, width :: Word32, height :: Word32 } deriving (Generic) instance GStorable GlyphExtents 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" hb_font_get_glyph_extents :: Font_ -> Word32 -> 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_extents_for_origin" hb_font_get_glyph_extents_for_origin :: Font_ -> Word32 -> Int -> Ptr GlyphExtents -> 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_from_name" hb_font_get_glyph_from_name :: Font_ -> CString -> Int -> Ptr Word32 -> IO Bool fontGlyphHAdvance :: Font -> Word32 -> Int32 fontGlyphHAdvance = fontFunc hb_font_get_glyph_h_advance foreign import ccall "hb_font_get_glyph_h_advance" hb_font_get_glyph_h_advance :: Font_ -> Word32 -> Int32 fontGlyphVAdvance :: Font -> Word32 -> Int32 fontGlyphVAdvance = fontFunc hb_font_get_glyph_v_advance foreign import ccall "hb_font_get_glyph_v_advance" hb_font_get_glyph_v_advance :: Font_ -> Word32 -> Int32 fontGlyphHKerning :: Font -> Word32 -> Word32 -> Int32 fontGlyphHKerning = fontFunc hb_font_get_glyph_h_kerning foreign import ccall "hb_font_get_glyph_h_kerning" hb_font_get_glyph_h_kerning :: Font_ -> Word32 -> Word32 -> Int32 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_h_origin" hb_font_get_glyph_h_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_glyph_v_origin" hb_font_get_glyph_v_origin :: Font_ -> Word32 -> Ptr Int32 -> Ptr Int32 -> IO Bool fontGlyphKerningForDir :: Font -> Word32 -> Word32 -> Maybe Direction -> (Int32, Int32) fontGlyphKerningForDir font a b dir = unsafePerformIO $ withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do hb_font_get_glyph_kerning_for_direction font' a b (dir2int dir) x' y' x <- peek x' y <- peek y' return (x, y) 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 () fontGlyphName :: Font -> Word32 -> Maybe String fontGlyphName a b = fontGlyphName_ a b 32 fontGlyphName_ :: Font -> Word32 -> Int -> Maybe String fontGlyphName_ font glyph size = unsafePerformIO $ withForeignPtr font $ \font' -> allocaBytes size $ \name' -> do success <- hb_font_get_glyph_name font' glyph name' (toEnum size) if success then Just <$> peekCStringLen (name', size) else return Nothing foreign import ccall "hb_font_get_glyph_name" hb_font_get_glyph_name :: Font_ -> Word32 -> CString -> Word32 -> IO Bool 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_origin_for_direction" hb_font_get_glyph_origin_for_direction :: Font_ -> Word32 -> Int -> Ptr Int32 -> Ptr Int32 -> IO () -- Skipping Draw methodtables, easier to use FreeType for that. fontNominalGlyph :: Font -> Char -> Maybe Word32 fontNominalGlyph font c = unsafePerformIO $ withForeignPtr font $ \font' -> alloca $ \glyph' -> do success <- hb_font_get_nominal_glyph font' (c2w c) glyph' if success then Just <$> peek glyph' else return Nothing foreign import ccall "hb_font_get_nominal_glyph" hb_font_get_nominal_glyph :: Font_ -> Word32 -> Ptr Word32 -> IO Bool fontPPEm :: Font -> (Word32, Word32) fontPPEm font = unsafePerformIO $ withForeignPtr font $ \font' -> alloca $ \x' -> alloca $ \y' -> do hb_font_get_ppem font' x' y' x <- peek x' y <- peek y' return (x, y) foreign import ccall "hb_font_get_ppem" hb_font_get_ppem :: Font_ -> Ptr Word32 -> Ptr Word32 -> IO () fontPtEm :: Font -> Float fontPtEm = fontFunc hb_font_get_ptem foreign import ccall "hb_font_get_ptem" hb_font_get_ptem :: Font_ -> Float 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_scale" hb_font_get_scale :: Font_ -> Ptr Int -> Ptr Int -> IO () {-fontSyntheticSlant :: Font -> Float fontSyntheticSlant = fontFunc hb_font_get_synthetic_slant foreign import ccall "hb_font_get_synthetic_slant" hb_font_get_synthetic_slant :: Font_ -> Float-} 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_variation_glyph" hb_font_get_variation_glyph :: Font_ -> Word32 -> Word32 -> Ptr Word32 -> IO Bool {-fontVarCoordsDesign :: Font -> [Float] fontVarCoordsDesign font = unsafePerformIO $ withForeignPtr font $ \font' -> alloca $ \length' -> do arr <- hb_font_get_var_coords_design font' length' length <- peek length' forM [0..fromEnum length-1] $ peekElemOff arr foreign import ccall "hb_font_get_var_coords_design" hb_font_get_var_coords_design :: Font_ -> Ptr Word -> IO (Ptr Float)-} 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_get_var_coords_normalized" hb_font_get_var_coords_normalized :: Font_ -> Ptr Word -> IO (Ptr Int) 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_from_string" hb_font_glyph_from_string :: Font_ -> CString -> Int -> Ptr Word32 -> IO Bool 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 foreign import ccall "hb_font_glyph_to_string" hb_font_glyph_to_string :: Font_ -> Word32 -> CString -> Int -> IO () data FontExtents = FontExtents { ascender :: Int32, descender :: Int32, lineGap :: Int32 } deriving (Generic) instance GStorable FontExtents 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_extents_for_direction" hb_font_get_extents_for_direction :: Font_ -> Int -> Ptr FontExtents -> IO () 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_h_extents" hb_font_get_h_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 foreign import ccall "hb_font_get_v_extents" hb_font_get_v_extents :: Font_ -> Ptr FontExtents -> IO Bool -- Not exposing the Font Funcs API as being extremely imparative with little value to callers. ------ --- Configurable fonts ------ 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 () 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 () createFontWithOptions :: FontOptions -> Face -> Font createFontWithOptions opts fce = unsafePerformIO $ do font <- withForeignPtr fce $ hb_font_create _setFontOptions font opts hb_font_make_immutable font newForeignPtr 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 ------ --- Internal ------ type Blob = ForeignPtr Blob' data Blob' type Blob_ = Ptr Blob' bs2blob :: ByteString -> IO Blob bs2blob (BS bytes len) = do blob <- withForeignPtr bytes $ \bytes' -> hb_blob_create bytes' len hb_MEMORY_MODE_DUPLICATE nullPtr nullFunPtr newForeignPtr hb_blob_destroy blob foreign import ccall "hb_blob_create" hb_blob_create :: Ptr Word8 -> Int -> Int -> Ptr () -> FunPtr (Ptr () -> IO ()) -> IO Blob_ hb_MEMORY_MODE_DUPLICATE = 0 foreign import ccall "&hb_blob_destroy" hb_blob_destroy :: FunPtr (Blob_ -> IO ()) blob2bs :: Blob_ -> ByteString blob2bs blob = unsafePerformIO $ alloca $ \length' -> do dat <- hb_blob_get_data blob length' length <- peek length' ret <- packCStringLen (dat, fromIntegral length) hb_blob_destroy' blob return ret foreign import ccall "hb_blob_get_data" hb_blob_get_data :: Blob_ -> Ptr Word -> IO CString foreign import ccall "hb_blob_destroy" hb_blob_destroy' :: Blob_ -> IO () 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 faceCollectFunc :: (Face_ -> Set_ -> IO ()) -> (Face -> [Word32]) faceCollectFunc cb fce = unsafePerformIO $ withForeignPtr fce $ \fce' -> do set <- createSet withForeignPtr set $ cb fce' set2list set data Set' type Set = ForeignPtr Set' type Set_ = Ptr Set' createSet :: IO Set createSet = do ret <- hb_set_create newForeignPtr hb_set_destroy ret foreign import ccall "hb_set_create" hb_set_create :: IO Set_ foreign import ccall "&hb_set_destroy" hb_set_destroy :: FunPtr (Set_ -> IO ()) 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 foreign import ccall "hb_set_next" hb_set_next :: Set_ -> Ptr Word32 -> IO Bool set2list :: Set -> IO [Word32] set2list set = return $ inner maxBound where inner iter | Just x <- setNext set iter = x : inner x | otherwise = []