/*==== COPIA DE TRABAJO QUE ESTOY MODIFICANDO ====
I found this sketch by chance at https://wokwi.com/projects/372442890656329729
and made a copy just in case it disappears: https://wokwi.com/projects/399058041202410497
This sketch you are watching now is a working copy which I am trying
to improve.
Although the original sketch says to be for the Persian language, I
suppose it is valid for any language that uses the Arabic alifbat.
For my surprise in the original sketch the text scrolled from right
to left. Shouldn't it scroll from left to right to be properly read?
I do not understand Arabic or Persian. Despite I have taken on the
challenge of fixing the code.
-------------
Once caught several bugs, the message finally scrolls rightwards.
I have still left the original Persian message scrolling wrongwards
below my new one. As I do not understand Arabic, I have just taken
one from Internet in the sincere hope that it is polite enough.
Fixed bugs:
-27/05/2024
1. For debug sake its better to declare Serial.begin(9600);
2. The actual length of the message must be calculated like this
TXTWidth(msg.c_str()); and not TXTWidth(txt);
3. The macro definition
#define LCDWidth u8g2.getDisplayWidth()
does not compile well. I have changed it to
#define LCDWidth 128
Conclusion. For short texts in tiny hosts it may be desirable
displaying an image instead of a text, as proposed at:
https://wokwi.com/projects/398880937745114113
This way any language using non-latin characters could be written.
Re-written 2024 by raphik
*/
#include <Arduino.h>
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
int xPos, yPos, xxPos, yyPos;
char txt[] = "سلام علیکم"; // <-- this is Persian
char newTxt[] = "السلام علیکم ورحمة الله وبرکاته"; // <-- this is Arabic
String msg;
String newMsg;
//----------------------------------------------------------------------------
#define DEBUG
#define LCDHeight 64
#define TXTWidth(t) u8g2.getUTF8Width(t)
#define LCDWidth 128 // u8g2.getDisplayWidth()
#define ALINE_CENTER(t) ((LCDWidth - (u8g2.getUTF8Width(t))) / 2)
#define ALINE_RIGHT(t) (LCDWidth - u8g2.getUTF8Width(t))
#define ALINE_LEFT 0
#define N_DISTINCT_CHARACTERS 62
#define IS_UNICODE(c) (((c) & 0xc0) == 0xc0)
#define VERSION 1
//----------------------------------------------------------------------------
typedef struct prGlyph {
int AsciiCode;
char* codeGlyph;
char* isoGlyph;
char* iniGlyph;
char* midGlyph;
char* endGlyph;
};
const prGlyph prForms[] PROGMEM = {
// Ascii Code, Code, Isolated, Initial, Medial, Final
{193, "\u0621", "\uFE80", "\uFE80", "\uFE80", "\uFE80" }, //1 HAMZA ء [*]
{194, "\u0622", "\uFE81", "\uFE81", "\uFE82", "\uFE82" }, //2 ALEF_MADDA آ [*]
{195, "\u0623", "\uFE83", "\uFE83", "\uFE84", "\uFE84" }, //3 ALEF_HAMZA_ABOVE أ [*]
{196, "\u0624", "\uFE85", "\uFE85", "\uFE86", "\uFE86" }, //4 WAW_HAMZA ؤ [*]
{197, "\u0625", "\uFE87", "\uFE87", "\uFE88", "\uFE88" }, //5 ALEF_HAMZA_BELOW إ [*]
{198, "\u0626", "\uFE89", "\uFE8B", "\uFE8C", "\uFE8A" }, //6 YEH_HAMZA ئ [*]
{199, "\u0627", "\uFE8D", "\uFE8D", "\uFE8E", "\uFE8E" }, //7 ALEF ا [*]
{200, "\u0628", "\uFE8F", "\uFE91", "\uFE92", "\uFE90" }, //8 BEH ب
{555, "\u0629", "\uFE93", "\uFE93", "\uFE94", "\uFE94" }, //9 TEH_MARBUTA ة [*]
{202, "\u062A", "\uFE95", "\uFE97", "\uFE98", "\uFE96" }, //10 TEH ت
{203, "\u062B", "\uFE99", "\uFE9B", "\uFE9C", "\uFE9A" }, //11 THEH ث
{204, "\u062C", "\uFE9D", "\uFE9F", "\uFEA0", "\uFE9E" }, //12 JEEM ج
{205, "\u062D", "\uFEA1", "\uFEA3", "\uFEA4", "\uFEA2" }, //13 HAH ح
{206, "\u062E", "\uFEA5", "\uFEA7", "\uFEA8", "\uFEA6" }, //14 KHAH خ
{207, "\u062F", "\uFEA9", "\uFEA9", "\uFEAA", "\uFEAA" }, //15 DAL د [*]
{208, "\u0630", "\uFEAB", "\uFEAB", "\uFEAC", "\uFEAC" }, //16 THAL ذ [*]
{209, "\u0631", "\uFEAD", "\uFEAD", "\uFEAE", "\uFEAE" }, //17 REH ر [*]
{210, "\u0632", "\uFEAF", "\uFEAF", "\uFEB0", "\uFEB0" }, //18 ZAIN ز [*]
{184, "\u0698", "\uFB8A", "\uFB8A", "\uFB8B", "\uFB8B" }, //19 ZHEH ژ [*]
{211, "\u0633", "\uFEB1", "\uFEB3", "\uFEB4", "\uFEB2" }, //20 SEEN
{212, "\u0634", "\uFEB5", "\uFEB7", "\uFEB8", "\uFEB6" }, //21 SHEEN
{213, "\u0635", "\uFEB9", "\uFEBB", "\uFEBC", "\uFEBA" }, //22 SAD ص
{214, "\u0636", "\uFEBD", "\uFEBF", "\uFEC0", "\uFEBE" }, //23 DAD ض
{215, "\u0637", "\uFEC1", "\uFEC3", "\uFEC4", "\uFEC2" }, //24 TAH ط
{216, "\u0638", "\uFEC5", "\uFEC7", "\uFEC8", "\uFEC6" }, //25 ZAH ظ
{217, "\u0639", "\uFEC9", "\uFECB", "\uFECC", "\uFECA" }, //26 AIN ع
{218, "\u063A", "\uFECD", "\uFECF", "\uFED0", "\uFECE" }, //27 GHAIN غ
{160, "\u0640", "\u0640", "\u0640", "\u0640", "\u0640" }, //28 TATWEEL ـ
{161, "\u0641", "\uFED1", "\uFED3", "\uFED4", "\uFED2" }, //29 FEH ف
{162, "\u0642", "\uFED5", "\uFED7", "\uFED8", "\uFED6" }, //30 QAF ق
{163, "\u0643", "\uFED9", "\uFEDB", "\uFEDC", "\uFEDA" }, //31 KAF Arabic ك
{164, "\u0644", "\uFEDD", "\uFEDF", "\uFEE0", "\uFEDE" }, //32 LAM ل
{165, "\u0645", "\uFEE1", "\uFEE3", "\uFEE4", "\uFEE2" }, //33 MEEM م
{228, "\u0646", "\uFEE5", "\uFEE7", "\uFEE8", "\uFEE6" }, //34 NOON ن
{167, "\u0647", "\uFEE9", "\uFEEB", "\uFEEC", "\uFEEA" }, //35 HEH ه
{168, "\u0648", "\uFEED", "\uFEED", "\uFEEE", "\uFEEE" }, //36 WAW و [*]
{169, "\u0649", "\uFEEF", "\uFEEF", "\uFEF0", "\uFEF0" }, //37 ALEF_MAKSURA [*]
{170, "\u064A", "\uFEF1", "\uFEF3", "\uFEF4", "\uFEF2" }, //38 YEH Arabic ي
{172, "\u06CC", "\uFBFC", "\uFBFE", "\uFBFF", "\uFBFD" }, //39 YEH Farsi ی
{141, "\u0686", "\uFB7A", "\uFB7C", "\uFB7D", "\uFB7B" }, //40 CHEH چ
{222, "\u067E", "\uFB56", "\uFB58", "\uFB59", "\uFB57" }, //41 Peh پ
{144, "\u06AF", "\uFB92", "\uFB94", "\uFB95", "\uFB93" }, //42 Gaf گ
{201, "\u06A9", "\uFB8E", "\uFB90", "\uFB91", "\uFB8F" }, //43 Kaf ک
{32, "\u0020", "\u0020", "\u0020", "\u0020", "\u0020" }, //44 Space
{44, "\u060C", "\u060C", "\u060C", "\u060C", "\u060C" }, //45 Kama
{20, "\u200C", "\u200C", "\u200C", "\u200C","\u200C" }, //46 half-space
{58, "\u003A", "\u003A", "\u003A", "\u003A", "\u003A" }, //47 :
{187, "\u061B", "\u061B", "\u061B", "\u061B", "\u061B" }, //48 ؛
{46, "\u002E", "\u002E", "\u002E", "\u002E", "\u002E" }, //49 .
{191, "\u061F", "\u061F", "\u061F", "\u061F", "\u061F" }, //50 ؟
{48, "\u06F0", "\u06F0", "\u06F0", "\u06F0", "\u06F0" }, //51 0
{49, "\u06F1", "\u06F1", "\u06F1", "\u06F1", "\u06F1" }, //52 1
{50, "\u06F2", "\u06F2", "\u06F2", "\u06F2", "\u06F2" }, //53 2
{51, "\u06F3", "\u06F3", "\u06F3", "\u06F3", "\u06F3" }, //54 3
{52, "\u06F4", "\u06F4", "\u06F4", "\u06F4", "\u06F4" }, //55 4
{53, "\u06F5", "\u06F5", "\u06F5", "\u06F5", "\u06F5" }, //56 5
{54, "\u06F6", "\u06F6", "\u06F6", "\u06F6", "\u06F6" }, //57 6
{55, "\u06F7", "\u06F7", "\u06F7", "\u06F7", "\u06F7" }, //58 7
{56, "\u06F8", "\u06F8", "\u06F8", "\u06F8", "\u06F8" }, //59 8
{57, "\u06F9", "\u06F9", "\u06F9", "\u06F9", "\u06F9" }, //60 9
{41, "\u0028", "\u0028", "\u0028", "\u0028", "\u0028" }, //61 (
{40, "\u0029", "\u0029", "\u0029", "\u0029", "\u0029" } //62 )
};
//----------------------------------------------------------------------------
bool isFromTheSet1(unsigned char ch){
const unsigned char theSet1[18] = {
32, '\0', 199, 194, 207, 208, 209, 210,
184, 168, 191, 40, 41, 46, 33, 44, 58, 248};
int i = 0;
while (i < 18)
{
if(ch == theSet1[i])
return true;
++i;
}
return false;
}
//----------------------------------------------------------------------------
bool isFromTheSet2(unsigned char ch){
const unsigned char theSet1[10] = {
32, '\0', 191, 40, 41, 46, 33, 44,
58, 248 };
int i = 0;
while (i < 10)
{
if(ch == theSet1[i])
return true;
++i;
}
return false;
}
//----------------------------------------------------------------------------
int FindGlyph(unsigned char chFind){
for (int i = 0; i < N_DISTINCT_CHARACTERS; i++) {
if (pgm_read_word(&(prForms[i].AsciiCode)) == chFind) {
return i;
break;
}
}
return -1;
}
//----------------------------------------------------------------------------
String prReshaper(char *Text){
String prBuffer = "";
int stat = 0;
unsigned char pLetter = ' '; //Previous word
unsigned char letter; //Letter
unsigned char nLetter; //Next word
unsigned char temp;
while(temp = *Text++){
//is Number ?
if (temp >= '0' && temp <= '9') {
//d = temp - '0';
letter = temp;
}
else if(temp >= 128){
letter = *Text++;
letter += 32;
temp += 32;
if(letter == 207){
if(temp == 218 || temp == 250){
letter = 144; //گ
}
}
else if(letter == 166)
{
if(temp == 218 || temp == 250){ //چ
letter = 141;
}
else
{
letter = 228; //ن
}
}
}
else
{
letter = temp;
}
//
if(letter == 172)
{
if(temp == 248 || temp == 32)
{
letter = 44;
}
}
temp = *Text++;
if(temp >= 128)
{
nLetter = *Text++;
nLetter += 32;
temp += 32;
if(nLetter == 207)
{
if(temp == 218 || temp == 250)
{
nLetter = 144; //گ
}
}
else if(nLetter == 166)
{
if(temp == 218 || temp == 250)
{ //چ
nLetter = 141;
}
else
{
nLetter = 228; //ن
}
}
*Text--;
*Text--;
}
else
{
nLetter = temp;
*Text--;
}
//
if(nLetter == 172)
{
if(temp == 248 || temp == 32)
{
nLetter = 44;
}
}
int isunk = 0;
/*
Final: at the end of the word.
Medial: at the middle of the word.
Initial: at the beginning of the word.
Isolated: the character alone (not part of a word).
*/
if (isFromTheSet1(pLetter))
if (isFromTheSet2(nLetter))
stat = 0; //Isolated
else
stat = 1; //Initial
else
if (isFromTheSet2(nLetter))
stat = 2; //Final
else
stat = 3; //Medial
int number = FindGlyph(letter);
#ifdef DEBUG
Serial.print("Letter code: ");
Serial.println(letter);
Serial.print("Number is: ");
Serial.println(number);
Serial.print("Pos: ");
Serial.println(stat);
Serial.println("--------------");
#endif
switch (stat){
case 0: //Isolated
prBuffer += (char*)pgm_read_word(&(prForms[number].isoGlyph));
break;
case 1: //Initial
prBuffer += (char*)pgm_read_word(&(prForms[number].iniGlyph));
break;
case 2: //Final
prBuffer += (char*)pgm_read_word(&(prForms[number].endGlyph));
break;
case 3: //Medial
prBuffer += (char*)pgm_read_word(&(prForms[number].midGlyph));
break;
default:
isunk = 1;
break;
}
if(isunk == 0)
pLetter = letter;
}
//utf8rev(prBuffer.c_str());
//https://stackoverflow.com/questions/20984220/invalid-conversion-from-const-char-to-char
utf8rev((char *)(prBuffer.c_str()));
return prBuffer;
}
//----------------------------------------------------------------------------
// https://stackoverflow.com/questions/199260/how-do-i-reverse-a-utf-8-string-in-place
void utf8rev(char *str){
/* this assumes that str is valid UTF-8 */
char *scanl, *scanr, *scanr2, c;
/* first reverse the string */
for (scanl = str, scanr = str + strlen(str); scanl < scanr;)
c = *scanl, *scanl++= *--scanr, *scanr= c;
/* then scan all bytes and reverse each multibyte character */
for (scanl = scanr = str; c = *scanr++;) {
if ( (c & 0x80) == 0) // ASCII char
scanl = scanr;
else if ( (c & 0xc0) == 0xc0 ) { // start of multibyte
scanr2 = scanr;
switch (scanr - scanl) {
case 4: c = *scanl, *scanl++= *--scanr, *scanr = c; // fallthrough
case 3: // fallthrough
case 2: c = *scanl, *scanl++= *--scanr, *scanr = c;
}
scanr = scanl = scanr2;
}
}
}
//----------------------------------------------------------------------------
//UTF-8 strlen function
int strlen_utf8(char *s) {
int i = 0, j = 0;
while (s[i]) {
if ((s[i] & 0xc0) != 0x80) j++;
i++;
}
return j;
}
// This example shows a scrolling text.
// If U8G2_16BIT is not set (default), then the pixel width of the text must be lesser than 128
// If U8G2_16BIT is set, then the pixel width an be up to 32000
void draw(void)
{
u8g2.drawUTF8(xPos, 20, newMsg.c_str());
u8g2.drawUTF8(LCDWidth - xxPos, 50, msg.c_str());
//u8g2.drawUTF8(ALINE_CENTER(txt), yPos, msg.c_str());
//u8g2.drawUTF8(ALINE_CENTER(txt), yyPos, msg.c_str());
}
void setup(void)
{
Serial.begin(9600);
u8g2.begin();
//Set font
//u8g2.setFont(u8g2_font_samim_12_t_all); //Number view in both modes. (English & Persian)
//u8g2.setFont(u8g2_font_iranian_sans_16_t_all); //Number view in both modes. (English & Persian)
//u8g2.setFont(u8g2_font_ganj_nameh_sans16_t_all); //No number view.
//u8g2.setFont(u8g2_font_samim_fd_16_t_all); //By default, this font displays numbers in Persian.
u8g2.setFont(u8g2_font_iranian_sans_14_t_all);
u8g2.enableUTF8Print();
//u8g2.setColorIndex(0); // BW display
u8g2.setFontMode(0); // enable transparent mode, which is faster
msg = prReshaper(txt);
newMsg = prReshaper(newTxt);
xPos = -TXTWidth(newMsg.c_str());
yyPos = LCDHeight + 20;
}
void loop(void)
{
// picture loop
u8g2.firstPage();
do {
draw();
} while (u8g2.nextPage());
//if (++xPos >= LCDWidth) xPos = -TXTWidth(txt); //Left to Right
if (++xPos >= LCDWidth) xPos = -TXTWidth(newMsg.c_str());
if (++xxPos >= LCDWidth + TXTWidth(txt)) xxPos = 0; //Right to Left
// if (++yPos >= TXTWidth(txt)) yPos = 0; //UP to Down
// if (--yyPos <= 0) yyPos = LCDHeight + 20; //Down to UP
delay(5);
}