Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loading fonts from memory #199

Merged
merged 23 commits into from
Sep 11, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
32e067a
Compiles, doesn't work
klausweiss Aug 3, 2022
fac20ed
s/FontDefPath/FontDefFile/g
klausweiss Aug 3, 2022
13f55dd
Some ffi changes
klausweiss Aug 3, 2022
5cbbe4b
Rename toUString
klausweiss Aug 4, 2022
06d061a
Demo in the todo app. TO BE REVERTED
klausweiss Aug 3, 2022
ec4bf11
Alloc an unsigned char* array for font
klausweiss Aug 4, 2022
e439230
Remove unused language extensions
klausweiss Aug 4, 2022
1e4bd8b
Remove unused and sort imports
klausweiss Aug 4, 2022
d6f11a0
Use useAsCStringLen and malloc manually
klausweiss Aug 5, 2022
cf7839c
Use nanovg from klausweiss/nanovg-hs (TO BE REVERTED)
klausweiss Aug 7, 2022
9e3bac9
Use newer klausweiss/nanovg-hs (TO BE REVERTED)
klausweiss Aug 7, 2022
0ee513d
Use nanovg 0.8.1 from hackage
klausweiss Aug 16, 2022
85ce069
Revert "Demo in the todo app. TO BE REVERTED"
klausweiss Aug 21, 2022
cf68aea
Add documentation to some functions in FFI.hs
klausweiss Aug 21, 2022
ced04e2
Unused import
klausweiss Aug 21, 2022
7112d1e
Improve appFontDef docs
klausweiss Aug 21, 2022
5403752
s/appFontDefMem/appFontDefMemory
klausweiss Aug 21, 2022
7f1db12
Bump nanovg-hs to 0.8.1 in monomer.nix
klausweiss Sep 11, 2022
5cd391b
Revert "s/appFontDefMem/appFontDefMemory"
klausweiss Sep 11, 2022
375c5cb
Reference file-embed in appFontDefMem docs
klausweiss Sep 11, 2022
7e15883
s/appFontDefFile/appFontDef
klausweiss Sep 11, 2022
03d16ce
Stop using record syntax for FontDef
klausweiss Sep 11, 2022
7880379
Revert "Stop using record syntax for FontDef"
klausweiss Sep 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cbits/fontmanager.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ int fmCreateFont(FMcontext* ctx, const char* name, const char* filename)
return fonsAddFont(ctx->fs, name, filename, 0);
}

int fmCreateFontMem(FMcontext* ctx, const char* name, const char* data, int dataSize)
{
// Pointer cast, as it was hard to create a Ptr CUChar in Haskell.
// Could be why it doesn't work.
const unsigned char* udata = data;
return fonsAddFontMem(ctx->fs, name, udata, dataSize, 1, 0);
}

void fmSetScale(FMcontext* ctx, float scale) {
ctx->scale = scale;
}
Expand Down
2 changes: 2 additions & 0 deletions cbits/fontmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ FMcontext* fmInit(float dpr);

int fmCreateFont(FMcontext* ctx, const char* name, const char* filename);

int fmCreateFontMem(FMcontext* ctx, const char* name, const char* data, int dataSize);

void fmSetScale(FMcontext* ctx, float scale);

