memononen / nanovg

Antialiased 2D vector drawing library on top of OpenGL for UI and visualizations.
zlib License
5.06k stars 767 forks source link

Bug ? The glyphs rendered are not on the same line #624

Closed BusyStudent closed 2 years ago

BusyStudent commented 2 years ago

Strange text rendering results when I render English and Chinese characters at the same time

Here is the rendering results of nanovg and sdl_ttf Screenshot_20220210_182822

Compared to SDL_ TTF,The Chinese glyphs and English glyphs seem to be not in the same line It was much lower than English glyphs

It looks doesnot very good

Here is the code


//text and the fontsize
const char *text = "啊AB啊C啊D啊E啊F界 Hello哈哈哈World";
float fontsize = 12;

//Setup nanovg ctxt
...
//Load ttf
//Microsoft Ya Hei
int f = nvgCreateFont(ctxt,"font","./msyh.ttc");
TTF_Font *ttf = TTF_OpenFont("./msyh.ttc",fontsize);

//SDL_TTF Render text 
SDL_Color color;
color.r = 0;
color.g = 0;
color.b = 0;
color.a = 255;

SDL_Surface *surf = TTF_RenderUTF8_Blended(ttf,text,color);

TTF_CloseFont(font);

int image = nvgCreateImageRGBA(
        ctxt,
        surf->w,
        surf->h,
        NVG_IMAGE_NEAREST,
        (Uint8*)surf->pixels
);
...

//Clear background
//nvgBeginFrame ...

//nanovg draw text
nvgBeginPath(ctxt);
nvgFillColor(ctxt,nvgRGB(0,0,0));
nvgFontFaceId(ctxt,f);
nvgFontSize(ctxt,fontsize);
nvgTextAlign(ctxt,NVG_ALIGN_TOP |  NVG_ALIGN_LEFT);
nvgText(ctxt,100,300,text,nullptr);
nvgFill(ctxt);
//SDL_ttf draw text
nvgBeginPath(ctxt);
nvgRect(ctxt,100,100,surf->w,surf->h);
nvgFillPaint(ctxt,nvgImagePattern(ctxt,
      100,
      100,
      surf->w,
      surf->h,
      0.0f/180.0f*NVG_PI,
      image,
      1.0f
 ));
nvgFill(ctxt);

//EndFrame ...
//SwapBuffer ...

Here is the font msyh.zip

Can anyone help me?

mulle-nat commented 2 years ago

The difference could be the way SDL/nanovg load the fonts. There are many parameters to freetype. What I would try is build ftview or ftgrid from the freetype-demos and check out the various "hinting" and anti-aliasing modes, which sometimes give surprising differences. You'd probably have to patch nanovg, to use your desired font mode.

BusyStudent commented 2 years ago

@mulle-nat @memononen But when i change the hinting in fons__tt_buildGlyphBitmap into different flags such as FT_LOAD_DEFAULT or FT_LOAD_NO_AUTOHINT,it didnot works It still perform as previous

I had already try all hintings and anti-aliasing modes

And and noticed the lsb in fontstash didnot be used in anywhere,what is the lsb used for I think the difference is the font metrics is not be handled correctly Because i saw the way of getting the font metrics in SDL_ttf is different from nanovg

in TTF_initFontMetrics


    /* Make sure that our font face is scalable (global metrics) */
    if (FT_IS_SCALABLE(face)) {
        /* Get the scalable font metrics for this font */
        FT_Fixed scale       = face->size->metrics.y_scale;
        font->ascent         = FT_CEIL(FT_MulFix(face->ascender, scale));
        font->descent        = FT_CEIL(FT_MulFix(face->descender, scale));
        font->height         = FT_CEIL(FT_MulFix(face->ascender - face->descender, scale));
        font->lineskip       = FT_CEIL(FT_MulFix(face->height, scale));
        underline_offset     = FT_FLOOR(FT_MulFix(face->underline_position, scale));
        font->line_thickness = FT_FLOOR(FT_MulFix(face->underline_thickness, scale));
    } else {
        /* Get the font metrics for this font, for the selected size */
        font->ascent         = FT_CEIL(face->size->metrics.ascender);
        font->descent        = FT_CEIL(face->size->metrics.descender);
        font->height         = FT_CEIL(face->size->metrics.height);
        font->lineskip       = FT_CEIL(face->size->metrics.height);
        /* face->underline_position and face->underline_height are only
         * relevant for scalable formats (see freetype.h FT_FaceRec) */
        underline_offset     = font->descent / 2;
        font->line_thickness = 1;
    }
