Парсинг String с русскими символами

FoxJone
Offline
Зарегистрирован: 19.04.2019

До сих пор не сталкивался, думал все просто, а получилось как всегда - не выходит аленький цветочек! После долгих мучений дошло, что проблема именно в типе String, начал разбираться, для чего черкнул простенький пример:

  String s ="Привет";
  Serial.println(s);  //печатаю всю строку, печатается "Привет"
  Serial.println(s.length()); //печатаю длину строки, печатается 12, хотя по факту 6 символов... Видимо русский символ считается за 2.
  for (int i = 0; i <= s.length(); i++) { Serial.print(s[i]);  }  //печатаю посимвольно - печатает квадратики, символов нет...

Вопрос знающим: как мне взять и напечатать отдельно взятый символ (несколько символов) из русской строки? У меня есть в голове пару вариантов, но все громоздкие костыли. Не может быть, что бы не было простого решения для столь тривиальной проблемы.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

за UTF-8 знаешь чонибуть?

FoxJone
Offline
Зарегистрирован: 19.04.2019

DetSimen пишет:

за UTF-8 знаешь чонибуть?

Немного знаю, но это не меняет вопроса: есть ли простой стандартный способ выдрать нужный русский символ из String? Например из такой "Hello1234Привет!".

sadman41
Offline
Зарегистрирован: 19.10.2016

Нет.

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

хранить и обрабатывать все в однобайтовой кодировке, например в WIN1251, а уже перед выводом преобразовывать как надо.

b707
Offline
Зарегистрирован: 26.05.2017

FoxJone пишет:

 

  String s ="Привет";
  Serial.println(s);  //печатаю всю строку, печатается "Привет"
  Serial.println(s.length()); //печатаю длину строки, печатается 12, хотя по факту 6 символов... Видимо русский символ считается за 2.
  for (int i = 0; i <= s.length(); i++) { Serial.print(s[i]);  }  //печатаю посимвольно - печатает квадратики, символов нет...

 

FoxJone - Вас ваш собственный комментарий в строке 3 ни на какие мысли не наталкивает? "Русский символ" состоит из звух байт, а вы печаете только один - конечно получается ерунда...

FoxJone
Offline
Зарегистрирован: 19.04.2019

b707 пишет:

FoxJone - Вас ваш собственный комментарий в строке 3 ни на какие мысли не наталкивает? "Русский символ" состоит из звух байт, а вы печаете только один - конечно получается ерунда...

Да знаю я, что национальные символы два байта занимают. Вопрос состоит в том, как быстро и без геммора этот символ распознать и вычленить. Была надежда, что это все делается штатными средствами языка.

Но не больно то и хотелось, мне этот стринг только для отладки нужен, пока шрифты не прорисованы. Пожалуй, начну со шрифтов - их все равно рисовать.

У меня просто идет поток байтов (традиционно не от меня, а от чужой железки), в котором идет текст. Русский, английский, цифры... У них там своя кодировка, каждый байт означает один символ. К примеру, пробел у них байт со значением 0 (Sic!). Руки бы поотрывал...

Мне этот поток надо превратить в символы на табло. Ничего сложного, просто фонт прорисовать. Но я хотел предварительно в мониторе на все это посмотреть. Но не судьба.

b707
Offline
Зарегистрирован: 26.05.2017

FoxJone пишет:

Мне этот поток надо превратить в символы на табло. Ничего сложного, просто фонт прорисовать. Но я хотел предварительно в мониторе на все это посмотреть. Но не судьба.

Вы хотите рисовать кастомный фонт под чужую кодировку? - а не проще перекодировать чужую кодировку в кодировку фонта? - по мне так второй метод на порядок менее трудоемкий, к тому же позволяет легко использовать кучу фонтов. а не один единственный.

FoxJone
Offline
Зарегистрирован: 19.04.2019

b707 пишет:

Вы хотите рисовать кастомный фонт под чужую кодировку? - а не проще перекодировать чужую кодировку в кодировку фонта? - по мне так второй метод на порядок менее трудоемкий, к тому же позволяет легко использовать кучу фонтов. а не один единственный.

Фонт все равно рисовать, его еще не существует в принципе. И вариант с фонтом мне кажется более оптимизированный в плане того, что там просто вызываешь нужный символ по значению байта и никаких лишних телодвижений. А нарисовать фонт - делов то на час, я же не каждый символ буду отрисовывать, а в основном копи-паст.

b707
Offline
Зарегистрирован: 26.05.2017

FoxJone пишет:

А нарисовать фонт - делов то на час

ну тут кому что проще :) Написать перекодировщик - это 10, ну может 20 строк на Си... А фонтов готовых в интернете сотни...

FoxJone
Offline
Зарегистрирован: 19.04.2019

b707 пишет:

ну тут кому что проще :) Написать перекодировщик - это 10, ну может 20 строк на Си... А фонтов готовых в интернете сотни...

Повторюсь: вопрос то в оптимизации. У меня там ДМД работает (а он тяжелый), там 485-й с буфером, там фонты (3 штуки разных одновременно). А давайте еще 20 строк на Си добавим, которые на лету будут перекодировать байтовую посылку (а ведь еще надо таблицу перекодировки...).

А чип то у меня до сих пор 328, мозгов то, как улитки... Давно пора перейти на СТМ, но платы то у меня под 328 разведены (за деньги и новые разводить - тоже деньги...) и на заводе платном мои шаблоны есть, а новые шаблоны тоже денег стоят...

Так и живем)

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Чтобы выцепить нужный символ в строке в кодировке UTF-8, надо, побайтово:

1. прочитали байт, вычислили, сколько следующих байт занимает символ;

2. Прочитали эти байты в буфер, получили байты, которые кодируют очередной символ в строке.

Собственно, всё. Кодировщиков/раскодировщиков UTF-8 - как г-на за баней. Если у вас задача работать с UTF-8 для вывода русских слов на дисплей - то тут очень много специфики, например, какая либа используется для работы с TFT, какие шрифты используются, и т.п. В общем случае задача вывода решается введением таблицы перекодировки русских символов выводимой строки (которая в UTF-8), в коды символов используемого шрифта.

Всё решаемо, но зависит от специфики. У меня во всех прошивках UTF-8, русские строки выводятся на дисплей именно по описанному алгоритму.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Вот, если интересно - выдрано из проекта работы с DMD:

int utf8_strlen(const String& str) // возвращает длину в символах строки в кодировке UTF-8
{
    
    int c,i,ix,q;
    for (q=0, i=0, ix=str.length(); i < ix; i++, q++)
    {
        c = (unsigned char) str[i];
        if      (c>=0   && c<=127) i+=0;
        else if ((c & 0xE0) == 0xC0) i+=1;
        else if ((c & 0xF0) == 0xE0) i+=2;
        else if ((c & 0xF8) == 0xF0) i+=3;
        //else if (($c & 0xFC) == 0xF8) i+=4; // 111110bb //byte 5, unnecessary in 4 byte UTF-8
        //else if (($c & 0xFE) == 0xFC) i+=5; // 1111110b //byte 6, unnecessary in 4 byte UTF-8
        else return 0; //invalid utf8
    }
    return q;
}
//--------------------------------------------------------------------------------------------------------------------------------
unsigned int utf8GetCharSize(unsigned char bt) 
{ 
  if (bt < 128) 
  return 1; 
  else if ((bt & 0xE0) == 0xC0) 
  return 2; 
  else if ((bt & 0xF0) == 0xE0) 
  return 3; 
  else if ((bt & 0xF8) == 0xF0) 
  return 4; 
  else if ((bt & 0xFC) == 0xF8) 
  return 5; 
  else if ((bt & 0xFE) == 0xFC) 
  return 6; 

 
  return 1; 
} 
//--------------------------------------------------------------------------------------------------------------------------------
bool utf8ToUInt(const String& bytes, unsigned int& target) 
{ 
  unsigned int bsize = bytes.length(); 
  target = 0; 
  bool result = true; 
  if (bsize == 1) 
  { 
    if ((bytes[0] >> 7) != 0) 
    { 
      result = false; 
    } 
      else 
      target = bytes[0]; 
   } 
    else 
    { 
      unsigned char cur_byte = bytes[0]; 
      target = ((cur_byte & (0xFF >> (bsize + 1))) << (6 * (bsize - 1))); 
      unsigned int i = 1; 
      
      while (result && (i < bsize)) 
      { 
        cur_byte = bytes[i]; 
        if ((cur_byte >> 6) != 2) 
        { 
          result = false; 
         } 
         else 
          target |= ((cur_byte & 0x3F) << (6 * (bsize - 1 - i))); i++; 
       } 
    } 
    return result; 
}
//--------------------------------------------------------------------------------------------------------------------------------
String convertFromUTF8ToDisplayFont(const String& src)
{
  String result;
  const char* ptr = src.c_str();

   while(*ptr) 
  {
    byte curChar = (byte) *ptr;
    unsigned int charSz = utf8GetCharSize(curChar);

    // у нас максимальная длина символа в UTF-8 - 6 байт, поэтому используем промежуточный буфер в 7 байт
    static char curBytes[7] = {0};

    for(byte i=0;i<charSz;i++) 
    {
      curBytes[i] = *ptr++;
    }

    curBytes[charSz] = '\0'; // добавляем завершающий 0
    
//    Serial.print(curBytes);
//    Serial.print(" = ");

    unsigned int myChar;
    if(utf8ToUInt(curBytes,myChar))
    {
        if(myChar <= 0x7F)
          result += (char) myChar;
        else if(myChar == 0x401) // Ё
          result += (char) 0xF0;
        else if(myChar == 0x451) // ё
          result += (char) 0xF1;
        else if(myChar >= 0x410 && myChar <= 0x43F) // А-Яa-п
          result += (char) (myChar - 0x390);
        else if(myChar >= 0x440 && myChar <= 0x44F) // р-я
          result += (char) (myChar - 0x360);        
      
//      Serial.print("0x");
//      Serial.println(myChar,HEX);
    }
    
  } // while 

  return result;
}
//--------------------------------------------------------------------------------------------------------------------------------

Пример использования:

String displayText = convertFromUTF8ToDisplayFont(F("Привет, Васся!"));

Как видно, в функции convertFromUTF8ToDisplayFont реализован ремаппинг UTF-8 в коды символов шрифта. Кстати до кучи - есть функция utf8_strlen - которая возвращает длину текста в символах, а не в байтах ;)

FoxJone
Offline
Зарегистрирован: 19.04.2019

Это все я в курсе. Делать не буду, лениво и не особо надо. Если читали, мне это надо было только для отладки.

Я просто пробил, вдруг есть какой то способ одной командой, а я то его и не знаю...

FoxJone
Offline
Зарегистрирован: 19.04.2019

DIYMan пишет:

Как видно, в функции convertFromUTF8ToDisplayFont реализован ремаппинг UTF-8 в коды символов шрифта. Кстати до кучи - есть функция utf8_strlen - которая возвращает длину текста в символах, а не в байтах ;)

Оппа! Точно! Я ведь этот проект и знаю хорошо, а чет даже и в голову не пришло.

Спасибо, коллега, сейчас голову поковыряю себе.

PS. Коллективный разум - это сила! Особливо распределенная БД по программированию)

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

FoxJone пишет:

