Advanced NanoVG custom Label?

Hi,

is there any fancy “label” class within VCV Rack world where, given whatever font I want to write and any text:

  1. I can set a fixed width/height for the box where the text will reside
  2. Due to these sizes, align the inner text horizontally (left, right, center) and vertically (center, top, bottom)

Coming from the “web” world, these are the basics. I found hard do it on NanoVG. Due to the size of the texts, the font and its size, I need to calculate manually the position.

Maybe there’s an already build-in class that do it automatically? Thanks

1 Like
struct TextDisplay : TransparentWidget {
  std::shared_ptr<Font> font;

  TextDisplay ( ) {
    font = APP->window->loadFont(asset::plugin(pluginInstance, "res/MyFont.ttf"));
  }

  void draw (const DrawArgs &args) override {
    nvgFontSize(args.vg, 16);
    nvgFontFaceId(args.vg, font->handle);
    nvgTextLetterSpacing(args.vg, 1);

    nvgBeginPath(args.vg);
    nvgFillColor(args.vg, nvgRGBA(0xa8, 0x81, 0x09, 0xff));

    nvgText(args.vg, 0, 0, "My Text", NULL);
  }
};

something like that?

Uhm? No. That’s a basic text writer.

I mean: box 200x50 for example, align “My text” center, in both horizontal and vertical. Like this Edit fiddle - JSFiddle - Code Playground

1 Like

and when you set the container to be 200x50 and set the text to be vertically centered when you place the text, it becomes like your example.

How do you do that?

1 Like

I usually use math.

    nvgFontSize(args.vg, 16);

font size in the example is 16 pixels. if I want there to be a 24 pixel line-height, then (24 - 16) / 2 works out to be 4 pixels above and below.

nvgText(args.vg, 0, 4, "My Text", NULL);

which offsets the y parameter by 4 pixels.

1 Like

Math eh? Whodathunkit…

Disclaimer: I am still very out of practice, so please forgive me if I ask things that may be obvious to others.

I’m curious as I have a custom label class derived from LedDisplay. My font of choice is proportional. Presently I mess around changing coords to get labels to line up with components, would be nice to have them centred automatically but not sure how to go about it with nvg etc I also have a ScribbleStrip class derived from LedDisplayTextField, same applies there.

I expect there are text metric functions somewhere to allow me to measure the width of a proportional font string and adjust coords accordingly, I don’t know and if I did I’m not sure where I’d need to put them.

2 Likes

the documentation for nanovg seems to be a little sparse, and tutorials out in the wild seem to be fairly rare, but there is documentation in the header file itself:

that should give you width of a text string. that’s the text section, and there are functions for multi-line management, etc.

not the best pointer, but it should be enough to get the math to work.

3 Likes

If you want an example of using nvgTextBounds I used it to make a function which draws a label with a rule at the bottom on either side sized to the size of the string.

that’s the function and here’s a UI where I called it 3 times with the resulting screen.

HTH

4 Likes

Thanks for all the tips, I think I’ve finally figure it out how to do it for my code, easily:

void drawTextBox(NVGcontext *vg, float fontSize, float width, float height, const std::string &text) {
	nvgFontSize(vg, fontSize);
	nvgTextAlign(vg, NVG_ALIGN_TOP | NVG_ALIGN_LEFT);

	// bounds
	float bounds[4];
	
	nvgTextBounds(vg, 0.0f, 0.0f, text.c_str(), NULL, bounds);

	float textX = bounds[0];
	float textY = bounds[1];
	float textWidth = bounds[2];
	float textHeight = bounds[3];

	// center
	nvgText(vg, width / 2.0f - textWidth / 2.0f, height / 2.0f - textHeight / 2.0f, text.c_str(), NULL);
}

It seems to works pretty nice with the font family I’ve tried :wink:

2 Likes

Mine:

struct RSLabelCentered : LedDisplay {
	std::shared_ptr<Font> font;
	int fontSize;
	std::string text;
	NVGcolor color;

	RSLabelCentered(int x, int y, const char* str = "", int fontSize = 10, const NVGcolor& colour = COLOR_RS_GREY) {
		font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/Ubuntu Condensed 400.ttf"));
		this->fontSize = fontSize;
		box.pos = Vec(x, y);
		text = str;
		color = colour;
	}