mulle-nat commented 2 years ago

As far as I understand nanovg, nanovg places each character into a small part of a large texture with a one pixel border around it. The actual rasterization is done by FreeType. Specifically here ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT); I would suspect that FT_LOAD_TARGET_LIGHT is the culprit, that's responsible for the visual difference. If it's not, I would start trial and erroring myself to success, as I have no further ideas :)

BusyStudent commented 2 years ago

After omitting FT_LOAD_TARGET_LIGHT, the visual difference still exist,it just make the text sharper.

BusyStudent commented 2 years ago

@mulle-nat Did you get any progress of it?

mulle-nat commented 2 years ago

I am not doing anything. As I wrote in my previous reply, I have no further ideas.

mulle-nat commented 2 years ago

@BusyStudent Were you able to solve the problem ?

BusyStudent commented 2 years ago

@BusyStudent Were you able to solve the problem ?

@mulle-nat Yes,I have find the reason,The reason is fontstash didnot handle bearingX and bearingY correctly, But I still didnot how to patch it by a clear way. It has big difference with SDL_ttf,And there are too many magic number

Here is my code,It works,but dirty

//in fons__getGlyph
    glyph->index = g;
    glyph->x0 = (short)gx;
    glyph->y0 = (short)gy;
    glyph->x1 = (short)(glyph->x0+gw);
    glyph->y1 = (short)(glyph->y0+gh);
    glyph->xadv = (short)(scale * advance * 10.0f);//Why 10.0f
    glyph->xoff = (short)(x0 - pad);
    glyph->yoff = (short)(y0 - pad) - y1;//< Just here
//in fons__tt_buildGlyphBitmap
    *x0 = FT_FLOOR(metrics->horiBearingX);
    *x1 = FT_CEIL(metrics->horiBearingX + metrics->width);
    *y0 = -FT_FLOOR(metrics->horiBearingY);
    *y1 = *y0 + FT_CEIL(metrics->height);
BusyStudent commented 2 years ago

@mulle-nat oh,sorry.It only works on current font... I read the old version of SDL_ttf.
My finnal idea is to add harbuzz to get offset / advance like here

mulle-nat commented 2 years ago

Interesting. I use harfbuzz in my projects, because it improved the looks, but that's about what I know. I didn't think of suggesting it to you, because I see it do better kerning (better spacing in the x-axis) only. But maybe it also also does y-axis offsets. So your idea sounds worth trying to me. Good luck.

BusyStudent commented 2 years ago

@mulle-nat Did you doing anything? I try to add hb in fonsTextIterInit,but after calling hb_shape. I received an all zero value array in hb_buffer_get_glyph_positions

@memononen I also check freetype documents,It seems that fontstash didn't align glyphs in the baseline ,it just output the glyph directly and I was stuck in align the glyph in fons__getQuad.

I have see the align code in SDL_ttf by getting y-axis offset fromascent - bitmap_top ,But when I port it to fontstash it works,but the lower case character was much higher than prev. I think the align algorithm still has something wrong

mulle-nat commented 2 years ago

The way I use harfbuzz is that I am building my own freetype library and define it to use harfbuzz. So in effect, if harfbuzz were to fix your problem, it would be doing so transparently to nanovg by providing better values through freetype. Or at least that's how I believe it works.

BusyStudent commented 2 years ago

Fixed,After using my reimplementation of fontstash