PS. Коллективный разум - это сила! Особливо распределенная БД по программированию)

 
Да шо уж там - мутота одна с этим UTF-8 :) Как только другой формат шрифтов или библиотеку заюзаешь - делай ремаппинг по новой. Вот, например, таблица перекодировки под шрифты формата GFXfont, которые адафрутовские:
 
const uint8_t utf8_rus_charmap[] PROGMEM = {
  'A', // А
  128, // Б
  'B', // В
  129, // Г
  130, // Д
  'E', // Е
  132, // Ж
  133, // З
  134, // И
  135, // Й
  'K', // К
  136, // Л
  'M', // М
  'H', // Н
  'O', // О
  137, // П
  'P', // Р
  'C', // С
  'T', // Т
  138, // У
  139, // Ф
  'X', // Х
  140, // Ц
  141, // Ч
  142, // Ш
  143, // Щ
  144, // Ъ
  145, // Ы
  146, // Ь
  147, // Э
  148, // Ю
  149, // Я
  
  'a', // а
  150, // б
  151, // в
  152, // г
  153, // д
  'e', // е
  155, // ж
  156, // з
  157, // и
  158, // й
  159, // к
  160, // л
  161, // м
  162, // н
  'o', // о
  163, // п
  
  'p', // р
  'c', // с
  164, // т
  'y', // у
  165, // ф
  'x', // х
  166, // ц
  167, // ч
  168, // ш
  169, // щ
  170, // ъ
  171, // ы
  172, // ь
  173, // э
  174, // ю
  175, // я
  
  'E', // Ё
  154, // ё
  };
Как видно - совершенно другое расположение символов в шрифте. А вот - кусок кода класса-хелпера, для вывода русского текста на TFT SSD1963, под STM32, с использованием библиотеки GxTFT и порта STM32GENERIC:
 
int TFTRus::print(const char* st,int x, int y, word bgColor, word fgColor, bool computeTextWidthOnly)
{ 
  if(!st)
  {
    return 0; 
  }
  
 int stl, i, tw = 0;
 stl = strlen(st);

  if(!computeTextWidthOnly)
  {
    pDisplay->setTextColor(fgColor,  bgColor);
  }
  
  
  uint8_t utf_high_byte = 0;
  unsigned int ch;

  String buff;
  buff.reserve(stl);

for (i = 0; i < stl; i++) 
  {
    ch = st[i];
    
    if ( ch >= 128) 
    {
      if ( utf_high_byte == 0 && (ch ==0xD0 || ch == 0xD1)) 
      {
        utf_high_byte = ch;
        continue;
      } 
      else 
      {
        if ( utf_high_byte == 0xD0) 
        {
          if (ch == 0x81) 
          { 
            //Ё
            ch = RUS_YO_BIG; // индекс буквы Ё в массиве перекодировки
          } 
          else 
          {
            if(ch <= 0xAF)  // [А-Я]
            {
              ch -= 0x90;
            } 
            else if( ch <= 0xBF) // [а-п]
            {
              ch -= 0x90;
            } 
            else 
            {
              // ch -= (0x90 - 1);
            }
          }
          
          ch = pgm_read_byte((utf8_rus_charmap + ch));
        
        } 
        else if (utf_high_byte == 0xD1) 
        {
          if (ch == 0x91) 
          {
            //ё
            ch = RUS_YO_SMALL; // индекс буквы ё в массиве перекодировки
          } 
          else   // [р-я]
          {
            ch -= 0x80;
            ch += 48;
          }
          
          ch = pgm_read_byte((utf8_rus_charmap + ch));
        }
        
        utf_high_byte = 0;
      }
    } 
    else 
    {
      utf_high_byte = 0;
    }


    buff += char(ch);
  } // for 

  if(computeTextWidthOnly)
  {
    return pDisplay->textWidth(buff,1);
  }
  else
  {
    return pDisplay->drawString(buff,x,y,1);
  }


}

Тут, как видно, попроще - только табличку ремаппинга подобрать ручками - и всё.