Text
Text rendering is more complex than simple shapes because a single character may need to draw hundreds of triangles based on its shape.
Modern game engines mainly use one of the following techniques to render text (https://stackoverflow.com/questions/25956272/better-quality-text-in-webgl):
- Geometry Meshes: draw triangles
- Bitmap Font: pre-generated bitmap font images
- SDF: pre-generated image, but better quality
The first one is to generate the shapes of the font at runtime while later two require a preprocessed image.
Unity's TextMesh Pro uses SDF method to render text (source).
Elm REGL uses Multi-channel signed distance (MSDF) to render text. It has better effects than SDF font.
Elm REGL JS with builtin font has consolas font built in, users could directly use this font.
Textbox
If you only need to render several characters with no style or multi-line demanding, you could use the simpler text API textbox.
{-| Render a textbox.
-}
textbox : ( Float, Float ) -> Float -> String -> String -> Color -> Renderable
textbox ( x, y ) size text font color =
...
The parameter's meaning is what the name suggests. The font name is what you defined by allFont in src/Lib/Resources.elm.
Textbox Pro
However, if you want to render multi-line paragraph or uses bold or italic styles, you need to use a more advanced API: textboxPro:
{-| Full TextBox options.
- `fonts`: A prioritized list of font names (e.g. ["Roboto", "sans-serif"]).
- `text`: The actual text content to render.
- `size`: The font size in virtual canvas size.
- `color`: The color of the text.
- `wordBreak`: Whether long words should break across lines (`True`) or overflow (`False`).
- `thickness`: Optional stroke thickness for the text (if supported by the renderer).
- `italic`: Optional italic shift.
- `width`: Optional maximum width of the text box. Used to wrap text.
- `lineHeight`: Optional line height, as a multiplier of font size.
- `wordSpacing`: Optional spacing between words, in spaces.
- `align`: Optional horizontal alignment of text (`"left"`, `"center"`, `"right"`).
- `tabSize`: Optional width of a tab character, in spaces.
- `valign`: Optional vertical alignment of text block (`"top"`, `"center"`, `"bottom"`).
- `letterSpacing`: Optional spacing between letters, in virtual canvas size.
-}
type alias TextBoxOption =
{ fonts : List String
, text : String
, size : Float
, color : Color
, wordBreak : Bool
, thickness : Maybe Float
, italic : Maybe Float
, width : Maybe Float
, lineHeight : Maybe Float
, wordSpacing : Maybe Float
, align : Maybe String
, tabSize : Maybe Float
, valign : Maybe String
, letterSpacing : Maybe Float
}
{-| Default TextBox options.
-}
defaultTextBoxOption : TextBoxOption
defaultTextBoxOption =
{ fonts = [ "consolas" ]
, text = ""
, size = 24
, color = Color.black
, wordBreak = False
, thickness = Nothing
, italic = Nothing
, width = Nothing
, lineHeight = Nothing
, wordSpacing = Nothing
, align = Nothing
, tabSize = Nothing
, valign = Nothing
, letterSpacing = Nothing
}
{-| Render a textbox with more options.
-}
textboxPro : ( Float, Float ) -> TextBoxOption -> Renderable
textboxPro ( x, y ) opt =
...
Here thickness and italic are floats so users could specify how much they want to apply that style. The effect is done through shaders and linear transformation so it is not as accurate as the font's own variants.
Multi-line rendering can be enabled by using Just <width> in the width field. Nothing means infinity length.
There is also a centered textbox command:
{-| Render a textbox, centered.
-}
textboxCentered : ( Float, Float ) -> Float -> String -> String -> Color -> Renderable
textboxCentered ( x, y ) size text font color =
Custom Font
Elm REGL only recognizes the configuration file generated by msdf-bmfont-xml.
There's also an online generator but we recommend you to run the tool locally to have better results.
First install this tool using your node package manager, such as pnpm:
pnpm install msdf-bmfont-xml -g
To generate a font asset, first prepare you font TTF file, e.g., a.ttf.
Then, run the following command in your messenger project to import the font
messenger font <Your Font File> -n <Name>
The name option could be any name you want to reference in messenger.
By default, the generator uses ASCII characters. If you are using non-ASCII characters, you need to prepare a text file indicating all the characters you want to include, and use -i <your file.txt> to specify the charset.
If you need to minimize the asset size, you could consider:
- Decrease the font size (
-s). Default value is 40, which may be too large if you only need to render small texts. - Decrease distance range for SDF (
--range). The default value is 4.
Decrease font size or distance range may cause strange shapes if you render texts with large size.
After preparing the font assets, import them to your project at src/Lib/Resources.elm:
allFont : ResourceDefs
allFont =
[ ( "firacode", FontRes "assets/fonts/firacode.png" "assets/fonts/firacode.json" )
]
Include your font image and JSON file like above. Then you should be able to render text using name "firacode".
Manual Font Asset Creation
You could also directly use msdf-bmfont tool to create font asset. Example:
msdf-bmfont --smart-size --pot -d 2 -f json a.ttf
You could run msdf-bmfont --help to see detailed explanation of the options.
It should generate a.png and a.json. You should put those two files to your asset folder.
Multiple Font in One Texture (MFOT) and Fallback Font
It is common that some characters may be missing from a font, especially non-ascii characters. In that case, users may want to use multiple font in one texture feature.
This feature enables users to use different font in a single draw command (e.g., textboxMF). This is especially benefinial for using fallback font.
Elm-regl provides several functions to support Multiple Font (MF) textbox rendering. The simplest one is textboxMF:
{-| Render a textbox with multiple fonts.
-}
textboxMF : ( Float, Float ) -> Float -> String -> List String -> Color -> Renderable
textboxMF ( x, y ) size text fonts color =
The fonts option is the list of font that you want to use when rendering the text, whose priority descends. For example, if the fonts is [ "a", "b" ], and my text is "x y". "x" is only available in font a while "y" is only available in font "b" and " " is available in both fonts. The renderer will use font a to draw "x" and " ", and use font b to draw "y".
To use multiple fonts, users must specify two fonts in allFont. The fonts in all multi-font commands must have the same texture (otherwise they will not be able to drawn using the same texture). So the following configuration is valid:
allFont : ResourceDefs
allFont =
[ ( "a", FontRes "assets/a.png" "assets/a.json" ),
( "b", FontRes "assets/a.png" "assets/b.json" )
]
while this is invalid:
allFont : ResourceDefs
allFont =
[ ( "a", FontRes "assets/a.png" "assets/a.json" ),
( "b", FontRes "assets/b.png" "assets/b.json" )
]
But how to make a texture that contains characters from multiple fonts?
Messenger provides this feature in the font command.
It allows you to input multiple font files, for example:
messenger font a.ttf -n a -s 36 -i a.txt b.ttf -n b -i b.txt c.ttf -n c
This command will import three TTF files and combine them into one single texture. The font a will have size 36 with charset from a.txt, font b will have charset from b.txt. You may use different charset for each font.
The --range argument is applied globally on one texture.