~alcinnz/harfbuzz-pure

5b7beb1fa0df5d230a736b8a7eda0fd0d66ef854 — Adrian Cochrane 2 years ago 728c810
Add, test, & fix shaping function.
M Data/Text/Glyphize.hs => Data/Text/Glyphize.hs +12 -2
@@ 3,9 3,19 @@ module Data.Text.Glyphize where
import Data.Text.Glyphize.Buffer
import Data.Text.Glyphize.Font

-- Don't think I'll implement shapeplans at this stage...
-- shape :: Font -> Buffer -> [(GlyphInfo, GlyphPos)]
import Foreign.Ptr
import Foreign.ForeignPtr
import System.IO.Unsafe (unsafePerformIO)

foreign import ccall "hb_shape" hb_shape :: Font_ -> Buffer_ -> Ptr () -> Int -> IO ()
shape :: Font -> Buffer -> [(GlyphInfo, GlyphPos)]
shape font buf = unsafePerformIO $ do
  buf_ <- freeze' buf
  withForeignPtr font $ \font' -> withForeignPtr buf_ $ \buf' ->
    hb_shape font' buf' nullPtr 0
  infos <- glyphInfos' buf_
  pos <- glyphsPos' buf_
  return $ zip infos pos
-- Defer implementing font features...

-- version :: (Int, Int, Int)

M Data/Text/Glyphize/Buffer.hs => Data/Text/Glyphize/Buffer.hs +37 -33
@@ 102,14 102,13 @@ data ClusterLevel = ClusterMonotoneGraphemes | ClusterMonotoneChars | ClusterCha
data GlyphInfo = GlyphInfo {
    codepoint :: Word32,
    cluster :: Word32
}
} deriving (Show, Read, Eq)
instance Storable GlyphInfo where
    sizeOf (GlyphInfo a b) = sizeOf a + sizeOf b
    alignment (GlyphInfo a b) = alignment a
    sizeOf _ = 2 * sizeOf (undefined :: Word32)
    alignment _ = alignment (undefined :: Word32)
    peek p = do
        q <- return $ castPtr p
        codepoint' <- peek q
        cluster' <- peekElemOff q 1
        codepoint' <- peek $ castPtr p
        cluster' <- peekElemOff (castPtr p) 1
        return $ GlyphInfo codepoint' cluster'
    poke p (GlyphInfo a b) = do
        q <- return $ castPtr p


@@ 119,10 118,10 @@ instance Storable GlyphInfo where
data GlyphPos = GlyphPos {
    x_advance :: Word32, y_advance :: Word32,
    x_offset :: Word32, y_offset :: Word32
}
} deriving (Show, Read, Eq)
instance Storable GlyphPos where
    sizeOf (GlyphPos a _ _ _) = 4 * sizeOf a
    alignment (GlyphPos a _ _ _) = alignment a
    sizeOf _ = 4 * sizeOf (undefined :: Word32)
    alignment _ = alignment (undefined :: Word32)
    peek p = do
        q <- return $ castPtr p
        xa <- peek q


@@ 167,6 166,7 @@ dirVertical dir = dir `Prelude.elem` [DirTTB, DirBTT]

type Buffer' = ForeignPtr Buffer''
data Buffer''
type Buffer_ = Ptr Buffer''

freeze = unsafePerformIO . freeze'
freeze' buf = do


@@ 213,12 213,16 @@ glyphInfos' :: Buffer' -> IO [GlyphInfo]
glyphInfos' buf' = alloca $ \length' -> do
    arr <- withForeignPtr buf' $ \buf'' -> hb_buffer_get_glyph_infos buf'' length'
    length <- peek length'
    forM [0..length - 1] $ peekElemOff arr
    if length == 0
    then return []
    else forM [0..length - 1] $ peekElemOff arr
glyphsPos = unsafePerformIO . glyphsPos'
glyphsPos' buf' = alloca $ \length' -> do
      arr <- withForeignPtr buf' $ \buf'' -> hb_buffer_get_glyph_positions buf'' length'
      length <- peek length'
      forM [0..length-1] $ peekElemOff arr
      if length == 0
      then return []
      else forM [0..length-1] $ peekElemOff arr

thaw :: Buffer' -> Buffer
thaw = unsafePerformIO . thaw'


@@ 255,18 259,18 @@ thaw' buf' = do
        invisibleGlyph = w2c invisibleGlyph', replacementCodepoint = w2c replacementCodepoint'
      }

foreign import ccall "hb_buffer_create" hb_buffer_create :: IO (Ptr Buffer'')
foreign import ccall "&hb_buffer_destroy" hb_buffer_destroy :: FunPtr (Ptr Buffer'' -> IO ())
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
    :: Ptr Buffer'' -> Ptr Word8 -> Int -> Int -> Int -> IO ()
    :: 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
    :: Ptr Buffer'' -> Int -> IO ()
    :: Buffer_ -> Int -> IO ()
