~alcinnz/harfbuzz-pure

ac0a6b05dc90078b7bdfcc04614e7aac6deaa6a4 — Adrian Cochrane 7 months ago be56626
Add more documentation.
3 files changed, 21 insertions(+), 1 deletions(-)

M Data/Text/Glyphize/Array.hs
M Data/Text/Glyphize/Buffer.hs
M Data/Text/Glyphize/Font.hs
M Data/Text/Glyphize/Array.hs => Data/Text/Glyphize/Array.hs +14 -0
@@ 12,21 12,29 @@ import Foreign.Marshal.Array (copyArray)
import GHC.IO (IO(IO))
import GHC.Exts (realWorld#, oneShot)

-- | Clone the given array so it can be freed without the losing access to the data.
-- Uses `memcpy` so it gets very heavily optimized by the OS.
clonePtr :: Ptr a -> Int -> ForeignPtr a
clonePtr ptr l = do
    ret <- mallocForeignPtrArray l
    withForeignPtr ret $ \ptr' -> copyArray ptr' ptr l
    return ret
-- | Iterate over an array in a ForeignPtr, no matter how small or large it is.
peekLazy :: Storable a => ForeignPtr a -> Int -> [a]
peekLazy fp 0 = []
peekLazy fp n
    | n <= chunkSize = withFP $ peekEager [] n
    | otherwise = withFP $ peekEager (plusForeignPtr fp chunkSize `peekLazy` (-) n chunkSize) chunkSize
  where withFP = accursedUnutterablePerformIO . withForeignPtr fp
-- | Variation of peekArray, taking a tail to append to the decoded list.
peekEager acc 0 ptr = return acc
peekEager acc n ptr = let n' = pred n in do
    e <- peekElemOff ptr n'
    peekEager (e:acc) n' ptr
-- | How many words should be decoded by `peekLazy` & `iterateLazy`.
chunkSize = 1024 -- 4k, benchmarks seem to like it!
-- | Convert an array from C code into a Haskell list,
-- performant no matter how small or large it is.
iterateLazy :: Storable a => Ptr a -> Int -> IO [a]
iterateLazy ptr l
  | l < 0 = putStrLn ("Invalid array length: " ++ show l) >> return []


@@ 67,4 75,10 @@ iterateLazy ptr l
accursedUnutterablePerformIO :: IO a -> a
accursedUnutterablePerformIO (IO m) = case m realWorld# of (# _, r #) -> r

-- | Harfbuzz produces ~40x as much output data as its input data.
-- In many applications that input data would be a large fraction of its heap.
-- As such, unless callers are processing these results, it is usually more
-- efficient for Haskell to recompute the glyphs than to store them.
--
-- This synonym of `oneShot` is used to instruct Haskell of this fact.
noCache = oneShot

M Data/Text/Glyphize/Buffer.hs => Data/Text/Glyphize/Buffer.hs +5 -1
@@ 395,12 395,14 @@ data GlyphInfo = GlyphInfo {
    -- but only when it is safe to do the elongation without interrupting text shaping.
} deriving (Show, Read, Eq, Generic)
instance NFData GlyphInfo
-- | Decodes multiple `GlyphInfo`s from a dereferenced `Word32` list according to
-- Harfbuzz's ABI.
decodeInfos :: [Word32] -> [GlyphInfo]
decodeInfos (codepoint':cluster':mask:_:_:rest) =
    GlyphInfo codepoint' cluster' (mask `testBit` 1) (mask `testBit` 2)
        (mask `testBit` 3):decodeInfos rest
decodeInfos _ = []
-- | Decodes `Buffer'`'s glyph information array.'
-- | Decodes `Buffer'`'s glyph information array.
glyphInfos buf' = do
    arr <- throwNull $ hb_buffer_get_glyph_infos buf' nullPtr
    length <- hb_buffer_get_length buf'


@@ 428,6 430,8 @@ data GlyphPos = GlyphPos {
    -- not effect how much the line advances.
} deriving (Show, Read, Eq, Generic)
instance NFData GlyphPos
-- | Decode multiple `GlyphPos`s from a dereferenced list according to
-- Harfbuzz's ABI.
decodePositions (x_advance':y_advance':x_offset':y_offset':_:rest) =
    GlyphPos x_advance' y_advance' x_offset' y_offset':decodePositions rest
decodePositions _ = []

M Data/Text/Glyphize/Font.hs => Data/Text/Glyphize/Font.hs +2 -0
@@ 922,6 922,8 @@ bs2blob (BS bytes len) = do
    blob <- throwNull $ withForeignPtr bytes $ \bytes' ->
        hb_blob_create bytes' len hb_MEMORY_MODE_DUPLICATE nullPtr nullFunPtr
    newForeignPtr hb_blob_destroy blob
-- | Convert from a ByteString to a temporary copy of Harfbuzz's equivalent.
-- Do not use this Blob outside the passed callback.
withBlob :: ByteString -> (Blob_ -> IO a) -> IO a
withBlob (BS bytes len) cb = withForeignPtr bytes $ \bytes' -> do
    throwNull $ pure bytes'