-
-
Notifications
You must be signed in to change notification settings - Fork 267
Open
Labels
enhancementNew feature or requestNew feature or requestpinnedexempt from stale botexempt from stale bot
Description
I have spent the last week, trying to display arabic text on the screen
Suggested Solution
I would like you to either add a font that comes by default for arabic, just like for chinese, korean, etc.
OR
Add documentation on how to use an arabic font.
Additional Context
I gave up on ChatGPT after it was just making things up. then after searching around everywhere I found this solution.
- Download some arabic tft font, from anywhere, example Amiri from google fonts
- Download otf2bdf and run:
otf2bdf -r 72 -p 32 Amiri.ttf -o amiri.bdf - Download bdfconv and run:
bdfconv -v -f 1 -m "0-127,1536-1791,65136-65279" amiri.bdf -o amiri_u8g2.c -n amiri_u8g2 -d amiri.bdf
This includes the basic english letters, isolated arabic letters, and arabic letters when connected to other letters. - The arabic letters will be all isolated when you simply do for example:
lcd.print("السلام عليكم\n");so you will need to create a function or use a library which gives tells lgfx the specific unicode of each character to print.
I'll edit say how when I have step 4 done.
Edit:
This is the way I'm currently displaying arabic text with all the logic to make words connected. (I could have used I library to connect letters but I chose not to)
NOTE: This is code for the ESP32, it can be made to work with arduino with a few changes. (hint: chatgpt can do it)
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_freertos_hooks.h"
#include "lgfx/v1/lgfx_fonts.hpp"
#include <cstdint>
#define LGFX_AUTODETECT
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
#include <amiri_u8g2.c>
LGFX lcd;
lgfx::U8g2font amiri_font (amiri_u8g2);
struct ArabicWord {
std::u16string word;
uint16_t wordWidth = 0;
std::vector<uint8_t> letterWidths;
};
struct ArabicMapping {
uint16_t base, initial, medial, final;
};
static ArabicMapping arabic_map[] = {
{0x0621, 0xFE80, 0xFE80, 0xFE80}, // Hamza
{0x0622, 0xFE81, 0xFE82, 0xFE82}, // Alef Madda
{0x0623, 0xFE83, 0xFE84, 0xFE84}, // Alef Hamza Above
{0x0624, 0xFE85, 0xFE86, 0xFE86}, // Waw Hamza Above
{0x0625, 0xFE87, 0xFE88, 0xFE88}, // Alef Hamza Below
{0x0626, 0xFE8B, 0xFE8C, 0xFE8A}, // Yeh Hamza
{0x0627, 0xFE8D, 0xFE8E, 0xFE8E}, // Alef
{0x0628, 0xFE91, 0xFE92, 0xFE90}, // Ba
{0x0629, 0xFE93, 0xFE94, 0xFE94}, // Teh Marbuta
{0x062A, 0xFE97, 0xFE98, 0xFE96}, // Ta
{0x062B, 0xFE9B, 0xFE9C, 0xFE9A}, // Tha
{0x062C, 0xFE9F, 0xFEA0, 0xFE9E}, // Jeem
{0x062D, 0xFEA3, 0xFEA4, 0xFEA2}, // Hah
{0x062E, 0xFEA7, 0xFEA8, 0xFEA6}, // Khah
{0x062F, 0xFEA9, 0xFEAA, 0xFEAA}, // Dal
{0x0630, 0xFEAB, 0xFEAC, 0xFEAC}, // Thal
{0x0631, 0xFEAD, 0xFEAE, 0xFEAE}, // Ra
{0x0632, 0xFEAF, 0xFEB0, 0xFEB0}, // Zain
{0x0633, 0xFEB3, 0xFEB4, 0xFEB2}, // Seen
{0x0634, 0xFEB7, 0xFEB8, 0xFEB6}, // Sheen
{0x0635, 0xFEBB, 0xFEBC, 0xFEBA}, // Sad
{0x0636, 0xFEBF, 0xFEC0, 0xFEBE}, // Dad
{0x0637, 0xFEC3, 0xFEC4, 0xFEC2}, // Tah
{0x0638, 0xFEC7, 0xFEC8, 0xFEC6}, // Zah
{0x0639, 0xFECB, 0xFECC, 0xFECA}, // Ain
{0x063A, 0xFECF, 0xFED0, 0xFECE}, // Ghain
{0x0641, 0xFED3, 0xFED4, 0xFED2}, // Fa
{0x0642, 0xFED7, 0xFED8, 0xFED6}, // Qaf
{0x0643, 0xFEDB, 0xFEDC, 0xFEDA}, // Kaf
{0x0644, 0xFEDF, 0xFEE0, 0xFEDE}, // Lam
{0x0645, 0xFEE3, 0xFEE4, 0xFEE2}, // Meem
{0x0646, 0xFEE7, 0xFEE8, 0xFEE6}, // Noon
{0x0647, 0xFEEB, 0xFEEC, 0xFEEA}, // Ha
{0x0648, 0xFEED, 0xFEEE, 0xFEEE}, // Waw
{0x0649, 0xFEEF, 0xFEE0, 0xFEF0}, // Alef Layina
{0x064A, 0xFEF3, 0xFEF4, 0xFEF2}, // Yeh
{0xFEF5, 0xFEF5, 0xFEF6, 0xFEF5}, // Lam & Alef Hamza Madda
{0xFEF7, 0xFEF7, 0xFEF8, 0xFEF7}, // Lam & Alef Hamza Above
{0xFEF9, 0xFEF9, 0xFEFA, 0xFEF9}, // Lam & Alef Hamza Below
{0xFEFB, 0xFEFB, 0xFEFC, 0xFEFB}, // Lam & Alef
};
static bool isLetter(uint16_t character) {
for (auto &entry : arabic_map)
if (entry.base == character)
return true;
return false;
}
static const ArabicMapping* getArabicMapping(uint16_t character) {
for (auto &entry : arabic_map)
if (entry.base == character)
return &entry;
return nullptr;
}
static std::vector<ArabicWord> shapeArabicText(const std::u16string &input) {
lgfx::FontMetrics ch_metric;
std::vector<ArabicWord> text;
text.emplace_back();
bool connectPrevious = false;
bool connectNext = false;
for (uint16_t i = 0; i < input.size(); i++) {
uint16_t ch = input[i];
// If new word
if (ch == u' ') {
if (!text.back().word.empty()) text.emplace_back();
text.back().word.push_back(u' ');
text.back().letterWidths.push_back(lcd.textWidth(" "));
text.back().wordWidth = text.back().letterWidths[0];
connectPrevious = false;
text.emplace_back();
} else {
// All just for Lam & Alef
if ((ch == u'ا' || ch == u'أ' || ch == u'إ' || ch == u'آ') && !text.back().word.empty()) {
uint8_t word_size = text.back().word.size();
bool lam_found = false;
uint8_t j = 1;
while (1) {
if (j == word_size) { break; }
ESP_LOGI("H", "HI1");
if (input[i - j] == u'ل') {
ESP_LOGI("H", "HI2");
text.back().wordWidth -= text.back().letterWidths.back();
text.back().word.erase(word_size - j);
text.back().letterWidths.erase(text.back().letterWidths.begin() + (word_size - j));
switch (ch) {
case u'ا': ch = 0xFEFB; break;
case u'أ': ch = 0xFEF7; break;
case u'إ': ch = 0xFEF9; break;
case u'آ': ch = 0xFEF5; break;
}
lam_found = true;
}
else if (isLetter(input[i - j])) {
ESP_LOGI("H", "HI3, %x", input[i-j]);
if (lam_found) {
ESP_LOGI("H", "HI4");
switch (input[i - j]) {
case u'أ': case u'إ': case u'آ': case u'ا': case u'و': case u'ز': case u'ر': case u'ذ': case u'د':
connectPrevious = false;
}
}
break;
}
j++;
}
}
// Get all possible version of the letter. (If returned null, then ch is not an arabic letter)
auto mapping = getArabicMapping(ch);
if (!mapping) {
text.back().word.push_back(ch);
amiri_font.updateFontMetric(&ch_metric, ch);
text.back().wordWidth += ch_metric.x_advance;
text.back().letterWidths.push_back(ch_metric.x_advance);
continue;
}
// Find next letter in word
uint16_t j = i + 1;
while (1) {
if (j > input.size() || input[j] == ' ') { connectNext = false; break; }
else if (isLetter(input[j])) { connectNext = true; break; }
j++;
}
// Set correct letter version
uint16_t shapedCh;
if (!connectPrevious && !connectNext) shapedCh = mapping->base;
else if (connectPrevious && connectNext) shapedCh = mapping->medial;
else if (connectPrevious && !connectNext) shapedCh = mapping->final;
else shapedCh = mapping->initial;
// Set letter and it's width
text.back().word.push_back(shapedCh);
amiri_font.updateFontMetric(&ch_metric, shapedCh);
text.back().wordWidth += ch_metric.x_advance;
text.back().letterWidths.push_back(ch_metric.x_advance);
// Set that the next letter can connect to this letter
connectPrevious = true;
switch (ch) {
case u'أ': case u'إ': case u'آ': case u'ا': case u'و': case u'ز': case u'ر': case u'ذ': case u'د':
connectPrevious = false;
}
// Same as above, specific to Lam & Alef
switch (shapedCh) {
case 0xFEF5: case 0xFEF6: case 0xFEF7: case 0xFEF8: case 0xFEF9: case 0xFEFA: case 0xFEFB: case 0xFEFC:
connectPrevious = false;
}
}
}
return text;
}
void loop(void*)
{
lcd.init();
lcd.setTextColor(TFT_WHITE);
lcd.setRotation(1); // Make screen landscape
lcd.clear();
lcd.setFont(&amiri_font);
lcd.setCursor(20, 30);
uint16_t scr_width = lcd.width();
std::u16string text = u"الْحَمْدُ لِلَّهِ رَبِّ الْعَالمينَ، الرَّحْمَٰنِ الرَّحِيمِ، مَلِكِ يَوْمِ الدِّينِ,,,, - السلام عليكم";
std::vector<ArabicWord> shapedText = shapeArabicText(text);
// Figure out the line widths (only to centre lines horizontally)
std::vector<uint16_t> lineWidths;
lineWidths.emplace_back();
uint8_t margin = 10;
for (auto word : shapedText) {
if (lineWidths.back() + word.wordWidth > scr_width - margin) lineWidths.push_back(margin);
lineWidths.back() += word.wordWidth;
}
// Draw letters
uint8_t j = 0;
uint16_t y = 60;
int16_t x = scr_width - margin - ((scr_width - lineWidths[j]) / 2);
for (auto word : shapedText) {
if (x - word.wordWidth < margin) {
j++;
x = scr_width - margin - ((scr_width - lineWidths[j]) / 2);
y += 60;
}
for (uint16_t i=0; i < word.word.size(); i++) {
lcd.drawChar(word.word[i], x - word.letterWidths[i], y);
x -= word.letterWidths[i];
}
}
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
extern "C" void app_main(void) {
xTaskCreate(loop, "app", 8192, NULL, 1, NULL);
}Thanks in advance.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requestpinnedexempt from stale botexempt from stale bot