olikraus / u8g2

U8glib library for monochrome displays, version 2
Other
5.07k stars 1.05k forks source link

Feature request / proposal: Clean up horizontal positioning issues #2398

Closed egnor closed 1 month ago

egnor commented 6 months ago

References:

It is a surprising misfeature that drawStr(x, y, text) doesn't always start drawing at x -- often the actual text starts a pixel or so to the right of the specified coordinate, depending on the character. This leads to little annoying misalignments that can be seen in many screenshots and examples.

My understanding of the issue

In the BDF font format which u8g2 is based on, glyphs are given as a set of pixels within a bounding box, and also an offset from logical "origin 0" of the glyph to the lower-left corner of the bounding box. This "origin 0" describes the start of the glyph's baseline. (There's also an "origin 1" used for writing systems like CJK with multiple writing directions, I'll ignore that here.)

In a theoretical sense, origin 0 is the "left hand edge" of the character, and it would make sense for drawStr to place the first character's origin 0 at the provided x/y coordinates. In a more accurate real sense, many fonts, especially Latin fonts, tend to use it as a rough kerning hint. For example, in helvR08, uppercase letters with a strong vertical left-hand-side riser have origin 0 one pixel to the left of that riser to give a bit more visual space from the preceding character. (I'm speculating as to the intent of the font's creator. Since X11 uses these fonts, I bet there's been discussion of this!)

Currently the only way for a user of u8g2 to deal with this is with the undocumented C-api-only u8g2_getStrX function.

Preferred behavior

For the purpose of u8g2, it should follow these values for placing one glyph after the next, I believe users care far more about the actual physical leftmost pixel (and rightmost pixel):

I believe this would be minimally surprising for users who care about pixels and don't care about the subtleties of BDF baseline origin semantics, and I believe the concepts of "left margin" and "right margin" are a lot easier to grasp than "offset vector from origin to corner of bounding box".

Behavior of drawStr/drawGlyph/drawUTF8 could be controlled by modes the same way Y-coordinate behavior is controlled by the setFontPos*(...) functions. I could imagine offering origin-point, leftmost-pixel, center-pixel, rightmost-pixel, and origin-point-plus-logical-width as X-positioning options consistent with previous Y-positioning options. The default could be compatible with prior behavior.

There's a number of interactions here with font-direction and so on to be worked through...

olikraus commented 6 months ago

Thanks for your proposal. I never received much feedback to the font rendering functions, so thanks for this :-) I like your ideas and they indeed make sense, however, my resources on this project are very limited at the moment, so let's see when I can spent some time on this.

egnor commented 6 months ago

Of course! Any obvious gotchas if someone else were to take a stab at it?

olikraus commented 1 month ago

I started to look into this. So far most fonts seem to start exactly at the origin like helvB08 in the picture below. pic0000

The only difference seems to be ncenB08, which starts one pixel earlier. Is there any example font, which starts one pixel to the right?

olikraus commented 1 month ago

I think there was a similar topic here: https://github.com/olikraus/u8g2/issues/1561

pic0001

So the uppercase C from helvR08 has a one pixel right offset.

for positive x offset the string width calculation will compensate this a little bit

olikraus commented 1 month ago

As a first step, I could implement "getGlyphLeftMargin ()" ...

olikraus commented 1 month ago
  1. As a conclusion for me: We have already a str-width compensation for positive x offset build into getStrWidth.
  2. the x offset is usually 0 (A in helvB08), but can be positive (C in helvR08) or negative (A in ncenB08)
  3. It looks to me that GetStrX is broken: It will only work for encodings < 128.

So at the moment I am more think to keep the name as getXOffset instead of get..LeftMargin, because it is somehow strange to have a negative margin.

I have introduced and tested

int8_t u8g2_GetXOffsetGlyph(u8g2_t *u8g2, uint16_t encoding);
int8_t u8g2_GetXOffsetUTF8(u8g2_t *u8g2, const char *utf8);

So the expected value "1" is returned:

printf("helvR08 C xoffset=%d\n", (int8_t)u8g2_GetXOffsetGlyph(&u8g2, 'C'));
printf("helvR08 Ĉ xoffset=%d\n", (int8_t)u8g2_GetXOffsetUTF8(&u8g2, "Ĉ"));

As expected during my review, the GetStrX doesn't work for UTF8 strings. It will return the wrong value 0:

printf("helvR08 Ĉ xoffset=%d\n", (int8_t)u8g2_GetStrX(&u8g2, "Ĉ"));
olikraus commented 1 month ago

u8g2_x_offset

olikraus commented 1 month ago

https://github.com/olikraus/u8g2/wiki/u8g2reference#getxoffsetglyph

olikraus commented 1 month ago

I think there is no need for a special mode. If the offset is not required, then just compenstate the offset: u8g2.drawUTF8(x-u8g2.getXOffsetUTF8(string), y, string)

I will close this for now...