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'