void fmFontFace(FMcontext* ctx, const char* font);
Expand Down
5 changes: 4 additions & 1 deletion examples/todo/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Main module for the 'Todo' example.
{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

module Main where

Expand All @@ -26,6 +27,8 @@ import TodoTypes

import qualified Monomer.Lens as L
import qualified Data.Text as T
import Data.FileEmbed (embedFile)
import Monomer.Main.Types (appFontDefMem)

type TodoWenv = WidgetEnv TodoModel TodoEvt
type TodoNode = WidgetNode TodoModel TodoEvt
Expand Down Expand Up @@ -248,7 +251,7 @@ main = do
appWindowIcon "./assets/images/icon.png",
appTheme customDarkTheme,
appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf",
appFontDef "Medium" "./assets/fonts/Roboto-Medium.ttf",
appFontDefMem "Medium" $(embedFile "./assets/fonts/Roboto-Medium.ttf"),
appFontDef "Bold" "./assets/fonts/Roboto-Bold.ttf",
appFontDef "Remix" "./assets/fonts/remixicon.ttf",
appInitEvent TodoInit
Expand Down
3 changes: 2 additions & 1 deletion monomer.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cabal-version: 1.12

-- This file has been generated from package.yaml by hpack version 0.34.4.
-- This file has been generated from package.yaml by hpack version 0.34.7.
--
-- see: https://github.com/sol/hpack

Expand Down Expand Up @@ -375,6 +375,7 @@ executable todo
, data-default >=0.5 && <0.8
, exceptions ==0.10.*
, extra >=1.6 && <1.9
, file-embed
, formatting >=6.0 && <8.0
, http-client >=0.6 && <0.9
, lens >=4.16 && <6
Expand Down
1 change: 1 addition & 0 deletions package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ executables:
- -threaded
dependencies:
- monomer
- file-embed

books:
main: Main.hs
Expand Down
14 changes: 12 additions & 2 deletions src/Monomer/Graphics/FFI.chs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ Based on code from cocreature's https://github.com/cocreature/nanovg-hs
module Monomer.Graphics.FFI where

import Control.Monad (forM)
import Data.ByteString (useAsCString)
import Data.ByteString (useAsCString, useAsCStringLen, ByteString)
import Data.ByteString.Char8 (useAsCStringLen, unpack)
import Data.Text (Text)
import Data.Text.Foreign (withCStringLen)
import Data.Sequence (Seq)
import Foreign
import Foreign.C (CString)
import Foreign.C (CString, castCharToCUChar, newCAStringLen)
import Foreign.C.Types
import Foreign.Marshal.Alloc
import Foreign.Ptr
Expand Down Expand Up @@ -118,6 +119,13 @@ withText t = useAsCString (T.encodeUtf8 t)
withNull :: (Ptr a -> b) -> b
withNull f = f nullPtr

allocCAString :: ByteString -> ((Ptr CChar, CInt) -> IO a) -> IO a
allocCAString bs continuation = do
-- not freeing the new string, as FMContext frees the fonts upon destruction
(ptr, len) <- newCAStringLen (unpack bs)
let args = (ptr, fromIntegral len)
continuation args

-- Common
{# pointer *FMcontext as FMContext newtype #}
deriving instance Storable FMContext
Expand All @@ -126,6 +134,8 @@ deriving instance Storable FMContext

{# fun unsafe fmCreateFont {`FMContext', withCString*`Text', withCString*`Text'} -> `Int' #}

{# fun unsafe fmCreateFontMem {`FMContext', withCString*`Text', allocCAString*`ByteString'&} -> `Int' #}

{# fun unsafe fmSetScale {`FMContext', `Double'} -> `()' #}

{# fun unsafe fmFontFace {`FMContext', withCString*`Text'} -> `()' #}
Expand Down
12 changes: 9 additions & 3 deletions src/Monomer/Graphics/FontManager.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import Monomer.Common.BasicTypes
import Monomer.Graphics.FFI
import Monomer.Graphics.Types
import Monomer.Helper (putStrLnErr)
import Control.Lens ((^.))
import Monomer.Graphics.Lens (fontName, fontPath, fontBytes)

-- | Creates a font manager instance.
makeFontManager
Expand Down Expand Up @@ -101,11 +103,15 @@ newManager ctx = FontManager {..} where
}

loadFont :: FMContext -> [Text] -> FontDef -> IO [Text]
loadFont ctx fonts (FontDef name path) = do
res <- fmCreateFont ctx name path
loadFont ctx fonts fontDef = do
res <- createFont fontDef
if res >= 0
then return $ path : fonts
then return $ name : fonts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the values inside the resulting IO [Text] weren't used and the fonts loaded from memory don't have a path identifying them, this was changed to return a list of font names instead.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! 👍

else putStrLnErr ("Failed to load font: " ++ T.unpack name) >> return fonts
where
name = fontDef ^. fontName
createFont FontDefFile{} = fmCreateFont ctx name (fontDef ^. fontPath)
createFont FontDefMem{} = fmCreateFontMem ctx name (fontDef ^. fontBytes)

setFont :: FMContext -> Double -> Font -> FontSize -> FontSpace -> IO ()
setFont ctx scale (Font name) (FontSize size) (FontSpace spaceH) = do
Expand Down
8 changes: 6 additions & 2 deletions src/Monomer/Graphics/NanoVGRenderer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,15 @@ newRenderer c rdpr envRef = Renderer {..} where
req = ImageReq name def Nothing ImageDelete []

loadFont :: VG.Context -> Set Text -> FontDef -> IO (Set Text)
loadFont c fonts (FontDef name path) = do
res <- VG.createFont c name (VG.FileName path)
loadFont c fonts fontDef = do
res <- createFont fontDef
case res of
Just{} -> return $ Set.insert name fonts
_ -> putStrLnErr ("Failed to load font: " ++ T.unpack name) >> return fonts
where
name = fontDef ^. L.fontName
createFont FontDefFile{} = VG.createFont c name (VG.FileName $ fontDef ^. L.fontPath)
createFont FontDefMem{} = VG.createFontMem c name (fontDef ^. L.fontBytes)

setFont
:: VG.Context
Expand Down
18 changes: 13 additions & 5 deletions src/Monomer/Graphics/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ Basic types for Graphics.

Angles are always expressed in degrees, not radians.
-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE Strict #-}

module Monomer.Graphics.Types where
Expand Down Expand Up @@ -45,11 +48,16 @@ data Color = Color {
instance Default Color where
def = Color 255 255 255 1.0

-- | The definition of a font.
data FontDef = FontDef {
_fntName :: !Text, -- ^ The logic name. Will be used when defining styles.
_fntPath :: !Text -- ^ The path in the filesystem.
} deriving (Eq, Show, Generic)
data FontDef
= FontDefFile
{ _fntFontName :: !Text -- ^ The logic name. Will be used when defining styles.
, _fntFontPath :: !Text -- ^ The path in the filesystem.
}
| FontDefMem
{ _fntFontName :: !Text -- ^ The logic name. Will be used when defining styles.
, _fntFontBytes :: !ByteString -- ^ The bytes of the loaded font.
}
deriving (Eq, Show, Generic)
Comment on lines +48 to +57
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to use this

data FontDef
  = FontDefFile
    { _fntName :: !Text  -- ^ The logic name. Will be used when defining styles.
    , _fntPath :: !Text  -- ^ The path in the filesystem.
    }
  | FontDefMem
    { _fntName :: !Text         -- ^ The logic name. Will be used when defining styles.
    , _fntBytes :: !ByteString  -- ^ The bytes of the loaded font.
    }
  deriving (Eq, Show, Generic)

but for reasons I don't understand (some lens stuff) I can't:

[ 21 of 104] Compiling Monomer.Graphics.Lens [Monomer.Graphics.Types changed]

.../monomer/src/Monomer/Graphics/Lens.hs:24:1: error:
    • Could not deduce (Applicative f) arising from a use of ‘pure’
      from the context: Functor f
        bound by the type signature for:
                   path :: Control.Lens.Type.Lens' FontDef Data.Text.Internal.Text
        at src/Monomer/Graphics/Lens.hs:24:1-42
      Possible fix:
        add (Applicative f) to the context of
          the type signature for:
            path :: Control.Lens.Type.Lens' FontDef Data.Text.Internal.Text
    • In the expression: pure ((FontDefMem x1_acDU) x2_acDV)
      In an equation for ‘path’:
          path _ (FontDefMem x1_acDU x2_acDV)
            = pure ((FontDefMem x1_acDU) x2_acDV)
      In the instance declaration for
        ‘HasPath FontDef Data.Text.Internal.Text’
   |
24 | makeLensesWith abbreviatedFields ''FontDef
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note this change creates traversals, not lenses, which is something to watch out for, see GHCI:

λ: FontDefMem "name" "Bytes" ^. fontPath 
""

I'm open to alternatives, but given how small these constructors are, I'm fine with leaving as is.

Copy link
Owner

@fjvallarino fjvallarino Sep 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with the naming you chose. I guess the error is caused by a previous Lens/Prism generated in https://github.com/fjvallarino/monomer/blob/main/src/Monomer/Lens.hs.


Alternatively, since this constructors are only used in a couple of places, you can use:

data FontDef
  = FontDefFile !Text !Text -- ^ Associates a font name with a filesystem path.
  | FontDefMem !Text !ByteString -- ^ Associates a font name with a font loaded in memory.
  deriving (Eq, Show, Generic)

Then, in NanoVGRenderer, you need to deconstruct:

...
createFont (FontDefFile name path) = VG.createFont c name (VG.FileName path)
createFont (FontDefMem name bytes) = VG.createFontMem c name bytes
...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I went with what you suggested. name was needed outside of the createFont function in both NanoVGRenderer and FontManager, so I hand-crafted a HasName instance for FontDef, since it can't be generated anymore due to no record syntax.


-- | The name of a loaded font.
newtype Font
Expand Down
22 changes: 19 additions & 3 deletions src/Monomer/Main/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import Monomer.Core.ThemeTypes
import Monomer.Core.WidgetTypes
import Monomer.Event.Types
import Monomer.Graphics.Types
import Data.ByteString (ByteString)

-- | Main Monomer monad.
type MonomerM s e m = (Eq s, MonadState (MonomerCtx s e) m, MonadCatch m, MonadIO m)
Expand Down Expand Up @@ -385,9 +386,24 @@ appDisableAutoScale disable = def {
Available fonts to the application. An empty list will make it impossible to
render text.
-}
appFontDef :: Text -> Text -> AppConfig e
appFontDef name path = def {
_apcFonts = [ FontDef name path ]
appFontDefFile :: Text -> Text -> AppConfig e
appFontDefFile name path = def {
_apcFonts = [ FontDefFile name path ]
}

{- |
Alias for 'appFontDefFile' for backwards compatibility.
-}
appFontDef = appFontDefFile
{-# DEPRECATED appFontDef "Use appFontDefFile directly" #-}
Copy link
Contributor Author

@klausweiss klausweiss Aug 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the changes here get through, I propose deprecating appFontDef in the light of having 2 distinct ways of loading fonts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we go down this route, we'll also need to update the examples and docs code to use appFontDefFile instead of appFontDef.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@klausweiss I'd prefer to keep the existing appFontDef name as is, and add the new memory based one as appFontDefMem. The main reason is to avoid breaking existing code. A secondary reason is this would make it work similarly to the image widget, that has image for path based images and imageMem for memory based ones.

If you prefer to keep it as appFontDefMemory, it works great for me too.

Copy link
Contributor Author

@klausweiss klausweiss Sep 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, sounds good (re deprecation).

Renamed appFontDefMemory back to appFontDefMem.


{-|
Available fonts to the application. An empty list will make it impossible to
render text.
-}
appFontDefMem :: Text -> ByteString -> AppConfig e
appFontDefMem name bytes = def {
_apcFonts = [ FontDefMem name bytes ]
}

-- | Initial theme.
Expand Down