memononen/nanovg

NanoVG string width

nextdayy opened this issue · 13 comments

Hi. I am currently trying to calculate the width of my text using nvgTextBounds, however it seems to only work with a font size of 12px. I have tried to divide the result by 12 then multiply it by the target font size, yet this still leads to small deviations especially after a lot of characters.

Is there any other way to get string width, or how can I improve my current method?

Can anyone help me?

It should work on all sizes. Can you show the code you use and explain more in detail what you'd expect to get.

Also worth taking a look at drawParagraph() in demo.c.

hi, thanks for your reply!

here is my code (note this is the LWJGL bind, in java):
public static float getTextWidth(long vg, String text, float fontSize) {
float[] bounds = new float[4];
return (nvgTextBounds(vg, 0, 0, text, bounds) / 12) * fontSize;
}

I'll be sure to look over that, thanks!

The font settings affect the text bounds, you will need to set them before you measure.

Also, what values you get and what do you expect? How to you use the value and which way it does not match?

Hi, thanks for your help! Just setting the font size using the nvg function before made it calculate correctly.
I have a couple more things as well that I have found, here is the first one:

  • I have been trying for ages to parse then draw an SVG. I have got the SVG to parse correctly, however I can't draw it properly. (note this is java bind) It uses the same code in your nanovg library example, translated to java, except one line (where I believe it goes wrong) which I couldn't understand. the line is: float* p = &path->pts[i*2];
    here is my code:
public static void drawSVGImage(long vg, String fileName, float x, float y, float width, float height) {
        if (ImageLoader.INSTANCE.loadSVGImage(fileName)) {
            try {
                NSVGImage image = ImageLoader.INSTANCE.getSVG(fileName);
                NSVGShape shape;
                NSVGPath path;
                int i;
                for (shape = image.shapes(); shape != null; shape.next()) {
                    //if ((shape.flags() == NSVG_FLAGS_VISIBLE)) {
                    //    continue;
                    //}

                    nvgFillColor(vg, color(vg, shape.fill().color()));
                    nvgStrokeColor(vg, color(vg, shape.stroke().color()));
                    nvgStrokeWidth(vg, shape.strokeWidth());

                    for (path = shape.paths(); path != null; path.next()) {
                        nvgBeginPath(vg);
                        FloatBuffer points = path.pts();
                        nvgMoveTo(vg, points.get(0), points.get(1));
                        for (i = 0; i < path.npts() - 1; i += 3) {
                            float[] p = new float[10];
                            for (int j = 0; j < i * 2 + 3; j++) {           // THIS WONT WORK
                                p[j] = points.get(j);
                                System.out.println(j + " " + p[j]);
                            }
                            nvgBezierTo(vg, p[2], p[3], p[4], p[5], p[6], p[7]);
                        }
                        if (path.closed() == 1) {
                            nvgLineTo(vg, points.get(0), points.get(1));
                        }
                        nvgStroke(vg);
                    }


                }
            } catch (Exception e ) {
                //e.printStackTrace();
            }
        }
    }```

Sneaky! You should not make me do your debugging ;)

for (i = 1; i < path.npts() - 1; i += 3) {
	int b = i * 2;
	nvgBezierTo(vg, points.get(b), points.get(b+1), points.get(b+2), points.get(b+3), points.get(b+4), points.get(b+5));
}

The loop starts from 1 since you submitted the first point already.

The data for the splines are stored like this:

Px,Py, CP0x,CP0y, CP1x,CP1y, Px,Py, CP0x,CP0y, CP1x,CP1y, ...

And nvgBezierTo expects:

CP0x,CP0y, CP1x,CP1y, Px,Py

When drawing the first point of the bezier is the previous draw location (i.e. moveTo, or last point of previously drawn path).

hi, thanks for your help! I tried this earlier, but still nothing :(
At least instead of crashing it just hangs now! Do you have any idea what would be wrong with my code, or should I just give up and raster it? I have been wanting to draw without rastering it because of performance reasons, or is that actually a problem?

Thanks you so much for your help :)

If the svg has a lot of small details, then the AA in nanovg wont work. Rasterization is safe bet.

Okay, thanks. Is there any major performance impact to rasterization? I haven't been wanting to use it because I was concerned about lag when the GUI opens, and high memory usage. Is this actually an issue?

It should not be a problem, but you should of course profile it if you're concerned.

hi! thanks for all your help, we decided in the end to just use PNGs!
back to my original point, it seems after much troubleshooting that nanoVG renders text at the wrong size compared to other programs (figma).

here is the font rendered at 12px, with a rectangle behind it demonstrating the expected height of the font,
image

however the expected height is only reached at a size of 14px:
image

is this to do with how NanoVG calculates ascends and descend of the font? is there any way to fix this, or do I just have to manually set all the font sizes to be slightly larger?
thanks so much for your help :)

AFAIK Font characters are designed so you can place consecutive lines below each other and that there is enough vertical spacing between the characters. So some space is to be expected.

Okay, yet why would this differ from many other programs, such as figma? It appears nanoVG calculates the font size as the maximum ascend and descend height scaled to the font size you specified, yet many other programs use the font size as the standard/baseline height, and the ascend/descend extend beyond that.

Is there any way to change this in NanoVG?

@nxtdaydelivery @memononen thank you very much! Sorry for the ping, but you absolutely saved me! I'm working on an actually readable debug screen in Kotlin, and just couldn't find out how to have both bold and regular text on the same line. Thank you!