foreign import ccall "hb_buffer_set_direction" hb_buffer_set_direction
    :: Ptr Buffer'' -> Int -> IO ()
    :: 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 [


@@ 296,7 300,7 @@ hb_script_from_string str = hb_tag_from_string $ case titlecase str of
    'S':'y':'r':'n':_ -> "Syrc"
hb_script_from_txt txt = hb_script_from_string $ Short.toString txt
foreign import ccall "hb_buffer_set_script" hb_buffer_set_script
    :: Ptr Buffer'' -> Word32 -> IO ()
    :: 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


@@ 305,22 309,22 @@ hb_language_from_txt txt = let Strict.PS ptr offset size = toByteString txt
    in withForeignPtr ptr $ \ptr' -> return $
        hb_language_from_string (plusPtr ptr' offset) (size - offset)
foreign import ccall "hb_buffer_set_language" hb_buffer_set_language
    :: Ptr Buffer'' -> Int -> IO ()
foreign import ccall "hb_buffer_set_flags" hb_buffer_set_flags :: Ptr Buffer'' -> Int -> IO ()
    :: 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
    :: Ptr Buffer'' -> Int -> IO ()
    :: Buffer_ -> Int -> IO ()
foreign import ccall "hb_buffer_set_invisible_glyph" hb_buffer_set_invisible_glyph
    :: Ptr Buffer'' -> Word32 -> IO ()
    :: Buffer_ -> Word32 -> IO ()
foreign import ccall "hb_buffer_set_replacement_codepoint" hb_buffer_set_replacement_codepoint
    :: Ptr Buffer'' -> Word32 -> IO ()
    :: Buffer_ -> Word32 -> IO ()
foreign import ccall "hb_buffer_guess_segment_properties" hb_buffer_guess_segment_properties
    :: Ptr Buffer'' -> IO ()
    :: Buffer_ -> IO ()


foreign import ccall "hb_buffer_get_content_type" hb_buffer_get_content_type
    :: Ptr Buffer'' -> IO Int
foreign import ccall "hb_buffer_get_direction" hb_buffer_get_direction :: Ptr Buffer'' -> IO Int
foreign import ccall "hb_buffer_get_script" hb_buffer_get_script :: Ptr Buffer'' -> IO Word32
    :: 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),


@@ 328,16 332,16 @@ hb_tag_to_string tag = [
    w2c (shiftR tag 8 .&. 0x7),
    w2c (shiftR tag 0 .&. 0x7)
  ]
foreign import ccall "hb_buffer_get_language" hb_buffer_get_language :: Ptr Buffer'' -> IO (Ptr ())
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 :: Ptr Buffer'' -> IO Int
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
    :: Ptr Buffer'' -> IO Int
    :: Buffer_ -> IO Int
foreign import ccall "hb_buffer_get_glyph_infos" hb_buffer_get_glyph_infos
    :: Ptr Buffer'' -> Ptr Int -> IO (Ptr GlyphInfo)
    :: Buffer_ -> Ptr Int -> IO (Ptr GlyphInfo)
foreign import ccall "hb_buffer_get_glyph_positions" hb_buffer_get_glyph_positions
    :: Ptr Buffer'' -> Ptr Int -> IO (Ptr GlyphPos)
    :: Buffer_ -> Ptr Int -> IO (Ptr GlyphPos)
foreign import ccall "hb_buffer_get_invisible_glyph" hb_buffer_get_invisible_glyph
    :: Ptr Buffer'' -> IO Word32
    :: Buffer_ -> IO Word32
foreign import ccall "hb_buffer_get_replacement_codepoint" hb_buffer_get_replacement_codepoint
    :: Ptr Buffer'' -> IO Word32
    :: Buffer_ -> IO Word32

M Data/Text/Glyphize/Font.hs => Data/Text/Glyphize/Font.hs +24 -54
@@ 3,13 3,14 @@ module Data.Text.Glyphize.Font where

import Data.ByteString
import Data.Text.Short
--import FreeType.Core.Base
import FreeType.Core.Base
import Data.Text.Glyphize.Buffer (Direction(..), dir2int)

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


@@ 39,11 40,11 @@ createFace bytes index = unsafePerformIO $ do
    face <- withForeignPtr blob $ flip hb_face_create index
    newForeignPtr 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_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


@@ 77,13 78,13 @@ createFont fce = unsafePerformIO $ do
    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 Face_
-- ftCreateFont :: FT_Face -> IO Font
-- ftCreateFont fce = unsafePerformIO $ do
--    font <- hb_ft_font_create_referenced
--    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_
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


@@ 297,30 298,6 @@ fontScale font = unsafePerformIO $
        y <- peek y'
        return (x, y)

foreign import ccall "hb_font_get_synthetic_slant" hb_font_get_synthetic_slant
    :: Font_ -> Float
fontSynthSlant :: Font -> Float
fontSynthSlant = fontFunc hb_font_get_synthetic_slant

foreign import ccall "hb_font_get_variance_glyph" hb_font_get_variance_glyph
    :: Font_ -> Word32 -> Word32 -> Ptr Word32 -> IO Bool
fontVarianceGlyph :: Font -> Word32 -> Word32 -> Maybe Word32
fontVarianceGlyph font glyph1 glyph2 = unsafePerformIO $
    withForeignPtr font $ \font' -> alloca $ \ret -> do
        success <- hb_font_get_variance_glyph font' glyph1 glyph2 ret
        if success
        then return . Just =<< peek ret
        else return Nothing

foreign import ccall "hb_font_get_var_coords_design" hb_font_get_var_coords_design
    :: Font_ -> Ptr Int -> IO (Ptr Float)
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..length-1] $ peekElemOff arr

