Basic utf8 support for B&W radios

This commit is contained in:
Raphael Coeffic
2022-06-11 15:38:08 +02:00
committed by Raphael Coeffic
parent 86152da85d
commit 30e3ac3399
10 changed files with 259 additions and 77 deletions
+5
View File
@@ -22,6 +22,7 @@
#include <limits.h>
#include "opentx.h"
#include "gui/common/stdlcd/fonts.h"
#include "gui/common/stdlcd/utf8.h"
pixel_t displayBuf[DISPLAY_BUFFER_SIZE] __DMA;
@@ -335,6 +336,10 @@ void lcdDrawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdFlag
break;
}
else if (c >= 0x20) {
// UTF8 detection
c = map_utf8_char(s, len);
if (!c) break;
lcdDrawChar(x, y, c, flags);
x = lcdNextPos;
}
+5
View File
@@ -22,6 +22,7 @@
#include <limits.h>
#include "opentx.h"
#include "common/stdlcd/fonts.h"
#include "common/stdlcd/utf8.h"
#if (defined(PCBX9E) || defined(PCBX9DP)) && defined(LCD_DUAL_BUFFER)
pixel_t displayBuf1[DISPLAY_BUFFER_SIZE] __DMA;
@@ -295,6 +296,10 @@ void lcdDrawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdFlag
break;
}
else if (c >= 0x20) {
// UTF8 detection
c = map_utf8_char(s, len);
if (!c) break;
if (c == 46 && FONTSIZE(flags) == TINSIZE) { // '.' handling
if (((flags & BLINK) && BLINK_ON_PHASE) || ((!(flags & BLINK) && (flags & INVERS)))) {
lcdDrawSolidVerticalLine(x, y-1, 5);
@@ -5,6 +5,7 @@ set(GUI_SRC
../common/stdlcd/menus.cpp
../common/stdlcd/draw_functions.cpp
../common/stdlcd/fonts.cpp
../common/stdlcd/utf8.cpp
../common/stdlcd/popups.cpp
../common/stdlcd/model_inputs.cpp
../common/stdlcd/model_mixes.cpp
+95
View File
@@ -0,0 +1,95 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/* -*- coding: utf-8 -*- */
#include <stdint.h>
#include "definitions.h"
#if defined(TRANSLATIONS_FR)
static wchar_t _utf8_lut[] = {
L'é', L'è', L'à', L'î', L'ç',
};
#elif defined(TRANSLATIONS_DE)
static wchar_t _utf8_lut[] = {
L'Ä', L'ä', L'Ö', L'ö', L'Ü', L'ü', L'ß',
};
#elif defined(TRANSLATIONS_CZ)
static wchar_t _utf8_lut[] = {
L'á', L'č', L'é', L'ě', L'í', L'ó', L'ř',
L'š', L'ú', L'ů', L'ý', L'Í', L'Ř', L'Ý',
L'ž', L'É',
};
#elif defined(TRANSLATIONS_ES)
static wchar_t _utf8_lut[] = {
L'Ñ', L'ñ', L'Á', L'á', L'É', L'é', L'Í',
L'í', L'Ó', L'ó', L'ú',
};
#elif defined(TRANSLATIONS_FI)
static wchar_t _utf8_lut[] = {
L'å', L'ä', L'ö', L'Å', L'Ä', L'Ö',
};
#elif defined(TRANSLATIONS_IT)
static wchar_t _utf8_lut[] = {
L'à', L'ù',
};
#elif defined(TRANSLATIONS_PL)
static wchar_t _utf8_lut[] = {
L'ą', L'ć', L'ę', L'ł', L'ń', L'ó', L'ś',
L'ż', L'ź', L'Ą', L'Ć', L'Ę', L'Ł', L'Ń',
L'Ó', L'Ś', L'Ż', L'Ź',
};
#else
#define NO_UTF8_LUT
#endif
#if !defined(NO_UTF8_LUT)
static unsigned char lookup_utf8_mapping(wchar_t w)
{
for (int i=0; i < DIM(_utf8_lut); i++) {
if (w == _utf8_lut[i])
return 0x95 + (uint8_t)i; // TODO: use constant
}
return 0x20; // return 'space' for unknown chars
}
#endif
unsigned char map_utf8_char(const char*& s, uint8_t& len)
{
unsigned char c = *s;
if ((c & 0xC0) == 0xC0) {
wchar_t w = (c & 0x1F) << 6;
if (!len) return 0;
len--; s++; c = *s;
w |= c & 0x3F;
// TODO: use constants
if (w >= 0x80 && w <= 0x94) { // extra chars
return (unsigned char)w;
}
#if !defined(NO_UTF8_LUT)
return lookup_utf8_mapping(w);
#else
return 0x20;
#endif
}
return c;
}
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
unsigned char map_utf8_char(const char*& s, uint8_t& len);
+5 -2
View File
@@ -60,14 +60,17 @@ static void sdReadTextFile(const char * filename, char lines[TEXT_VIEWER_LINES][
else if (c != '\\' && escape > 0 && escape < sizeof(escape_chars)) {
escape_chars[escape - 1] = c;
if (escape == 2 && !strncmp(escape_chars, "up", 2)) {
c = CHAR_UP;
lines[current_line-menuVerticalOffset][line_length++] = STR_CHAR_UP[0];
c = STR_CHAR_UP[1];
}
else if (escape == 2 && !strncmp(escape_chars, "dn", 2)) {
c = CHAR_DOWN;
lines[current_line-menuVerticalOffset][line_length++] = STR_CHAR_DOWN[0];
c = STR_CHAR_DOWN[1];
}
else if (escape == 3) {
int val = atoi(escape_chars);
if (val >= 200 && val < 225) {
lines[current_line-menuVerticalOffset][line_length++] = '\302';
c = '\200' + val - 200;
}
}
+45 -37
View File
@@ -571,23 +571,24 @@ char *getSwitchPositionName(char *dest, swsrc_t idx)
return dest;
}
// this should be declared in header, but it used so much foreign symbols that we declare it in cpp-file and pre-instantiate it for the uses
template<size_t L>
char *getSourceString(char (&dest)[L], mixsrc_t idx)
{
size_t dest_len = L;
if (idx == MIXSRC_NONE) {
return getStringAtIndex(dest, STR_VSRCRAW, 0);
} else if (idx <= MIXSRC_LAST_INPUT) {
idx -= MIXSRC_FIRST_INPUT;
*dest = CHAR_INPUT;
if (strlen(g_model.inputNames[idx])) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(dest + 1, L - 1, "%.*s", (int)sizeof(g_model.inputNames[idx]), g_model.inputNames[idx]);
#pragma GCC diagnostic pop
static_assert(L > sizeof(STR_CHAR_INPUT) - 1, "dest string too small");
dest_len -= sizeof(STR_CHAR_INPUT) - 1;
char* pos = strAppend(dest, STR_CHAR_INPUT, sizeof(STR_CHAR_INPUT) - 1);
if (g_model.inputNames[idx][0] != '\0' && (dest_len > sizeof(g_model.inputNames[idx]))) {
memset(pos, 0, sizeof(g_model.inputNames[idx]) + 1);
strncpy(pos, g_model.inputNames[idx], sizeof(g_model.inputNames[idx]));
} else {
strAppendUnsigned(dest + 1, idx + 1, 2);
strAppendUnsigned(pos, idx + 1, 2);
}
}
@@ -595,46 +596,52 @@ char *getSourceString(char (&dest)[L], mixsrc_t idx)
else if (idx <= MIXSRC_LAST_LUA) {
#if defined(LUA_MODEL_SCRIPTS)
div_t qr = div(idx - MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS);
if (qr.quot < MAX_SCRIPTS && qr.rem < scriptInputsOutputs[qr.quot].outputsCount) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
// possible truncation is intentional
snprintf(dest + 1, L - 1, "%.*s", (int)sizeof(g_model.scriptsData[qr.quot].name), g_model.scriptsData[qr.quot].name);
// instance Name is empty : dest = n-ScriptFileName/OutputName
if (*(dest + 1) == '\0') {
snprintf(dest + 1, L - 1, "%d-%.*s/%.*s", qr.quot + 1,
if (qr.quot < MAX_SCRIPTS &&
qr.rem < scriptInputsOutputs[qr.quot].outputsCount) {
static_assert(L > sizeof(STR_CHAR_LUA) - 1, "dest string too small");
dest_len -= sizeof(STR_CHAR_LUA) - 1;
char* pos = strAppend(dest, STR_CHAR_LUA, sizeof(STR_CHAR_LUA) - 1);
if (g_model.scriptsData[qr.quot].name[0] != '\0') {
// instance Name is not empty : dest = InstanceName/OutputName
snprintf(pos, dest_len, "%.*s/%.*s", sizeof(g_model.scriptsData[qr.quot].name), g_model.scriptsData[qr.quot].name,
(int)sizeof(scriptInputsOutputs[qr.quot].outputs[qr.rem].name), scriptInputsOutputs[qr.quot].outputs[qr.rem].name);
} else {
// instance Name is empty : dest = n-ScriptFileName/OutputName
snprintf(pos, dest_len, "%d-%.*s/%.*s", qr.quot + 1,
(int)sizeof(g_model.scriptsData[qr.quot].file), g_model.scriptsData[qr.quot].file,
(int)sizeof(scriptInputsOutputs[qr.quot].outputs[qr.rem].name), scriptInputsOutputs[qr.quot].outputs[qr.rem].name);
// instance Name is not empty : dest = InstanceName/OutputName
} else {
snprintf(dest + 1, L - 1, "%.*s/%.*s", LEN_SCRIPT_NAME, g_model.scriptsData[qr.quot].name,
(int)sizeof(scriptInputsOutputs[qr.quot].outputs[qr.rem].name), scriptInputsOutputs[qr.quot].outputs[qr.rem].name);
}
#pragma GCC diagnostic pop
*dest = CHAR_LUA;
}
#else
strcpy(dest, "N/A");
strncpy(dest, "N/A", L-1);
#endif
}
#endif
else if (idx <= MIXSRC_LAST_POT) {
if (g_eeGeneral.anaNames[idx - MIXSRC_Rud][0]) {
// TODO: add correct symbol
char* pos = dest;
if (idx <= MIXSRC_LAST_STICK) {
dest[0] = CHAR_STICK;
pos = strAppend(pos, STR_CHAR_STICK, sizeof(STR_CHAR_STICK) - 1);
dest_len -= sizeof(STR_CHAR_STICK) - 1;
#if NUM_SLIDERS > 0
} else if (idx < MIXSRC_FIRST_SLIDER) {
dest[0] = CHAR_POT;
pos = strAppend(pos, STR_CHAR_POT, sizeof(STR_CHAR_POT) - 1);
dest_len -= sizeof(STR_CHAR_POT) - 1;
} else {
dest[0] = CHAR_SLIDER;
pos = strAppend(pos, STR_CHAR_SLIDER, sizeof(STR_CHAR_SLIDER) - 1);
dest_len -= sizeof(STR_CHAR_SLIDER) - 1;
#else
} else {
dest[0] = CHAR_POT;
pos = strAppend(pos, STR_CHAR_POT, sizeof(STR_CHAR_POT) - 1);
dest_len -= sizeof(STR_CHAR_POT) - 1;
#endif
}
idx -= MIXSRC_Rud;
copyToTerminated(dest, g_eeGeneral.anaNames[idx], offset_t<1>{});
size_t ana_len = std::min(sizeof(g_eeGeneral.anaNames[idx]), dest_len - 1);
strncpy(pos, g_eeGeneral.anaNames[idx], ana_len);
pos[ana_len] = '\0';
} else {
idx -= MIXSRC_Rud;
getStringAtIndex(dest, STR_VSRCRAW, idx + 1);
@@ -645,7 +652,7 @@ char *getSourceString(char (&dest)[L], mixsrc_t idx)
} else if (idx <= MIXSRC_LAST_SWITCH) {
idx -= MIXSRC_FIRST_SWITCH;
if (g_eeGeneral.switchNames[idx][0] != '\0') {
copyToTerminated(dest, g_eeGeneral.switchNames[idx], offset_t<1>{});
copyToTerminated(dest, g_eeGeneral.switchNames[idx]);
} else {
getStringAtIndex(dest, STR_VSRCRAW,
idx + MIXSRC_FIRST_SWITCH - MIXSRC_Rud + 1);
@@ -676,13 +683,13 @@ char *getSourceString(char (&dest)[L], mixsrc_t idx)
} else {
idx -= MIXSRC_FIRST_TELEM;
div_t qr = div(idx, 3);
dest[0] = CHAR_TELEMETRY;
char *pos = strAppend(&dest[1], g_model.telemetrySensors[qr.quot].label,
sizeof(g_model.telemetrySensors[qr.quot].label));
char* pos = strAppend(dest, STR_CHAR_TELEMETRY, 2);
pos = strAppend(pos, g_model.telemetrySensors[qr.quot].label,
sizeof(g_model.telemetrySensors[qr.quot].label));
if (qr.rem) *pos = (qr.rem == 2 ? '+' : '-');
*++pos = '\0';
}
dest[L - 1] = '\0'; // assert the termination (should not be nesseccary)
dest[L - 1] = '\0'; // assert the termination
return dest;
}
@@ -755,14 +762,15 @@ char *strAppendFilename(char *dest, const char *filename, const int size)
return dest;
}
std::string formatNumberAsString(int32_t val, LcdFlags flags, uint8_t len, const char * prefix, const char * suffix)
#if defined(LIBOPENUI)
std::string formatNumberAsString(int32_t val, LcdFlags flags, uint8_t len,
const char *prefix, const char *suffix)
{
char s[100];
BitmapBuffer::formatNumberAsString(s, 99, val, flags, len, prefix, suffix);
return std::string(s);
}
#endif
#if defined(RTCLOCK)
#include "rtc.h"
+22 -5
View File
@@ -745,6 +745,14 @@ void checkSwitches()
LED_ERROR_END();
}
#elif defined(GUI)
static const char* switch_state_str[] {
" ",
STR_CHAR_UP,
"-",
STR_CHAR_DOWN,
};
void checkSwitches()
{
swarnstate_t last_bad_switches = 0xff;
@@ -780,10 +788,9 @@ void checkSwitches()
((states & mask) == (switches_states & mask)) ? 0 : INVERS;
if (attr) {
if (++numWarnings < 6) {
char c = (" " STR_CHAR_UP
"-" STR_CHAR_DOWN)[(states & mask) >> (i * 3)];
const char* s = switch_state_str[(states & mask) >> (i * 3)];
drawSource(x, y, MIXSRC_FIRST_SWITCH + i, attr);
lcdDrawChar(lcdNextPos, y, c, attr);
lcdDrawText(lcdNextPos, y, s, attr);
x = lcdNextPos + 3;
}
}
@@ -801,9 +808,19 @@ void checkSwitches()
if (++numWarnings < 6) {
lcdDrawTextAtIndex(x, y, STR_VSRCRAW, NUM_STICKS + 1 + i, INVERS);
if (IS_POT(POT1 + i))
lcdDrawChar(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? 126 : 127, INVERS); // TODO: use constants for chars
lcdDrawChar(
lcdNextPos, y,
g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i)
? 126
: 127,
INVERS); // TODO: use constants for chars
else
lcdDrawChar(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? CHAR_UP : CHAR_DOWN, INVERS);
lcdDrawText(
lcdNextPos, y,
g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i)
? STR_CHAR_UP
: STR_CHAR_DOWN,
INVERS);
x = lcdNextPos + 3;
}
}
+40
View File
@@ -18,6 +18,7 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/* -*- coding: utf-8 -*- */
#include <QtCore/QDir>
#include <QtCore/QDebug>
@@ -267,6 +268,45 @@ TEST(Lcd, Dblsize)
EXPECT_TRUE(checkScreenshot("dblsize"));
}
#define TEST_CHAR_RIGHT "\302\200"
#define TEST_CHAR_LEFT "\302\201"
#define TEST_CHAR_UP "\302\202"
#define TEST_CHAR_DOWN "\302\203"
#define TEST_CHAR_DELTA "\302\210"
#define TEST_CHAR_STICK "\302\211"
#define TEST_CHAR_POT "\302\212"
#define TEST_CHAR_SLIDER "\302\213"
#define TEST_CHAR_SWITCH "\302\214"
#define TEST_CHAR_TRIM "\302\215"
#define TEST_CHAR_INPUT "\302\216"
#define TEST_CHAR_FUNCTION "\302\217"
#define TEST_CHAR_CYC "\302\220"
#define TEST_CHAR_TRAINER "\302\221"
#define TEST_CHAR_CHANNEL "\302\222"
#define TEST_CHAR_TELEMETRY "\302\223"
#define TEST_CHAR_LUA "\302\224"
//#define EXTRA_TEST TEST_CHAR_RIGHT TEST_CHAR_LEFT TEST_CHAR_UP TEST_CHAR_DOWN
#define EXTRA_TEST TEST_CHAR_DELTA TEST_CHAR_STICK TEST_CHAR_POT TEST_CHAR_SLIDER \
TEST_CHAR_SWITCH TEST_CHAR_TRIM TEST_CHAR_INPUT
TEST(Lcd, Extra_chars)
{
lcdClear();
lcdDrawText(0, 10, EXTRA_TEST);
EXPECT_TRUE(checkScreenshot("extra_chars"));
}
#if defined(TRANSLATIONS_FR)
TEST(Lcd, fr_chars)
{
lcdClear();
lcdDrawText(0, 10, u8"éèàîç");
EXPECT_TRUE(checkScreenshot("fr_chars"));
}
#endif
#if defined(PCBTARANIS) && LCD_W >= 212
TEST(Lcd, DrawSwitch)
{
+17 -33
View File
@@ -336,40 +336,24 @@
#define TR_SENSOR_SBEC_VOLTAGE "BecV"
#define TR_SENSOR_SBEC_CURRENT "BecA"
#define STR_CHAR_RIGHT "\200"
#define STR_CHAR_LEFT "\201"
#define STR_CHAR_UP "\202"
#define STR_CHAR_DOWN "\203"
#define STR_CHAR_RIGHT "\302\200"
#define STR_CHAR_LEFT "\302\201"
#define STR_CHAR_UP "\302\202"
#define STR_CHAR_DOWN "\302\203"
#define STR_CHAR_DELTA "\210"
#define STR_CHAR_STICK "\211"
#define STR_CHAR_POT "\212"
#define STR_CHAR_SLIDER "\213"
#define STR_CHAR_SWITCH "\214"
#define STR_CHAR_TRIM "\215"
#define STR_CHAR_INPUT "\216"
#define STR_CHAR_FUNCTION "\217"
#define STR_CHAR_CYC "\220"
#define STR_CHAR_TRAINER "\221"
#define STR_CHAR_CHANNEL "\222"
#define STR_CHAR_TELEMETRY "\223"
#define STR_CHAR_LUA "\224"
#define CHAR_UP STR_CHAR_UP[0]
#define CHAR_DOWN STR_CHAR_DOWN[0]
#define CHAR_DELTA STR_CHAR_DELTA[0]
#define CHAR_STICK STR_CHAR_STICK[0]
#define CHAR_POT STR_CHAR_POT[0]
#define CHAR_SLIDER STR_CHAR_SLIDER[0]
#define CHAR_SWITCH STR_CHAR_SWITCH[0]
#define CHAR_TRIM STR_CHAR_TRIM[0]
#define CHAR_INPUT STR_CHAR_INPUT[0]
#define CHAR_FUNCTION STR_CHAR_FUNCTION[0]
#define CHAR_CYC STR_CHAR_CYC[0]
#define CHAR_TRAINER STR_CHAR_TRAINER[0]
#define CHAR_CHANNEL STR_CHAR_CHANNEL[0]
#define CHAR_TELEMETRY STR_CHAR_TELEMETRY[0]
#define CHAR_LUA STR_CHAR_LUA[0]
#define STR_CHAR_DELTA "\302\210"
#define STR_CHAR_STICK "\302\211"
#define STR_CHAR_POT "\302\212"
#define STR_CHAR_SLIDER "\302\213"
#define STR_CHAR_SWITCH "\302\214"
#define STR_CHAR_TRIM "\302\215"
#define STR_CHAR_INPUT "\302\216"
#define STR_CHAR_FUNCTION "\302\217"
#define STR_CHAR_CYC "\302\220"
#define STR_CHAR_TRAINER "\302\221"
#define STR_CHAR_CHANNEL "\302\222"
#define STR_CHAR_TELEMETRY "\302\223"
#define STR_CHAR_LUA "\302\224"
#define LEN_FSGROUPS "\001"
#define TR_FSGROUPS "-""1""2""3"