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 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. 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 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_ return $ zip infos pos 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