foreign import ccall "hb_font_get_var_coords_normalized"
    hb_font_get_var_coords_normalized :: Font_ -> Ptr Int -> IO (Ptr Int)
fontVarCoordsNormalized :: Font -> [Int]


@@ 355,13 332,11 @@ fontGlyph2Str font glyph length = unsafePerformIO $
data FontOptions = FontOptions {
    optionPPEm :: Maybe (Int, Int),
    optionPtEm :: Maybe Float,
    optionScale :: Maybe (Int, Int),
    optionSynthSlant :: Maybe Float
    optionScale :: Maybe (Int, Int)
}
defaultFontOptions = FontOptions {
    optionPPEm = Nothing, optionPtEm = Nothing,
    optionScale = Nothing,
    optionSynthSlant = Nothing
    optionScale = Nothing
}

_setFontOptions font opts = do


@@ 374,9 349,6 @@ _setFontOptions font opts = do
    case optionScale opts of
        Just (x, y) -> hb_font_set_scale font x y
        Nothing -> return ()
    case optionSynthSlant opts of
        Just slant -> hb_font_set_synthetic_slant font slant
        Nothing -> return ()

createFontWithOptions :: FontOptions -> Face -> Font
createFontWithOptions opts fce = unsafePerformIO $ do


@@ 385,18 357,16 @@ createFontWithOptions opts fce = unsafePerformIO $ do
    hb_font_make_immutable font
    newForeignPtr hb_font_destroy font

--ftCreateFontWithOptiosn :: FontOptions -> FT_Face -> Font
--ftCreateFontWithOptions opts fce = unsafePerformIO $ do
--    font <- hb_ft_font_create_referenced
--    _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

foreign import ccall "hb_font_set_ppem" hb_font_set_ppem :: Font_ -> Int -> Int -> 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 ()
foreign import ccall "hb_font_set_synthetic_slant" hb_font_set_synthetic_slant
    :: Font_ -> Float -> IO ()

-- Defer implementation of other functions...



@@ 409,7 379,7 @@ 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 :: FunPtr (Blob_ -> IO ())
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 ()))



@@ 419,7 389,7 @@ bs2blob bytes@(PS ptr offset length) = do
    blob <- withForeignPtr ptr $ \ptr' ->
        hb_blob_create (plusPtr ptr' offset) (length - offset)
            hb_MEMORY_MODE_READONLY bytes' destructor
    newForeignPtr hb_blob_destroy blob
    Conc.newForeignPtr blob $ hb_blob_destroy blob

faceFunc :: (Face_ -> a) -> (Face -> a)
faceFunc cb fce = unsafePerformIO $ withForeignPtr fce $ return . cb

M Main.hs => Main.hs +17 -4
@@ 1,12 1,25 @@
{-# 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 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
    buf' <- freeze' defaultBuffer
    buf <- thaw' buf'
    print buf
    -- TODO test I can shape text!
    print $ guessSegmentProperties $ defaultBuffer { text = Right "Testing, testing"}

    words <- getArgs
    blob <- BS.readFile "assets/Lora-Regular.ttf"
    let font = createFont $ createFace blob 0
    print $ Prelude.map (shapeStr font) words

M harfbuzz-pure.cabal => harfbuzz-pure.cabal +1 -1
@@ 75,7 75,7 @@ executable shape-text
  main-is:             Main.hs

  -- Other library packages from which modules are imported
  build-depends:       base >=4.9 && <5, harfbuzz-pure
  build-depends:       base >=4.9 && <5, harfbuzz-pure, parallel, bytestring

  -- Directories containing source files.
  hs-source-dirs:      .