ColleagueRiley / RFont

Simple-to-use single header modular font rendering library written in C.
zlib License
25 stars 2 forks source link

A list of errors and suggestions #7

Closed EimaMei closed 3 months ago

EimaMei commented 4 months ago

Introduction

This is a compilation of issues that I've noticed and had to deal with when developing siliapp and siliui. I bring this up because the text rendering for those libraries were partially based on RFont and as such, said mistakes in my libraries also exist within RFont.

Issue 1: No way of getting the correct height for text

Description

This is quite a huge issue in regards to using RFont for UIs. RFont assumes that the specified user's size is equivalent to the actual total height of the text, which is often not true for both English and especially international text. This can either result in A) some characters looking slightly awkward and too cramped or B) a very noticeable misalignment to appear when the text is suppose to be centered in a larger object.

Solution

RFont needs to do 2 things:

Currently RFont sets every newlines' vertical advancement to the user-specified size, which is only partially correct. The formula to calculate the actual line height is:

f32 newline = font.size * (-descent * font.scale)

descent is available when RFont is calculating font.scale, so implementing this wouldn't be difficult. Note that the newline should then be scaled down to the user-specified size for it to work properly.

To make sure that the issues of UI misalignment or cropping text doesn't appear, it is advisable to also provide the full height of the text alongside its width. It won't be too difficult to calculate it as all it would be is scaledNewline * (numOfNewLines + 1).

PS I've provided two screenshots of how long a newline should be using the Helvetica font, which were taken from the Kate text editor as well as from my siliapp library.

Code to reproduce the bug

void drawRectangle(RGFW_rect rect, int windowWidth, int windowHeight) {
    float x1 = 2.0f * rect.x / windowWidth - 1.0f;
    float y1 = 1.0f - 2.0f * rect.y / windowHeight;
    float x2 = 2.0f * (rect.x + rect.w) / windowWidth - 1.0f;
    float y2 = 1.0f - 2.0f * (rect.y + rect.h) / windowHeight;

    rglColor4f(0, 0, 0, 1);
    rglBegin(RGL_TRIANGLES_2D);
        rglVertex2f(x1, y1);
        rglVertex2f(x1, y2);
        rglVertex2f(x2, y2);

        rglVertex2f(x1, y1);
        rglVertex2f(x2, y2);
        rglVertex2f(x2, y1);
    rglEnd();
}
...
drawRectangle(RGFW_RECT(0, 0, 300, 60), win->r.w, win->r.h);
rglRenderBatch();
RFont_set_color(0.0f, 1.0f, 0, 1.0f);
RFont_draw_text(font, "jjabcdefghijklmnopqrstuvwxyz\nŠ", 0, 0, 60);

Screenshots

Bug

image

Expected behaviour

image image

Extras (Citation)

Issue 2: Advance width for space and tab

Description

According to the TrueType font specification, the space and horizontal tabulation are required to be set to a glyph with a positive advance:

  1. Each of the following characters must map to a glyph with no contours and positive advance width: 0x0009 HORIZONTAL TABULATION ... 0x0020 SPACE ...
  2. The following groups of characters must have the same width 0x0009 (HORIZONTAL TABULATION) and 0x0020 (SPACE)

Currently RFont bases the space based on the user-specified size divided by four, which might not look good for every font.

Solution:

Implement the correct advance width for spaces and tabs when detected in a string.

Extras (Citation)

Suggestion 1: A small improvement for RFont_draw_text_len

Description

RFont_draw_text_len has a rather convoluted loop condition to take account the length if it isn't 0

   char* str;

   for (str = (char*)text; (len == 0 || (size_t)(str - text) < len) && *str; str++) {
         if (*str == '\n') {
         x = startX;
         y += size;
         continue;
      }

      if (*str == ' ' || *str == '\t') {
         x += (size / 4);
         continue;
      }
      ...

This works, but clearly is less than ideal. I would suggest that instead of this you would transform the start of the for loop into:

    const char* str = text;
    while ((str - text) < len) {
        char x = *str;
        str += 1;

        switch (x)  {
            case '\0': { ...; break; }
            case '\n': { ...; break; }
            case '\t':
            case ' ': { ...; break; }
        }
        ...
    }

And for the RFont_draw_text/RFont_draw_text_spacing functions, you would replace 0 with USIZE_MAX or some other equivalent that would give you the maximum value of size_t (((size_t)0 - 1) could work, but that's stingy). The ending result would be this:

size_t RFont_draw_text(RFont_font* font, const char* text, float x, float y, u32 size) {
   return RFont_draw_text_len(font, text, USIZE_MAX, x, y, size, 0.0f);
}

size_t RFont_draw_text_spacing(RFont_font* font, const char* text, float x, float y, u32 size, float spacing) {
   return RFont_draw_text_len(font, text, USIZE_MAX, x, y, size, spacing);
}

These few changes makes the code more clean and slightly more efficient, while also retaining the original intent in a different way.

PS This suggestion also applies to RFont_text_width.

ColleagueRiley commented 3 months ago

Most of these should be solved with the lastest commit

EimaMei commented 3 months ago

Git cloning and then trying to run make results in a compilation error.

[eimamei@archlinux ~]$ git clone https://github.com/ColleagueRiley/RFont
...
[eimamei@archlinux ~]$ cd RFont/example
[eimamei@archlinux example]$ make
make gl
make[1]: Entering directory '/home/eimamei/RFont/example'
gcc -Wall -Werror -Wstrict-prototypes -Wextra main.c -I./include -lXrandr -lX11 -lm -lGL -I./ext -I../ -D RFONT_RENDER_LEGACY -Wall -O3 -o gl
In file included from main.c:21:
../RFont.h: In function ‘RFont_create_atlas’:
../RFont.h:891:34: error: ‘calloc’ sizes specified with ‘sizeof’ in the earlier argument and not in the later argument [-Werror=calloc-transposed-args]
  891 |    u8* data = (u8*)calloc(sizeof(u8), atlasWidth * atlasHeight * 4);
      |                                  ^~
../RFont.h:891:34: note: earlier argument should specify number of elements, later size of each element
cc1: all warnings being treated as errors
make[1]: *** [Makefile:39: gl] Error 1
make[1]: Leaving directory '/home/eimamei/RFont/example'
make: *** [Makefile:34: all] Error 2
ColleagueRiley commented 3 months ago

There appears to be some kind of imposter among us.