Feature request: more flexible support for scaleable vector font formats

edited October 2016 in raylib: text
Hi Ray!

I've been wrestling a little over the last couple of days with the problem of displaying smooth crisp beautiful text at arbitrary, run-time-determined (because resolution-dependent) sizes.

The only way I've managed to get it to work so far is by using FreeType to load in the font glyph, drawing that glyph to a raylib Image a la LoadImageEx, and then loading a Texture2D from that image. The end result looks good, but the process is a bit unwieldy and relies on an additional 3rd party library, albeit an excellent one. It would be great to have more native raylib support for this, something along the lines of

LoadVectorFont([font path], &handle);
SetFontSize(handle, &size) <-- but in the way that FreeType handles it, such that there's no loss of resolution if it's a vector font
DrawText(string, handle, size)

Again, really just to allow scaling of vector fonts without loss of crispness (as happens now with TTF fonts if using native raylib functions)

As an aside, it would also be lovely to have support for Open Type fonts!

(Before finding this solution, I also tried:
* using default font and other bitmap fonts (bundled or otherwise) - no good, i like them in a vacuum but the pixelated/blocky look doesn't fit my project
* loading a TTF font through raylib - works well at the default size, but doesn't scale well. also, the default character subset does not include some of the characters I need to correctly credit my Kickstarter backers :P
* drawing a letter by hand, taking a photo of that, cleaning the photo, vectorizing the photo, converting back to png, and assembling these images into an XNA spritesheet. looks pretty good, actually, and scales ok provided the initial texture is pretty big, but not the most intuitive workflow, and it requires a 3rd party vectorizer if not using a readymade font (without the vectorization step the results don't look nearly as sharp & smooth)
* using the FreeType process described above, except drawing to a RenderTexture2D using DrawPixel. I run out of QUADS when the font size gets above 150 or so :P....tried doing a kludge where I draw to the texture over multiple frames, but the render texture seems to be getting wiped every time I call BeginTextureMode, and I can't seem to get the intermediate results to save correctly to my local static clipboard texture....anyway, this is more complex than the Image method, and the Image method works, so....)

Thanks as always!

Cheers,
Ilya

Comments

  • edited October 2016
    Here's how I'm doing it now
    (You'll need to build & install FreeType if you don't already have it if you wanna try)

    #include <ft2build.h>
    #include FT_FREETYPE_H
    #include "raylib.h"

    // FreeType loads only one glyph at a time,
    // to see other letters, change this value
    // (or modify the code to loop through an array of glyphs, etc)
    #define EXAMPLECHAR 'q'

    // rather than passing a scale param to DrawTextureEx, which blurs the image,
    // change FONTSIZE to scale the font without loss of crispness
    #define FONTSIZE 512

    // substitute any old TTF font you might have lying around
    // or if you don't have one you can get some at
    // https://fontlibrary.org/ or
    // https://www.fontsquirrel.com/
    #define FONTPATH "../fonts/fontTTF_roboto/Roboto-Regular.ttf"

    int main(){
    int screen_width = 1920;
    int screen_height = 1080;
    InitWindow(screen_width, screen_height, "wly FreeType test");

    FT_Library ft;
    // unix-style: returns 0 for success
    int error = FT_Init_FreeType( &ft );
    if (error) return;

    FT_Face ftFace;
    if (FT_New_Face(ft, FONTPATH, 0, &ftFace)) return;

    if (FT_Set_Pixel_Sizes(ftFace, 0, FONTSIZE)) return;

    int glyph_index = FT_Get_Char_Index(ftFace, EXAMPLECHAR);
    if (FT_Load_Glyph(ftFace, glyph_index, FT_LOAD_DEFAULT)) return;

    if (FT_Render_Glyph(ftFace->glyph, FT_RENDER_MODE_NORMAL)) return;

    Image glyphImage;
    glyphImage.width = ftFace->glyph->bitmap.width;
    glyphImage.height = ftFace->glyph->bitmap.rows;
    glyphImage.mipmaps = 1;
    glyphImage.format = UNCOMPRESSED_R8G8B8A8;

    // adapted from raylib's LoadImageEx
    glyphImage.data = (unsigned char *)malloc(glyphImage.width*glyphImage.height*4*sizeof(unsigned char));
    int k = 0;
    for (int i = 0; i < glyphImage.width*glyphImage.height*4; i += 4){
    ((unsigned char *)glyphImage.data)[i] = 0.0f;
    ((unsigned char *)glyphImage.data)[i+1] = 0.0f;
    ((unsigned char *)glyphImage.data)[i+2] = 0.0f;
    ((unsigned char *)glyphImage.data)[i+3] = ftFace->glyph->bitmap.buffer[k];
    k++;
    }

    Texture2D texture2D = LoadTextureFromImage(glyphImage);
    UnloadImage(glyphImage);

    while (!WindowShouldClose()){
    BeginDrawing();
    ClearBackground(RAYWHITE);

    // FreeType glyph bitmap is not actually a perfect FONTSIZE square - possibly not even close
    // but, close enough for testing purposes
    // we just wanna make sure the letter is visible
    DrawTexture(texture2D, GetScreenWidth() / 2 - FONTSIZE/2, GetScreenHeight() / 2 - FONTSIZE/2, WHITE);
    EndDrawing();
    }

    CloseWindow();

    return 0;
    }
  • edited October 2016
    ...I dunno, maybe/probably this is out of scope for a stepping-stone teaching library? One major thing that initially attracted me to raylib and that I really like about it is its compactness. I am reading the FreeType FAQ and it's very unapologetic about the library's narrow and sharp focus, its very clear sense of identity as a font service and nothing else. Surely that clarity of version helps account for its quality popularity and longevity.

    Heh, I guess what I'm saying is, I withdraw the feature request?
  • edited October 2016
    Hi wly_cdgr!

    You're right, text system is not frexible enough.

    Actually, I've been very concerned about fonts and text management since the beginning of raylib (due to my previous experience in that field, working as localization engineer in EA) and I tried to simplify that aspect as much as possible for the user... I've also kept improving the system (adding TTF and FNT support) but there is still work to do.

    I added support for AngelCode fonts (.fnt + .png) and TTF fonts on version 1.4 and I reviewed it for version 1.5, some values are hardcoded and there is really way for improvement. Explanation below:

    FreeType is an amazing library (probably the best one out there) but it's quite big and I don't want to add more dependencies to raylib, it's against raylib's philosophy. Alternatively, raylib uses internally a header-only single-file library to manage TTF fonts: stb_truetype (https://github.com/nothings/stb/blob/master/stb_truetype.h); stb_truetype it's also a great library that keeps improving every new version... and it's way simpler and smaller than FreeType.

    The process to print text in a game always work as follows:

    1) Generate an image containing the glyphs for a specific font; that image is usually generated from a vectorized version of the glyphs (TTF, OTF fonts) and generation can be done previously (to use the image as resource) or on-game loading.

    - Currently raylib support both approaches. Pre-generated image fonts (.PNG, .FNT) can be loaded the same way as on-init generated fonts (.TTF), just using the function LoadSpriteFont(); depending on the extension, raylib calls one secundary function or another... Now I see that approach is not flexible enough (well, actually, I already knew it but I though no student would notice it... :P) --> POINT to IMPROVE!

    2) Convert that image to a texture and configure sampling parameters for that texture, it means, define how the texture will be scaled when drawing to screen.

    - Currently raylib uses by default the simpler (but visually worse) scaling algorythm for all loaded textures, I took that decision to increase performance and to focus on 2D pixel-based games (also considered that most textures won't be ever scaled in game)... now I see that, considering how library has evolved, it's not the best decision any more. --> POINT to IMPROVE!

    3) Draw text on screen = draw quads with the corresponding piece of the loaded texture depending on the letter to draw.

    - Currently raylib has some predefined limits (mostly configurable):

    a) For TTF fonts, letters should be inside a limited range: 32..127
    b) TTF fonts are loaded by default at fixed 32 pixels size (horrible decision! >_<)
    c) All required glyphs must be contained and ordered inside the font texture; if one glyph is not available a blank space must be left.
    d) Maximum number or available quads for drawing (in one frame) is 4096.

    All 4 points could be reconfigured --> POINTS to IMPROVE!

    So, I'll start working on the following to make it available for raylib 1.6 (hopefully coming next month):

    1) Allow TTF loading with custom configuration: LoadSpriteFontTTF()? or GenerateFontImage()? - need to think most intuitive naming
    2) Allow texture sampling (scaling) parameters configuration: SetTextureSampling()? or just set better texture sampling than the current one by default.
    3) Allow custom font range for all font types, also allowing a wider set of characters - need to think a bit more about this one...

    As an immediate solution, I recommend you to try AngelCode fonts: https://github.com/raysan5/raylib/wiki/Using-SpriteFonts

    About OpenType fonts, that's more complicated, I depend on stb_truetype.

    About the special characters required for your Kickstarter backers (hey! I didn't know you had a Kickstarter!), which characters are they? are they inside 0..255 ASCII range?

    Wow, what a long answer... I expect to have covered all the points... :P

    Thank you very much for pointing all those issues so detailed, that's the kind of information I need from people that is actually using the library for some project... and that's the way to make raylib better.
  • edited October 2016
    About your second concern: withdraw the feature request, I say: no!

    It's true raylib is primary designed to be a stepping-stone game-programming learning library but I also tried to make the library as professional as possible, offering the users a simple way to do things but also advance features for advance users. :)
  • edited October 2016
    Hi Ray!

    I tried out AngelCode as you suggested, starting with your example text_bmfont_ttf.c, which worked from the get-go. I then downloaded BMFont and started making my own .fnt files, but it took a bit of fiddling around to get the BMFont output to interface correctly with raylib.

    These BMFont settings changes proved necessary:
    * select only the 95 characters from SPACE (32) to ~ -- anything else triggers the "unordered chars data" error (well, actually, you can leave out multiple contiguous characters at the end of that sequence....). BMFont doesn't do this by default or have a built-in way to do it, so you have to do it manually
    * change Bit Depth to 32 in Export Settings from the 8 bit default -- otherwise raylib renders all glyphs as solid rectangles

    Also, I found a crash bug:
    If you change the font size in BMFont Font Settings, but forget to increase the texture size in Export Settings to match (BMFont does not do this automatically), raylib crashes when you try to load that font.

    The AngelCode approach does provide some additional flexibility by allowing user to prebake multiple and arbitrary font sizes instead of always having to scale from 32, but since I really need a run-time solution, I'll just use your default font as a placeholder and look forward to 1.6! I won't need the final system for a few months.
  • edited October 2016
    Re: Kickstarter backer characters, everything is in 0-255 range, .....except a

    ☺︎
    (Hex Unicode [0x263A 0xFE0E])

    and a

    ༼ ༎ຶ ᴥ ༎ຶ ༽
    (0xF3C [0xF0E 0xEB6] 0x1D25 [0xF0E 0xEB6] 0xF3D)

    (That is QWOP Bear, a symbol of Babycastles, an indie game art collective in NYC)


    Re: OpenType, no big deal, honestly I don't even know why I mentioned it as for any of my purposes for the next couple years TrueType should be just fine. Just keep hearing about it's supposed to be great, but, ok, professional graphic design is not what I'm doing here.

    Re: API updates to accommodate more flexible TTF loading and smoother scaling, thank you! I'm glad you are refusing to accept my withdrawal of the feature request :)

    Thanks as always & cheers,
    Ilya

Sign In or Register to comment.