	void draw(const DrawArgs &args) override {
		if(font->handle >= 0) {
			bndSetFont(font->handle);

			nvgFontSize(args.vg, fontSize);
			nvgFontFaceId(args.vg, font->handle);
			nvgTextLetterSpacing(args.vg, 0);
			nvgTextAlign(args.vg, NVG_ALIGN_CENTER);

			nvgBeginPath(args.vg);
			nvgFillColor(args.vg, color);
			nvgText(args.vg, 0, 0, text.c_str(), NULL);
			nvgStroke(args.vg);

			bndSetFont(APP->window->uiFont->handle);
		}
	}
};

Thanks everyone :slight_smile:

If anyone who actually knows what they’re doing fancies giving that a quick sanity check I’d be grateful for any suggestions / improvements / pointing out of cluelessness.

Unfortunately, this approch doesn’t work with every font :frowning:

I’ve tried for example Segment7, and it seems that render it remove additional space in the top of the target box.

With DejaVuSansMono, nvgTextBounds returns height of 20px for example, and the font (16 pixels) is rendered 2 pixel from above, correctly (2+16+2 = 20); but with Segment7 the space is 0 (so 0 + 16 != 20), resulting in a font “moved” around the top.

Is there a way to also retrieve the “padding” between the the nvgTextBounds and actually where the font render? So I can compensate…

Could probably use Modulo, or just offset position by 2px.

nvgTextBox also might pad the text so it will always be the proper size to fit.

The point is to “automate” this process :slight_smile: If for every font/label I Need to manually calcolate them, than I simply place coordinates as well hehe.

Not sure about your suggestion about nvgTextBox?

So, I can now center my custom labels, whoopee! Trying to do the same for my custom scribble strips (derived from LedDisplayTextField). I can get them centered easily enough, however the cursor no longer behaves as expected, I have to click several characters to the right of where I’d like the cursor, I’m not sure how to go about fixing that.

	void draw(const DrawArgs &args) override {
		if(cursor > numChars) {
			text.resize(numChars);
			cursor = numChars;
			selection = numChars;
		}

		//nvgScissor(args.vg, RECT_ARGS(args.clipBox));
		if (font->handle >= 0) {
			bndSetFont(font->handle);

			float bounds[4];
			nvgTextBounds(args.vg, 0.0f, 0.0f, text.c_str(), NULL, bounds);
			float textWidth = bounds[2];

			// If we subtract textWidth / 2 from bndIconLabelCaret parameter 2 textOffset.x we get dynamically centered strips
			//   however the mouse doesn't position the cursor accordingly
			NVGcolor highlightColor = color;
			highlightColor.a = 0.5;
			int begin = std::min(cursor, selection);
			int end = (this == APP->event->selectedWidget) ? std::max(cursor, selection) : -1;
			bndIconLabelCaret(args.vg, textOffset.x, textOffset.y,
				box.size.x - 2*textOffset.x, box.size.y - 2*textOffset.y,
				-1, color, textSize, text.c_str(), highlightColor, begin, end);

			bndSetFont(APP->window->uiFont->handle);
		}
		//nvgResetScissor(args.vg);
	}

Any suggestions?

that’s why I said I “use math”. just subclass to do the math for you. you’re only looking at a few lines of code.

Sure, but which code calculate the margin rendered for a fixed font? As I said above, it seems you cannot operate on “segment” the same way you do with other font, the space is different. It messes the whole calculation…

I’m really struggling to understand how knowing the exact size of the container, as well as the exact size of what you are trying to place inside of that container, makes math hard.

as I posted above, you can get the exact size of the string you are trying to render from nanovg:

as an array of floating point values, that gives you the starting X position, the ending X position, the starting Y position, and the ending Y position.

am I just missing something here?

if that math doesn’t work for you, you can go even more low level by working with the glyph positions:

you’re placing a known box, inside of another known box, relative to the position of that box.

@jerrysv nope :slight_smile: If the font size (for example) is 16 pixel and the bound return 20 as height, nothing ensure that the font is drawn as 2+16+2 :slight_smile:

It should be printed neat the top, resulting in a 20 pixels height (the box), with a botton space higher than the top. Thats what I mean…

I.e. “the starting Y position, and the ending Y position.” is ok, but the text could be not draw in the middle of those sizes :slight_smile: Thats the trouble.

Tomorrow I’ll try that nvgTextMetrics, now I’m not at home.