{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Блоги: заметки с тегом юникод",
    "_rss_description": "Автоматически собираемая лента заметок, написанных в блогах на Эгее",
    "_rss_language": "ru",
    "_itunes_email": "",
    "_itunes_categories_xml": "",
    "_itunes_image": false,
    "_itunes_explicit": "no",
    "home_page_url": "https:\/\/blogengine.me\/blogs\/tags\/yunikod\/",
    "feed_url": "https:\/\/blogengine.me\/blogs\/tags\/yunikod\/json\/",
    "icon": false,
    "authors": [
        {
            "name": "Илья Бирман",
            "url": "https:\/\/blogengine.me\/blogs\/",
            "avatar": false
        }
    ],
    "items": [
        {
            "id": "124655",
            "url": "https:\/\/bolknote.ru\/all\/podderzhka-yunikoda-vo-flippere-zero\/",
            "title": "Поддержка Юникода во «Флиппере Зеро»",
            "content_html": "<p>Если помните, какое-то время назад я <a href=\"https:\/\/bolknote.ru\/all\/pishem-na-flipper-zero-po-russki\/\">разбирался<\/a> как обстоит дело с поддержкой Юникода во «Флиппере Зеро». Если вкратце: плохо, но жить можно. Исследование этой проблемы даже подтолкнуло меня к написанию игры, в которой я мужественно поборол все трудности, связанные с ограниченностью АПИ в этом вопросе.<\/p>\n<p>Видно, что авторы не подумали о поддержке Юникода, поэтому он там работает кое-как и по чистой случайности, просто потому что его поддерживает сторонний фреймворк «<i>U∞g2<\/i>», используемый для вывода на экран.<\/p>\n<p>Ситуацию можно было бы немного улучшить простым патчем — надо всего лишь поменять в функции <tt>canvas_glyph_width<\/tt> тип входного параметра с <tt>char<\/tt> на <tt>uint16_t<\/tt> (эта функция возвращает ширину символа в пикселях) и она начнёт работать с кодировкой <i>UCS-2<\/i>.<\/p>\n<p>Две недели назад я подумал — почему бы не попробовать предложить этот патч авторам прошивки. Если не примут, то может быть хоть в дискуссию вступят, всё польза. Скачал разработческую версию ядра, сделал правку и <a href=\"https:\/\/github.com\/flipperdevices\/flipperzero-firmware\/pull\/3226\">закинул<\/a> патч.<\/p>\n<p>Сегодня патч приняли без лишних слов. Вот это да, не ожидал! Справедливости ради, это небольшое изменение, которое не должно ничего сломать. Теперь хорошо бы подумать как сделать так, чтобы в остальном АПИ поддержка Юникода заработала. А там, кажется, малой кровью не обойдёшься.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2023.11.30@2x.jpg\" width=\"1000\" height=\"384\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Внезапно мой небольшой коммит для поддержки Юникода функцией вычисления ширины символа приняли<\/div>\n<\/div>\n",
            "date_published": "2023-11-30T19:41:50+05:00",
            "date_modified": "2023-11-30T19:59:06+05:00",
            "tags": [
                "flipper zero",
                "программирование",
                "юникод"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Thu, 30 Nov 2023 19:41:50 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "124655",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "123899",
            "url": "https:\/\/bolknote.ru\/all\/pishem-na-flipper-zero-po-russki\/",
            "title": "Пишем на «Флиппере Зеро» по-русски",
            "content_html": "<p class=\"remark\">Вкратце: в русским текстом работать можно, но сложно, код лежит на «<a href=\"https:\/\/github.com\/bolknote\/Flipper-Zero-Privet-Mir\">Гитхабе<\/a>».<\/p>\n<p>Не мог не обратить внимание, что несмотря на приличное количество русскоязычных авторов программ для «Флиппера Зеро» и российское происхождение устройства, в софте под него нигде не обнаружилось ни одной буквы по-русски.<\/p>\n<!-- Flipper Zero --><p>Стало интересно — а есть ли вообще в устройстве поддержка русского языка? Сел вечерком за исходники прошивки и оказалось, что однозначного ответа на заданный вопрос нет. Частичная поддержка есть и её можно заставить работать, но она работает как будто по недосмотру — никто её не собирался делать, так случайно вышло.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2023.10.26@2x.jpg\" width=\"500\" height=\"234\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Вывод русских букв (трансляция с экрана через оболочку <i>qFlipper<\/i>)<\/div>\n<\/div>\n<p>Сейчас расскажу, что я имею ввиду и постараюсь покороче, никто сейчас не любит длинные тексты.<\/p>\n<p>Внутри «Флиппера» довольно много всякого АПИ для вывода текста, от незатейливого, выводящего строку в заданные координаты, до довольно навороченного, умеющего переносить и выравнивать текст. К сожалению, с русскими буквами оно работать не умеет.<\/p>\n<p>Под капотом у этого АПИ — очень известная в своей нише библиотека <a href=\"https:\/\/github.com\/olikraus\/u8g2\/\"><i>U∞g2<\/i><\/a>, у которой по историческим, видимо, причинам есть два набора функций для работы со строками. Одни принимают только однобайтовую кодировку <i>ASCII<\/i>, другие работают в Юникоде.<\/p>\n<p>АПИ «Флиппера», к сожалению, использует функции, которые с Юникодом работать не умеют. К счастью, по какой-то случайности, упомянутое АПИ содержит два вызова, которые помогают решить эту проблему, правда придётся помучаться.<\/p>\n<p>Первый из них — <tt>canvas_set_custom_u8g2_font<\/tt>.<\/p>\n<p>Чтобы что-то написать русскими буквами, нужен шрифт, который их содержит. Стандартный вывод <tt>canvas_set_font<\/tt> позволяет выбрать один из нескольких шрифтов, ни одни из которых кириллицу не поддерживает.  Тут и приходит на помощь <tt>canvas_set_custom_u8g2_font<\/tt>. Он позволяет выбрать произвольный шрифт пакета <i>U∞g2<\/i>, — внутри их целая куча и некоторые в названии содержат слово «<i>cyrillic<\/i>».<\/p>\n<p>Вот как я это делаю:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">#include &lt;u8g2\/u8g2_fonts.c&gt;\r\n\/\/ …\r\ncanvas_set_custom_u8g2_font(canvas, u8g2_font_haxrcorp4089_t_cyrillic);<\/code><\/pre><p>Файл <tt>u8g2\/u8g2_fonts.c<\/tt> внутри себя содержит шрифты в особом формате, можно открыть его прямо в текстовом виде и посмотреть как всё устроено.<\/p>\n<p>Шрифт мы выбрали, теперь надо что-нибудь вывести. Тут на помощь приходит <tt>canvas_draw_glyph<\/tt>. «Под капотом» у него лежит <tt>u8g2_DrawGlyph<\/tt>, которая понимает символы Юникода почти без танцев с бубном. Думаю, это чистое везение — наверняка эта функция появилась сильно позднее, поэтому у неё нет двух вариантов для работы с разными кодировками.<\/p>\n<p>Плохая новость в том, что строки в коде мы обычно пишем в кодировке <i>UTF-8<\/i>, а <tt>canvas_draw_glyph<\/tt> ожидает их в <i>UCS-2<\/i>. Но есть и хорошая новость — в прошивке доступно АПИ для перекодирования, как обычно, великолепно описанное на языке Си.<\/p>\n<p>Но <tt>canvas_draw_glyph<\/tt>, как можно понять из названия, выводит только один символ. Как же вывести строку? Достаточно просто — надо вывести её посимвольно, сдвигая координату на ширину символа. Звучит просто, но как это сделать?<\/p>\n<p>Тут нас ждёт очередная засада. Дело в том, что <tt>canvas_glyph_width<\/tt>, которая должна бы это делать, принимает на вход только <tt>char<\/tt>, при том, что нижележащая <tt>u8g2_GetGlyphWidth<\/tt> преспокойно работает с Юникодом.<\/p>\n<p>Поэтому я сделал просто — выдрал реализацию <tt>u8g2_GetGlyphWidth<\/tt> себе в исходники. В итоге мякотка моего решения выглядит вот так:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">void unicode_draw_utf8_str(Canvas* canvas, uint8_t x, uint8_t y, char* str) {\r\n    FuriStringUTF8State state = FuriStringUTF8StateStarting;\r\n    FuriStringUnicodeValue value = 0;\r\n\r\n    for(; *str; str++) {\r\n        furi_string_utf8_decode(*str, &amp;state, &amp;value);\r\n        if(state == FuriStringUTF8StateError) furi_crash(NULL);\r\n\r\n        if(state == FuriStringUTF8StateStarting) {\r\n            canvas_draw_glyph(canvas, x, y, value);\r\n            x += unicode_GetGlyphWidth(&amp;canvas-&gt;fb, value);\r\n        }\r\n    }\r\n}<\/code><\/pre><p>Тут <tt>unicode_GetGlyphWidth<\/tt> — скопированная реализация <tt>u8g2_GetGlyphWidth<\/tt>, а <tt>furi_string_utf8_decode<\/tt> используется для кодирования <i>UTF-8<\/i>-строки в последовательные символы <tt>UCS-2<\/tt>.<\/p>\n",
            "date_published": "2023-10-26T21:29:37+05:00",
            "date_modified": "2023-10-26T21:29:19+05:00",
            "tags": [
                "flipper zero",
                "программирование",
                "си",
                "юникод"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Thu, 26 Oct 2023 21:29:37 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "123899",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125660",
            "url": "https:\/\/bolknote.ru\/all\/2092\/",
            "title": "I � Unicode",
            "content_html": "<p>Давайте я вам про Unicode ещё раз расскажу?<\/p>\n<p>Как известно, в памяти компьютера числа представлены битами, которые группируются в байты. Один байт может хранить одно из 256 значений (поскольку состоит из восьми бит, каждый из которых может хранить одно из двух). Следовательно, числа, значения которых &gt; 255 хранятся в больше, чем одном байте.<\/p>\n<p>Например, числа до 65535 можно уместить уже в двух байтах: в так называемом старшем записывается сколько раз полных 256 содержится в числе, а в младшем — остаток: старший × 256 + младший.<\/p>\n<p>В зависимости от типа процессора, порядок, в котором записаны в памяти старшие и младшие байты, различается. Собственно, мне хорошо известны только две системы: младший записывается первым (как в процессорах Intel) и старший записывается первым (в процессорах ARM, которые стоят в смартфонах). Есть ещё смешанная, но с ней я не сталкивался. Системы эти носят имена: little-endian и big-endian (системы со смешанным порядком называются middle-endian и термин не указывает на то как именно «мешается» этот порядок). Краткая запись названий — LE и BE.<\/p>\n<p>Есть ещё системы, которые умеют переключать порядок (те же ARM) и называются bi-endian.<\/p>\n<p>Термины little-endian и big-endian пришли к нам из «Приключений Гулливера» и на русский переводятся как «тупоконечный» и «остроконечный». Те, кто читали, те помнят (война по поводу того с какой стороны разбивать яйца). Информатика тут какбэ намекает. Хотя у каждой системы есть свои достоинства и (не удержался) мне ближе LE.<\/p>\n<p>Сюрприз для непрограммистов: буквы в памяти компьютера тоже представлены числом. Это просто номер по порядку в компьютерном алфавите. Так девочки в нашем классе «кодировали» записки: вместо букв ставили номер позиции в алфавите. В чём-то они были правы, но только не в том, что это шифр.<\/p>\n<p>Когда-то компьютеры победивших сейчас систем использовали всего 256 символов и всем было хорошо — туда умещались все символы, которые присутствовали в том мире, где эти компьютеры создавались. Экспансия привела к тому, что 256 значений для символов перестало хватать.<\/p>\n<p>Было принято очевидное решение — выделять на символ не один байт, а несколько. Так появился стандарт Unicode, где огромному количеству символов дано своё число и закреплены начертания, стандарт дополняется и новые версии выходят почти каждый год.<\/p>\n<p>Система кодирования Unicode, где выделяются два байта, называется UTF-16 (16 бит на символ), там где четыре байта — UTF-32 (32 бита). Название UCS-4 (четыре байта) является синонимом UTF-32, а UCS-2 (два байта) подмножеством UTF-16. UCS-2 <a href=\"http:\/\/www.unicode.org\/faq\/basic_q.html#14\">отличается от UTF-16<\/a> отсутствием так называемых «суррогатных пар» (которые появились только в Unicode 2.0, вы не хотите знать что это) и является устаревшим стандартом, можете про него забыть.<\/p>\n<p>Так как способов хранения чисел, не умещающихся в памяти у нас несколько, то системы подразделяются на UTF-16BE, UTF-16LE, UTF-32BE и UTF-32LE. Отсюда видно, что UCS-4LE это тоже, что и UTF-32LE. Если порядок байт не указан, то <a href=\"http:\/\/unicode.org\/faq\/utf_bom.html#gen7\">принято считать<\/a>, что используется big-endian.<\/p>\n<p>Первого апреля 2005-го года были предложены шуточные «стандарты» <a href=\"http:\/\/tools.ietf.org\/html\/rfc4042\">UTF-9 и UTF-18<\/a>, отношения к рассматриваемой проблеме они имеют. Для телеграфа и прочего слоновьего гуано, разрабатывались UTF-5 и UTF-6, но о их судьбе мне ничего не известно. Так же есть <a href=\"http:\/\/en.wikipedia.org\/wiki\/UTF-7\">UTF-7<\/a>, который в стандарт не вошёл, но реально применяется (в модифицированном виде) внутри почтового протокола <a href=\"http:\/\/tools.ietf.org\/html\/rfc3501\">IMAP4<\/a>, про него я рассказывать не буду, мне он стал известен из-за <a href=\"http:\/\/old.antichat.ru\/txt\/utf7\/\">оригинального способа его использования<\/a> для XSS-атак в IE (в частности, решением этой проблемы я занимался в PEAR PHP <a href=\"http:\/\/pear.php.net\/package\/HTML_Safe\">классе HTML_Safe<\/a>). Можно упомянуть ещё <a href=\"http:\/\/en.wikipedia.org\/wiki\/UTF-1\">UTF-1<\/a>, но с ней я не сталкивался в работе.<\/p>\n<p>BOM. BOM расшифровывается как «byte order mark» (признак порядка байт) и ставится внутри файлов упомянутых двух- и четырёхбайтных кодировок. <a href=\"http:\/\/unicode.org\/faq\/utf_bom.html#gen6\">Если BOM внутри файла не встретился, принимается порядок big-endian<\/a>. У BOM есть значание. В UCS-2 это 65279 (для программистов — FEFF), для UCS-4\/UTF-32 — это 4278124544 (FEFF0000). Число выбрано так, чтобы старшие и младшие байты у них не совпадали и по их порядку можно было бы определить какой порядок байт используется. К сожалению, BOM не даёт возможности определить использутеся двух- или четырёхбайтная кодировка.<\/p>\n<p>Теперь непрограммистам будет трудно.<\/p>\n<p>Пока всё было достаточно просто, но человечество придумало ещё одну кодировку — UTF-8, с плавающим размером. Хорошие новости заключаются в том, что порядок следования байт тут определён и никаких LE и BE рядом с UTF-8 не ставится. Соотвественно и BOM тут не нужен. Он может использоваться только для того, чтобы указать программе, что это именно UTF-8 и <a href=\"http:\/\/unicode.org\/faq\/utf_bom.html#BOM\">имеет номер 15711167 (EF BB BF)<\/a>. Откуда можно сделать вывод (дорогие писатели редакторов), что использование в UTF-8 BOM от UTF-16 — ошибка.<\/p>\n<p>Трудность в том, что UTF-8, по сути это ещё один способ записи многобайтовых чисел (а каждая буква в стандарте Unicode — многобайтовое число). У системы есть целых два плюса (ирония!): старая однобайтовая кодировка совместима с UTF-8, а значит буржуинам не нужно переделывать свои программы, если они не используют в них буквы и других языков (например, на любом старом англоязычном сайте как бы уже используется кодировка UTF-8), второй плюс — латиница записывается компактнее (в один символ). Минусы — чисто программисткие: работа с кодировкой требует больше ресурсов из-за плавающего размера.<\/p>\n<p>Итак. Каждый символ в кодировке занимает от 1 до 4-х байт. Вообще, формат устроен так, что можно было бы взять и более длинные цепочки, но в Unicode нет столько символов, чтобы записывать их более длинными последовательностями.<\/p>\n<p>Тут надо вспомнить что такое биты. Бит — единица информации, мельче не бывает, у него всего два значения — 0 или 1. Байт состоит из 8 битов, биты очень удобно записывать в позиционной двоичной системе: 00001011. «Позиционная» тут означает, что значение числа зависит от его позиции. Кстати, это привычная нам система. В числе «22» две двойки, но у первой значение в <i>десять<\/i> раз больше, чем у второй. Это <i>десятичная<\/i> позиционная система. В двоичной, каждая более левая однёрка будет больше в два раза своей соседки.<\/p>\n<p>Таким образом число 1011 расшифровывается из двоичной как 1 × 2<sup>3<\/sup> + 0 × 2<sup>2<\/sup> + 1 × 2<sup>1<\/sup> + 1 × 2<sup>0<\/sup> = 1 × 8 + 1 × 2 + 1 × 1 = 8 + 2 + 1 = 11 в десятичной системе.<\/p>\n<p>UTF-8 устроен следующим образом. Пусть, мы двигаемся по строке, содержащей два байта: 208 и 159. В битах это 11010000 и 10011111. (Немного осталось, потерпите).<\/p>\n<p>В первом символе нужно посчитать количество бит со значением «1» до первого нуля. Это общее количество байт, которым записан данный символ. Если количество байт — один (это вроде как специальный признак), то вы нашли не первый байт символа.<\/p>\n<p>У нас в примере количество бит до первого нуля — два. Значит, буква записана двумя символами — первый это тот, на которым мы находимся и второй — который следует за ним. Каждый байт в UTF-8 разбит на две части — до первого нулевого бита. Первая часть — общая длина байт последовательности, а оставшаяся — значение. Биты из значения записывают последовательно (у нас это 10000 011111) и смотрят какое число получилось (у нас это — 1055, это номер <a href=\"http:\/\/www.fileformat.info\/info\/unicode\/char\/041f\/index.htm\">буквы «П»<\/a> в Unicode).<\/p>\n<p>Могу рассказать про UTF-7 и UTF-1, если интересно. Или про суррогатные пары.<\/p>\n",
            "date_published": "2009-04-15T13:26:00+05:00",
            "date_modified": "2024-01-28T00:08:50+05:00",
            "tags": [
                "utf8",
                "юникод"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Wed, 15 Apr 2009 13:26:00 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125660",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        }
    ],
    "_e2_version": 4079,
    "_e2_ua_string": "Aegea 11.0 (v4079e)"
}