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.
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