2024-06-19 05:10 AM - edited 2024-09-06 04:57 AM
I want to align digits without prepending with 0. So %3d instead of %03d.
000:00
100:00
10:00
1:00
With non-monospaced fonts, such as verdana this doesn't work due to the space character not having the same width as digits. I don't care about the width of punctuation. All digits do have the same width in most non-monospaced fonts, but the space differs.
Unicode has different space types. Such as U+2007 "FIGURE SPACE" (https://jkorpela.fi/chars/spaces.html). This character should be a space character with the same width as a digit. Not all fonts support this character(https://www.fileformat.info/info/unicode/char/2007/fontsupport.htm). Arial is non-monospaced, but supports this character.
Below is a demonstration with ARIAL with regular space and "FIGURE SPACE"
WWW:WW
lll:ll
000:00
100:00
10:00
1:00
WWW:WW
lll:ll
000:00
100:00
10:00
1:00
As you can see (if the forum renders this correctly) the width of characters differs, but digits have the same width. And the "FIGURE SPACE" is the same width as the digits. So that's good.
But if I try this in TouchGFX it doesn't work as regular spaces are used. I wrote some code to add this, but ideally this should be implemented inside TouchGFX snprintf method:
textArea1.setLinespacing(-5); // alignment is easier to see this way
textArea1.invalidateContent();
Unicode::snprintf(textArea1Buffer, TEXTAREA1_SIZE, "WWW\nlll\n%3d\n%3d\n%3d\n", 100,10,1);
textArea1.resizeToCurrentText();
textArea1.invalidateContent();
textArea2.setLinespacing(-5);// alignment is easier to see this way
textArea2.invalidateContent();
Unicode::snprintf(textArea2Buffer, TEXTAREA2_SIZE, "WWW\nlll\n%3d\n%3d\n%3d\n", 100,10,1);
bool typographySupportsFigureSpace = true;// TODO: how to detect this?
if (typographySupportsFigureSpace)
{
auto len = Unicode::strlen(textArea2Buffer);
for(auto i = 0; i < len; ++i)
{
if (textArea2Buffer[i] == 0x0020)
{
textArea2Buffer[i] = 0x2007; // "FIGURE SPACE"
}
}
}
textArea2.resizeToCurrentText();
textArea2.invalidateContent();
Result (when using ARIAL and adding the needed characters to the typography):
So it works. But I would have to implement this for every single aligned text field. And I cannot simply replace all spaces since some of them should be normal spaces. In my opinion aligning digits is a huge aesthetic improvement over having them jump around and it doesn't add much CPU overhead. I was able to succesfully modify an existing font that lacked "FIGURE SPACE" and didn't have monospaced digits using fontforge. I made all digits equal width, centered them, removed kerning pairs and added the "FIGURE SPACE" character. This worked. So adding support for "FIGURE SPACE" in an snprintf variant is a huge benefit since you can make it work with all fonts if you are willing to edit them.
My questions are:
Solved! Go to Solution.
2024-09-06 04:45 AM - edited 2024-09-17 05:02 AM
I was still working on it a bit. So this is my final answer which also summarizes all the steps I took.
I made a font somewhat monospaced for digits and added a "figure space" character using these instructions: https://graphicdesign.stackexchange.com/a/165940/196693
In order to add a font to TouchGFX it needs to be copied in the asset folder when TouchGFX Designer is closed (/TouchGFX/assets/fonts/). I noticed that I sometimes had to clear the font cache (/TouchGFX/generated/fonts/) if I updated the font in the asset folder. (An alternative is to change the font in typography and change it back again, but then a new font appears in the asset folder, which had to be deleted manually). If I didn't clear the cache the font would not render properly (It looked like a shifted font table, it rendered multiple partial characters in the place of one).
The simplest way to prevent digits moving horizontally is to set the text as right aligned in TouchGFX Designer and use resizeToCurrentTextWithAlignment() after updating the string:
textArea1.invalidateContent();
Unicode::snprintf(textArea1Buffer, TEXTAREA1_SIZE, "%3d:%02d.%d S", m_timeSecondsMul10/(10*60), (m_timeSecondsMul10/10)%60, m_timeSecondsMul10%10);
textArea1.resizeToCurrentTextWithAlignment();
textArea1.invalidateContent();
But sometimes a string contains letters and numbers and you want it left-aligned. And in that case "figure space" is the only way to prevent the digits from moving horiziontally when the number of successive digits changes. Or you need to split the string into multiple textAreas, but that is tedious. I rather print one string.
Instead of copying the figure space character from wikipedia or some unicode table website and pasting it in the "wildcard characters" field where you cannot really see it I paste 0x2007 in "wildcard ranges" e.g. "0-9,A-Z,a-z,0x2007". In other words you can paste the hex code of single characters in wildcard ranges. Ideal for white space characters.
I initially tried to rewrite vsnprintf. I thought I could delegate all formatting to existing print functions and only override the number formatting. This worked. But it was more than 500 lines of code. So I opted for something simpler:
#include <ctype.h> // for isdigit()
void replaceSpacesBeforeDigitsWithFigureSpaces(touchgfx::Unicode::UnicodeChar* s)
{
touchgfx::Unicode::UnicodeChar *start = nullptr;
touchgfx::Unicode::UnicodeChar *s_ptr = s;
if(s == nullptr)
{
return;
}
while(*s_ptr != '\0')
{
if ( isdigit(*s_ptr) )
{
if (start != nullptr) // if digit follows a series of 1 or more spaces
{
while (start < s_ptr)
{
*start = FIGURE_SPACE; // replace
++start;
}
start = nullptr; // reset marker
}
}
else if(*s_ptr == ' ')
{
if (start == nullptr) // only mark first space
{
start = s_ptr; // mark first space
}
}
else // no space or digit so reset
{
start = nullptr; // reset marker
}
++s_ptr; // next character
}
}
In order to detect if figure space is supported:
bool fontCharacterSupported(const touchgfx::Font* fontPtr, touchgfx::Unicode::UnicodeChar c)
{
if (fontPtr == nullptr)
{
return false; // no font so no character is supported
}
auto fallbackChar = fontPtr->getFallbackChar();
if (c == fallbackChar)
{
return true; // fallback character is always supported
}
auto fallbackGlyth = fontPtr->getGlyph(fallbackChar);
auto glyth = fontPtr->getGlyph(c);
return (glyth != nullptr) && (glyth != fallbackGlyth);
}
bool fontCharacterSupported(const touchgfx::TextArea &textArea, touchgfx::Unicode::UnicodeChar c)
{
return fontCharacterSupported(textArea.getTypedText().getFont(), c);
}
Definitions and prototypes:
#include <touchgfx/widgets/TextArea.hpp> // for touchgfx::TextArea
constexpr touchgfx::Unicode::UnicodeChar FIGURE_SPACE = 0x2007;
bool fontCharacterSupported(const touchgfx::Font* fontPtr, touchgfx::Unicode::UnicodeChar c);
bool fontCharacterSupported(const touchgfx::TextArea &textArea, touchgfx::Unicode::UnicodeChar c);
void replaceSpacesBeforeDigitsWithFigureSpaces(touchgfx::Unicode::UnicodeChar* s);
I placed all this code in FrontendApplication.hpp and FrontendApplication.cpp
example code (executed every x ticks):
static int counter = 0;
counter = counter >= 10 ? counter/10 : 123456;
textArea2.invalidateContent();
Unicode::snprintf(textArea2Buffer, TEXTAREA2_SIZE, "a b %6d", counter);
bool typographySupportsFigureSpace = fontCharacterSupported(textArea2.getTypedText().getFont(), FIGURE_SPACE);
if (typographySupportsFigureSpace)
{
replaceSpacesBeforeDigitsWithFigureSpaces(textArea2Buffer);
}
textArea2.resizeToCurrentText();
textArea2.invalidateContent();
The first character and the last digit now stay perfectly in place regardless of the number of digits and the values of the digits. This is exactly what I wanted.
2024-06-20 04:46 AM
Hello @unsigned_char_array ,
Can't this be achieve by aligning the text to the right?
1) If there is a workaround (aligning text to the right for instance), this won't be a priority.
2) Instead of overwriting the touchgfx_printf function, can't you create a my_printf function, that does exactly what you want it do do?
You would probably have to split the string with '\n' to get each line, then check if the line is only number and then try replacing the spaces for that string with the figure space. Maybe wrap the whole thing in a try or implement another safety mecanism.
3) I am often lazy and simple add "0x00-0xff" as wildcard character when simulating. I assume you can just add it with "0x2007".
Regards,
2024-06-20 05:41 AM
@GaetanGodart wrote:Can't this be achieve by aligning the text to the right?
Only if the string has just a number in it. I have a few fields that are only digits, so I will definitely use it in those situations! But I mostly use textArea with wildcard:(.
For instance "Sensor value: <%3d>" and that would have the text at the beginning of the string jump back and forth depending on the sensor value if I align it to the right since you can only set the alignment for the whole string, not just the wildcard part.
I cannot split the text into a fixed and a variable string since the beginning part of the string varies in width depending on the selected language.
@GaetanGodart wrote:2) Instead of overwriting the touchgfx_printf function, can't you create a my_printf function, that does exactly what you want it do do?
I can write my own snprintf function for unicode but I don't want to rewrite the entire formatting functionality. I only want to rewrite the spacing part. I assume that part is closed source in the library (touchgfx::Unicode::composeString?).
I could write a function that first processes the formatting and then passes it to TouchGFX snprintf (either by multiple calls or by modifying the arguments and using vsnprintf, but that would take more stack). But that might not be as elegant. I would have to think about it. It might be too much work without having that part of the source code. If you can help me save work that would be great.
@GaetanGodart wrote:3) I am often lazy and simple add "0x00-0xff" as wildcard character when simulating. I assume you can just add it with "0x2007".
That works! I can just add an individual character in wildcard ranges e.g. "0-9,A-Z,a-z,0x2007". Thanks!
2024-06-20 06:45 AM
1)
I think it would work by using 2 strings: 1 for "Sensor : " and 1 for the value.
You align the value to the right.
When using various languages, just align the "Sensor :" textArea to the right too and make it too big.
Sounds like it would work. I know it's a workaround but that sounds easier than making your own print function.
Another workaround would be to use a monospaced font.
I always use Consolas now when I can. I can also use Inconsolata which free to use.
2)
I have looked and yes it seems to be close source.
I can help you but my knowledge is limited.
I looked at the snprintf function and asked chatGPT how to create a custom print function and I understand the steps but I have never done it from scratch and also I don't how you plan on modifying the arguments or doing multiple calls.
3)
Great!
Regards,
2024-06-24 06:51 AM
Aligning text to the right in a text area with wildcard doesn't work in a container. It always centers the text.
I print minutes and seconds.
It kind of looks like this (if the forum renders it correctly):
100:00
10:00
0:00
I've set the alignment to the right, but I don't see a difference with left or center alignment.
I tried upgrading to TouchGFX 4.24.0 but the problem persists.
How can this be?
"FIGURE SPACE" still seems the best solution at the moment for non-monospaced fonts. I will implement my own printf function.
I have one more question: why is there a separate float print function in TouchGFX?
2024-06-24 07:14 AM - edited 2024-06-24 07:14 AM
Hello @unsigned_char_array ,
I was able to put a textArea inside a custom container, align that textArea to the right:
Can you share a project where the alignment misbehave, this sound like a bug.
The separate float print function have for description : "Variant of snprintf for floats only. The format supports several \%f with flags/modifiers".
I assume it is optimized.
Regards,
2024-06-24 11:46 AM - edited 2024-06-28 06:37 AM
I created a new project from scratch in TouchGFX 4.24.0 and replicated the result. I used Arial and right align a field, but it clearly aligns left.
I think it's due to resizeToCurrentText. I think that doesn't take alignment into account. Resizing should move the left border of the text box, not the right border if it is right aligned.
I found the solution while typing this post: Using resizeToCurrentTextWithAlignment() instead solved the problem.
Edit: I wrote a function to test if a character is supported by a font.
bool fontCharacterSupported(const touchgfx::Font* fontPtr, touchgfx::Unicode::UnicodeChar c)
{
if (fontPtr == nullptr)
{
return false; // no font so no character is supported
}
auto fallbackChar = fontPtr->getFallbackChar();
if (c == fallbackChar)
{
return true; // fallback character is always supported
}
auto fallbackGlyth = fontPtr->getGlyph(fallbackChar);
auto glyth = fontPtr->getGlyph(c);
return (glyth != nullptr) && (glyth != fallbackGlyth);
}
2024-09-05 03:24 AM
Hello @unsigned_char_array ,
I am glad you found a solution. :smiling_face_with_smiling_eyes:
I selected your last message as "best answer".
Regards,
2024-09-06 04:45 AM - edited 2024-09-17 05:02 AM
I was still working on it a bit. So this is my final answer which also summarizes all the steps I took.
I made a font somewhat monospaced for digits and added a "figure space" character using these instructions: https://graphicdesign.stackexchange.com/a/165940/196693
In order to add a font to TouchGFX it needs to be copied in the asset folder when TouchGFX Designer is closed (/TouchGFX/assets/fonts/). I noticed that I sometimes had to clear the font cache (/TouchGFX/generated/fonts/) if I updated the font in the asset folder. (An alternative is to change the font in typography and change it back again, but then a new font appears in the asset folder, which had to be deleted manually). If I didn't clear the cache the font would not render properly (It looked like a shifted font table, it rendered multiple partial characters in the place of one).
The simplest way to prevent digits moving horizontally is to set the text as right aligned in TouchGFX Designer and use resizeToCurrentTextWithAlignment() after updating the string:
textArea1.invalidateContent();
Unicode::snprintf(textArea1Buffer, TEXTAREA1_SIZE, "%3d:%02d.%d S", m_timeSecondsMul10/(10*60), (m_timeSecondsMul10/10)%60, m_timeSecondsMul10%10);
textArea1.resizeToCurrentTextWithAlignment();
textArea1.invalidateContent();
But sometimes a string contains letters and numbers and you want it left-aligned. And in that case "figure space" is the only way to prevent the digits from moving horiziontally when the number of successive digits changes. Or you need to split the string into multiple textAreas, but that is tedious. I rather print one string.
Instead of copying the figure space character from wikipedia or some unicode table website and pasting it in the "wildcard characters" field where you cannot really see it I paste 0x2007 in "wildcard ranges" e.g. "0-9,A-Z,a-z,0x2007". In other words you can paste the hex code of single characters in wildcard ranges. Ideal for white space characters.
I initially tried to rewrite vsnprintf. I thought I could delegate all formatting to existing print functions and only override the number formatting. This worked. But it was more than 500 lines of code. So I opted for something simpler:
#include <ctype.h> // for isdigit()
void replaceSpacesBeforeDigitsWithFigureSpaces(touchgfx::Unicode::UnicodeChar* s)
{
touchgfx::Unicode::UnicodeChar *start = nullptr;
touchgfx::Unicode::UnicodeChar *s_ptr = s;
if(s == nullptr)
{
return;
}
while(*s_ptr != '\0')
{
if ( isdigit(*s_ptr) )
{
if (start != nullptr) // if digit follows a series of 1 or more spaces
{
while (start < s_ptr)
{
*start = FIGURE_SPACE; // replace
++start;
}
start = nullptr; // reset marker
}
}
else if(*s_ptr == ' ')
{
if (start == nullptr) // only mark first space
{
start = s_ptr; // mark first space
}
}
else // no space or digit so reset
{
start = nullptr; // reset marker
}
++s_ptr; // next character
}
}
In order to detect if figure space is supported:
bool fontCharacterSupported(const touchgfx::Font* fontPtr, touchgfx::Unicode::UnicodeChar c)
{
if (fontPtr == nullptr)
{
return false; // no font so no character is supported
}
auto fallbackChar = fontPtr->getFallbackChar();
if (c == fallbackChar)
{
return true; // fallback character is always supported
}
auto fallbackGlyth = fontPtr->getGlyph(fallbackChar);
auto glyth = fontPtr->getGlyph(c);
return (glyth != nullptr) && (glyth != fallbackGlyth);
}
bool fontCharacterSupported(const touchgfx::TextArea &textArea, touchgfx::Unicode::UnicodeChar c)
{
return fontCharacterSupported(textArea.getTypedText().getFont(), c);
}
Definitions and prototypes:
#include <touchgfx/widgets/TextArea.hpp> // for touchgfx::TextArea
constexpr touchgfx::Unicode::UnicodeChar FIGURE_SPACE = 0x2007;
bool fontCharacterSupported(const touchgfx::Font* fontPtr, touchgfx::Unicode::UnicodeChar c);
bool fontCharacterSupported(const touchgfx::TextArea &textArea, touchgfx::Unicode::UnicodeChar c);
void replaceSpacesBeforeDigitsWithFigureSpaces(touchgfx::Unicode::UnicodeChar* s);
I placed all this code in FrontendApplication.hpp and FrontendApplication.cpp
example code (executed every x ticks):
static int counter = 0;
counter = counter >= 10 ? counter/10 : 123456;
textArea2.invalidateContent();
Unicode::snprintf(textArea2Buffer, TEXTAREA2_SIZE, "a b %6d", counter);
bool typographySupportsFigureSpace = fontCharacterSupported(textArea2.getTypedText().getFont(), FIGURE_SPACE);
if (typographySupportsFigureSpace)
{
replaceSpacesBeforeDigitsWithFigureSpaces(textArea2Buffer);
}
textArea2.resizeToCurrentText();
textArea2.invalidateContent();
The first character and the last digit now stay perfectly in place regardless of the number of digits and the values of the digits. This is exactly what I wanted.