+
+## Информация для разработчиков ассистивных технологий и пользователей библиотеки
+
+При использовании MathCAT вызовы обычно выполняются в следующем порядке:
+1. Задаётся расположение каталога `Rules` MathCAT с помощью [SetRulesDir].
+2. С помощью вызовов [`SetPreference`] задаются необходимые ассистивной технологии настройки. Обычно указываются `Language` и используемый движок `TTS`, если он есть. Настоятельно рекомендуется задать движок.
+3. MathML передаётся с помощью [`SetMathML`].
+4. Ассистивная технология вызывает [`GetSpokenText`] для получения текста для озвучивания и [`GetBraille`] для получения Брайля в Unicode. Если указан идентификатор узла, соответствующие ячейки Брайля будут выделены.
+
+Для навигации можно вызывать:
+* [`DoNavigateKeyPress`] — принимает события клавиатуры;
+* [`DoNavigateCommand`] — принимает команды, в которые внутри программы преобразуются события клавиатуры.
+
+Оба вызова возвращают строку для озвучивания.
+Для выделения текущего узла навигации используются атрибуты `id`. Если они ещё не заданы,
+[`SetMathML`] возвращает строку MathML с атрибутами `id` у всех узлов, для которых такие атрибуты отсутствовали.
+Текущий узел можно получить с помощью:
+* [`GetNavigationMathMLId`];
+* [`GetNavigationMathML`] — возвращает строку MathML выбранного узла.
+
+Примечание: оба вызова также возвращают второе целое число. Это смещение символа в листовом узле.
+Оно необходимо для посимвольной навигации по листовым узлам из нескольких символов, например `sin` и `1234`.
+Сейчас значение всегда равно `0`: эта функция требует дальнейшей разработки.
+
+Текущие значения настроек можно получить с помощью вызова [`GetPreference`].
+
+Все функции могут возвращать код ошибки.
+
+Примечание: MathCAT выполняет значительную работу по исправлению некачественного MathML. В частности, генераторы MathML часто разделяют на части числа с запятыми и точками. MathCAT пытается собрать их обратно, но для этого должен знать региональные правила использования разделителей групп цифр и десятичных разделителей. Например, в США запись `1,234.0` является допустимым числом, а в Европе — нет, поскольку запятая используется как десятичный разделитель. Региональные настройки определяются страной, для которой создан документ, а не языком озвучивания математических выражений. Допустимый вид числа задают настройки `BlockSeparators` и `DecimalSeparators`. Вызывающая программа должна устанавливать эти значения, если они известны. По умолчанию используется американский формат чисел.
+
+## Для пользователей Rust
+MathCAT написан на Rust. Достаточно собрать MathCAT и добавить в файл `Cargo.toml` своего проекта запись наподобие следующей:
+```
+[dependencies.MathCAT]
+mathcat = 0.2.0 # проверьте актуальную версию и используйте её
+```
+
+Точные сигнатуры функций с комментариями:
+```
+/// Задаёт каталог Rules.
+/// ВАЖНО: это должен быть самый первый вызов MathCAT, если только не задана переменная среды MathCATRulesDir.
+pub fn set_rules_dir(dir: String) -> Result<()>
+
+/// Возвращает номер версии сборки из Cargo.toml.
+pub fn get_version() -> String
+
+/// Заменяет ранее заданный MathML.
+/// Возвращает канонический MathML с атрибутами 'id' у всех узлов, у которых не было идентификатора.
+/// Идентификаторы можно использовать для синхронного выделения, если настройка API `Bookmark` равна true.
+pub fn set_mathml(mathml_str: String) -> Result
Руководство переводчика и разработчика правил
+
+## Информация для разработчиков правил и переводчиков MathCAT
+Эта страница находится в процессе подготовки.
+
+## Начало работы
+Если вы планируете участвовать в разработке MathCAT, используйте GitHub:
+1. Создайте форк репозитория MathCAT `github.com/NSoiffer/MathCAT`.
+2. Клонируйте форк, чтобы получить локальную копию для работы.
+3. Переключитесь на созданную для вашей работы ветку, обычно названную кодом языка перевода, и работайте в ней.
+
+Если вы ещё не знакомы с этими действиями, найдите одно из многочисленных руководств. Они достаточно просты, поэтому отсутствие опыта не должно вас останавливать.
+
+
+## Переводчикам на другие языки
+Если вы хотите перевести MathCAT, свяжитесь с @NSoiffer. Он подготовит начальный вариант перевода, который может значительно сэкономить время. В результате будут созданы файлы в каталоге `Rules/Languages/xx`, где `xx` — код языка, например `fr`, `de` или `el`. Перевод выполняется в этом каталоге. Необходимо отредактировать четыре категории файлов:
+1. `definitions.yaml`: содержит переводы числительных, в том числе количественных и порядковых. Проверьте начальный перевод и внесите необходимые исправления. Эти числительные используются, например, при озвучивании фразы «три пятых». На некотором этапе образование числительных в языках становится регулярным, поэтому некоторые списки в файле можно сократить, а некоторые следует дополнить. Подробнее см. английские комментарии в файле.
+2. Файлы `xxx_Rules.yaml`, сейчас это `ClearSpeak_Rules.yaml` и `SimpleSpeak_Rules.yaml`. Они соответствуют разным стилям речи. Настоятельно рекомендуется сначала выбрать только один стиль. Эти файлы обычно содержат слова, описывающие структуру выражения, например «дробь» и «степень», а также связующие слова. Поскольку стили речи во многом похожи, существует также каталог `SharedRules` с общими файлами правил. Они подключаются в `ClearSpeak_Rules.yaml` и `SimpleSpeak_Rules.yaml` с помощью правил `- include: file_name`. Их тоже необходимо перевести.
+
+— библиотека, которая преобразует MathML в:
+
+* текст для озвучивания со встроенными командами речевого синтезатора;
+* Брайль (код Немета, технический вариант UEB, а в дальнейшем и другие математические системы записи Брайля);
+* представление для навигации по математическим выражениям, в том числе с возможностью обзора.
+
+Цель MathCAT — предоставить программам экранного доступа и другим вспомогательным технологиям удобную библиотеку для качественного озвучивания MathML и преобразования MathML в Брайль. MathCAT продолжает идеи MathPlayer (подробнее об этом будет сказано ниже) и использует накопленный в этом проекте опыт, чтобы ещё лучше озвучивать математические выражения, представлять их в Брайле и обеспечивать навигацию по ним. В MathCAT применяются новые идеи, которые разрабатывает [рабочая группа MathML](https://mathml-refresh.github.io/charter-drafts/math-2020.html): они позволяют авторам указывать смысл использованного обозначения. Например, $(3, 6)$ может обозначать точку на плоскости, открытый интервал или даже сокращённую запись наибольшего общего делителя. Если эта информация передана в MathML, MathCAT использует её для более естественного озвучивания.
+
+Предстоит сделать: подключить сторонние библиотеки для поддержки общего подмножества математических команд TeX и ASCIIMath.
+
+
+# Документация для разных пользователей MathCAT
+
+MathCAT используют разные аудитории с различными потребностями и задачами. Подробности приведены в соответствующих разделах документации:
+* Пользователи вспомогательных технологий: [информация о доступных настройках](users.md).
+* Разработчики вспомогательных технологий и пользователи библиотеки: [информация об API MathCAT](callers.md).
+* Переводчики и авторы правил: [информация о файлах, которые необходимо перевести](helpers.md).
+* Разработчики MathCAT: [информация о процессе разработки и тестировании](developers.md).
+
+# Некоторые технические подробности
+MathCAT написан на Rust и может использоваться из многих языков программирования. На данный момент есть интерфейсы для:
+* [C/C++](https://github.com/NSoiffer/MathCATForC);
+* [Python](https://github.com/NSoiffer/MathCATForPython) — этот интерфейс используют [дополнение NVDA](https://addons.nvda-project.org/addons/MathCAT.en.html) и программа экранного доступа [Orca](https://help.gnome.org/users/orca/stable) для Linux, написанные на Python;
+* [Java](https://github.com/mwhapples/MathCAT4J) — сейчас этот интерфейс используется для экспериментов с MathCAT в [BrailleBlaster](https://www.brailleblaster.org/);
+* [WebAssembly (Wasm, отчасти похожий на JavaScript)](https://github.com/NSoiffer/MathCATDemo/) — этот интерфейс используется в веб-демонстрации MathCAT.
+
+MathCAT применяет несколько эвристик, чтобы исправлять некачественный MathML и приводить его к рекомендованному виду. Например, конвертеры TeX и WYSIWYG-редакторы могут разделить число «1,234» в выражении «1,234+1» по запятой. MathCAT распознаёт такую ситуацию и объединяет число в один элемент `mn`. Другие исправления затрагивают структуру: MathCAT создаёт элементы `mrow` с учётом словаря операторов MathML и при необходимости добавляет невидимые знаки применения функции, умножения, сложения (для смешанных дробей) и разделители (например, между $i$ и $j$ в $a\_{ij}$). Это упрощает генерацию речи и кода Немета, а также может быть полезно другим приложениям. Сейчас очистка MathML не доступна через API, но в дальнейшем может стать ещё одной функцией MathCAT. В целом MathCAT исправляет MathML достаточно осторожно. Иногда результат может оказаться неверным, однако ожидается, что правильных исправлений будет значительно больше. Поиск типичных ошибок в конвертерах MathML и исправление некачественного MathML остаются постоянной задачей проекта.
+
+## Текущее состояние (обновлено 27.03.2026)
+
+MathCAT активно развивается. DAISY принимает деятельное участие в разработке, и вклад новых участников приветствуется. MathCAT распространяется с открытым исходным кодом. [Репозиторий проекта доступен на GitHub](https://github.com/daisy/MathCAT). [О проблемах дополнения MathCAT, относящихся к NVDA, можно сообщить здесь](https://github.com/daisy/MathCATForPython/issues).
+
+* MathCAT поддерживает озвучивание и навигацию для английского, немецкого, испанского, финского, индонезийского, норвежского, шведского, вьетнамского и китайского языков (традиционное письмо).
+* MathCAT поддерживает код Немета, UEB, CMU, вьетнамскую систему записи Брайля, а также немецкую и австрийскую системы записи LaTeX и ASCIIMath.
+* Существует [дополнение NVDA](https://addons.nvda-project.org/addons/MathCAT.en.html). Оно может заменить MathPlayer для пользователей английского языка и поддерживаемых переводов. Начиная с NVDA 2026.1 MathCAT встроен в NVDA, поэтому скачивать дополнение не нужно.
+
+Преобразование в код Немета в MathCAT значительно качественнее, чем в MathPlayer и других конвертерах MathML → код Немета. Оно также интегрировано с навигацией: точки 7 и 8 обозначают текущий узел, а маршрутизация курсора Брайля работает во время навигации. Благодаря высокому качеству вывода [BrailleBlaster](https://www.brailleblaster.org/) использует MathCAT для преобразования MathML в код Немета и UEB.
+
+Ряд других разработчиков вспомогательных технологий также включили MathCAT в свои продукты. Среди них особенно выделяется Vispero/JAWS. Сейчас JAWS поддерживает озвучивание MathCAT на английском и испанском языках, а также вывод в коде Немета и UEB. В дальнейшем появятся другие языки и системы записи Брайля. В средстве просмотра математических выражений JAWS доступны все предусмотренные MathCAT команды озвучивания и навигации, в том числе навигации по Брайлю. Настройки MathCAT находятся в центре настроек JAWS.
+
+Другие вспомогательные технологии, использующие MathCAT:
+
+* программа экранного доступа Orca для Linux использует MathCAT для озвучивания, навигации и вывода в Брайле;
+* Dolphin EasyReader использует MathCAT;
+* Kurzweil 3000 использует озвучивание MathCAT и одновременно выделяет соответствующее подвыражение двумя цветами, обеспечивая наглядное визуальное сопровождение для зрячих пользователей;
+* Microsoft объявила, что Экранный диктор будет использовать MathCAT в одной из будущих версий.
+
+[_Другим компаниям_: если вы включили MathCAT в свой продукт и хотите, чтобы он был упомянут здесь, напишите мне по электронной почте или создайте issue с предложением обновить документацию.]
+
+Была разработана [демонстрационная версия](https://nsoiffer.github.io/MathCATDemo/), которая показывает некоторые возможности MathCAT и помогает при отладке. Сообщайте, пожалуйста, обо всех найденных ошибках. Эта демонстрация _не_ отражает типичный способ взаимодействия пользователей вспомогательных технологий с MathCAT, но показывает функции, которые такие технологии потенциально могут предоставить конечным пользователям: выделение озвучиваемого фрагмента, навигацию и вывод в Брайле.
+
+Планы дальнейшей работы:
+
+* Добавить другие языки. Если вы хотите перевести MathCAT на язык, который пока не поддерживается, создайте issue в [репозитории MathCAT на GitHub](https://github.com/daisy/MathCAT/issues).
+* Поддержать дополнительные системы записи Брайля. Для добавления такой поддержки нужны три составляющие:
+ * спецификация системы;
+ * возможность задавать вопросы специалисту по соответствующей системе записи Брайля;
+ * человек, готовый перенести не менее 200 примеров из спецификации в используемый для тестов формат: MathML и строку символов Брайля в Unicode. Это может занять 30 часов или больше.
+
+ Если вы можете собрать всё необходимое, создайте issue в [репозитории MathCAT на GitHub](https://github.com/daisy/MathCAT/issues).
+* Реализовать преобразование _из_ Брайля _в_ MathML.
+* Разработать двумерные варианты систем записи Брайля для многострочных обновляемых дисплеев Брайля, например Monarch и Canute 360.
+
+
+
+## Почему MathCAT?
+
+MathCAT — продолжение MathPlayer. Я начал разрабатывать функции доступности MathPlayer в компании Design Science в 2004 году вскоре после прихода в компанию. В то время MathPlayer был главным образом подключаемым модулем C++ для Internet Explorer (IE), отображавшим MathML на веб-страницах. Долгое время это была наиболее полная из доступных реализаций MathML. Первоначальную работу над отображением математических выражений выполнили основатель Design Science Пол Топпинг и технический директор компании, ныне покойный Роберт Майнер. Позже по ряду причин IE отказался от интерфейса, который MathPlayer использовал для отображения, и не предложил ему замену: веб переходил к использованию JavaScript в браузере и отказывался от рисков безопасности, связанных с внешним кодом. После этого MathPlayer стал библиотекой, предназначенной исключительно для обеспечения доступности и вызываемой другими программами, главным образом NVDA. MathPlayer был проприетарным, но распространялся бесплатно.
+
+В начале 2017 года я покинул Design Science. Позже в том же году WIRIS приобрела компанию. Я предложил бесплатно исправлять ошибки MathPlayer, и сначала эта инициатива получила поддержку. Но когда пришло время выпускать новую версию, многие сотрудники, работавшие в компании во время приобретения, уже ушли, а оставшаяся команда не была заинтересована в поддержке MathPlayer. Окончательное решение было принято только в конце 2020 года. В 2021 году я начал работать над заменой MathPlayer. В качестве дополнительной задачи я решил изучить Rust и реализовал проект на нём. Rust — низкоуровневый язык со строгой типизацией и безопасной работой с памятью, но без автоматической сборки мусора или подсчёта ссылок. Его часто называют более безопасной заменой C/C++.
+
+Rust достаточно эффективен. На компьютере с Core i7-770K (мощным по меркам примерно 2017 года процессором) для выражения среднего размера
+
+требуется около 4 мс, чтобы создать строку ClearSpeak на английском языке
+"_e raised to the exponent, negative 1 half times; open paren; the fraction with numerator; x minus mu; and denominator sigma; close paren squared, end exponent_"
+и строку в коде Немета "⠑⠘⠤⠹⠂⠌⠆⠼⠈⠡⠷⠹⠭⠤⠨⠍⠌⠨⠎⠼⠾⠘⠘⠆".
+Примерно 2 мс занимают очистка MathML, 1 мс — генерация речи, ещё 1 мс — генерация Брайля. Сюда входит проверка актуальности всех файлов правил, которая оказывается достаточно затратной. Эту проверку можно отключить с помощью настройки: она нужна главным образом при отладке. Если проверка отключена, время уменьшается до 2,3 мс.
+На более мощном процессоре Intel Core Ultra 9 285 2025 года генерация речи и Брайля в одном потоке занимает около 1 мс.
+
++<math> + <mrow> + <msup> + <mi>e</mi> + <mrow> + <mo>−</mo> + <mfrac> + <mn>1</mn> + <mn>2</mn> + </mfrac> + <msup> + <mrow> + <mrow> + <mo>(</mo> + <mrow> + <mfrac> + <mrow> + <mi>x</mi> + <mo>−</mo> + <mi>μ</mi> + </mrow> + <mi>σ</mi> + </mfrac> + </mrow> + <mo>)</mo> + </mrow> + </mrow> + <mn>2</mn> + </msup> + </mrow> + </msup> + </mrow> +</math> ++
Руководство пользователя
+
+## Рекомендации по выбору голоса
+
+В NVDA можно использовать разные синтезаторы речи. Для выбора откройте `Параметры:Настройки...` в NVDA, а затем выберите категорию `Речь`. Обычно доступны как минимум три варианта: eSpeak NG, Microsoft Speech API и голоса Windows OneCore. Все синтезаторы работают, но голоса Windows OneCore неправильно произносят «a», поэтому рекомендуется выбрать другой вариант. В частности, хорошей заменой голосам OneCore служит Microsoft Speech API.
+
+## Информация для пользователей MathCAT
+
+MathCAT поддерживает ряд настроек озвучивания, Брайля и навигации. Они описаны ниже.
+Пока поддерживаются не все настройки. Для каждой настройки указано текущее состояние поддержки. Символ ✓ перед настройкой означает, что она поддерживается хотя бы частично.
+
+Примечание: в NVDA настройки задаются в диалоге параметров MathCAT. Чтобы открыть его, перейдите в параметры NVDA, выберите «Параметры», а затем «Настройки MathCAT...». Настройки разделены на три категории: «Речь», «Навигация» и «Брайль». Такое же разделение используется ниже.
+
+MathCAT поддерживает несколько режимов навигации. Способ входа в режим навигации и выхода из него зависит от используемой вспомогательной технологии (подробнее в списке ниже). MathCAT принимает те же команды и сочетания клавиш, что и MathPlayer. Они [перечислены в этом документе](nav-commands.md).
+Документация описывает множество полезных способов навигации по математическим выражениям. Чтобы быстро начать работу:
+
+* Используйте клавиши-стрелки для перемещения влево, вправо, вверх и вниз по структуре математического выражения, например для входа в дробь и выхода из неё.
+* Внутри таблицы нажимайте Ctrl+стрелка для перемещения по ячейкам.
+* Нажимайте Home и End для перехода к началу и концу выражения.
+* Нажимайте пробел для озвучивания текущей позиции.
+* Нажимайте Shift+стрелка вверх или Shift+стрелка вниз для изменения режима навигации (см. [документацию по навигации](nav-commands.md)).
+
+Чтобы начать навигацию:
+
+* NVDA: нажмите NVDA+Alt+M или пробел для входа в режим навигации по математическому выражению, а для выхода нажмите Escape.
+
+Навигация MathCAT работает одинаково в Word и в браузере.
+
+При навигации по выражению сочетание Ctrl+C в NVDA копирует математическое содержимое текущего узла. Поддерживаются следующие форматы:
+
+* MathML (по умолчанию);
+* LaTeX;
+* ASCIIMath;
+* текст для озвучивания.
+
+## Список настроек
+
+Ниже перечислены настройки. Большинство из них принимают только ограниченный набор значений, который указан в описании.
+Значение по умолчанию приведено в \[квадратных скобках\].
+
+### Настройки речи
+
+* ✓Impairment: [Blindness]
+ * Значения: Blindness, LowVision, LearningDisability.
+ * Описание: определяет, следует ли устранять неоднозначность некоторых обозначений при озвучивании.
+ * Состояние: основное внимание уделялось значению Blindness, но другие значения также частично поддерживаются. Эту поддержку необходимо улучшить.
+
+* ✓Language: [en]
+ * Значения: любой известный код языка и подкод, например `en-uk`.
+ [Список вариантов приведён на этом сайте](https://www.venea.net/web/culture_code).
+ * Описание: определяет используемый язык.
+ Если региональный вариант не найден среди правил озвучивания, используется основной язык. Если не найдены и правила для основного языка, используется английский язык (`en`).
+ * Состояние: сейчас поддерживаются только английский, испанский, финский, индонезийский, шведский, вьетнамский и китайский языки.
+ Другие языки будут добавляться с помощью добровольцев.
+
+* ✓SpeechStyle: [ClearSpeak]
+ * Значения: любой реализованный стиль речи. Сейчас доступны только ClearSpeak и SimpleSpeak.
+ * Описание: стиль речи, то есть согласованный подход к озвучиванию выражения.
+ * ClearSpeak был разработан ETS для важных экзаменов, например SAT. [Подробности спецификации ClearSpeak приведены в этом документе Word](../ClearSpeakRulesAndPreferences.docx).
+ * SimpleSpeak стремится сократить озвучивание: простые выражения, например $\frac{a}{b}$, читаются быстро, без обрамляющих слов («a over b»). Они отличаются от более сложных выражений, например $\frac{a}{b+1}$, в которых обрамляющие слова используются всегда («fraction a over b plus 1 end fraction»).
+ * Состояние: сейчас реализованы только ClearSpeak и SimpleSpeak, но в дальнейшем, вероятно, будет реализован MathSpeak.
+
+* ✓Verbosity: [Medium]
+ * Значения: Terse, Medium, Verbose.
+ * Описание: определяет количество дополнительных слов при озвучивании. Например, в подробном режиме квадратный корень читается как «the square root of x», а в кратком — как «square root x».
+ * Состояние: настройка поддерживается, но со временем, вероятно, будет дорабатываться.
+
+* ✓MathRate: [100]
+ * Значения: число от 1 до 100.
+ * Описание: изменяет относительную скорость речи в процентах от стандартной скорости речевого синтезатора. Значение `100` означает, что математические выражения читаются с той же скоростью, что и обычный текст.
+ Настройка работает только в реализациях, которые указывают MathCAT создавать разметку речевого синтезатора, например SSML.
+ * Состояние: настройка должна работать в NVDA.
+
+* ✓PauseFactor: [50]
+ * Значения: число от 0 до 100.
+ * Описание: изменяет относительную длительность добавляемых MathCAT пауз. Значение 0 отключает все паузы, а значение 100 увеличивает обычную длительность пауз в десять раз.
+ Настройка работает только в реализациях, которые указывают MathCAT создавать разметку речевого синтезатора, например SSML.
+ * Состояние: настройка должна работать в NVDA.
+
+* ✓SpeechSound: [None]
+ * Значения: None, Beep.
+ * Описание: перед чтением выражения и после него воспроизводится звуковой сигнал.
+ * Состояние: настройка должна работать в NVDA.
+
+* SubjectArea: [General]
+ * Состояние: настройка использовалась в MathPlayer, но пока не реализована. Я жду дальнейшего обсуждения в рабочей группе MathML: возможно, эта настройка будет использоваться для задания разных значений `intent` по умолчанию.
+
+* Chemistry: [SpellOut]
+ * Значения: SpellOut, AsCompound, Off.
+ * Описание: определяет способ чтения химических формул. Примеры для $\mathrm{H}_2\mathrm{O}$:
+ * ✓SpellOut: «H 2 O» (настройка подробности определяет, произносятся ли слова `sub` и `super`);
+ * AsCompound: «Water»;
+ * ✓Off: «H sub 2 O».
+ * Состояние: реализовано множество эвристик для определения того, является ли запись химической формулой. Распознавание химических обозначений не всегда очевидно, поэтому MathCAT иногда может не распознать формулу или по ошибке принять другую запись за химическую. Работа группы MathML может существенно упростить авторам явное указание химических формул.
+
+SpeechOverrides:
+
+* ✓CapitalLetters: "cap" # слово-префикс для прописных букв, если оно не задано в unicode.yaml; пустая строка передаёт обработку программе экранного доступа
+* LeftParen: "" # переопределение слова
+* RightParen: "" # переопределение слова
+
+ClearSpeak содержит ряд настроек. Они предназначены для авторов, но могут задаваться и пользователями, хотя обычно не слишком им полезны.
+
+* ✓CapitalLetters: Auto, SayCaps или изменение высоты тона
+* ✓AbsoluteValue: Auto, AbsEnd, Cardinality, Determinant
+* ✓Fraction: Auto, Ordinal, Over, FracOver, General, EndFrac, GeneralEndFrac, OverEndFrac, Per
+* ✓Exponent: Auto, Ordinal, OrdinalPower, AfterPower
+* ✓Roots: Auto, PosNegSqRoot, RootEnd, PosNegSqRootEnd
+* ✓Functions: Auto, None
+* ✓Trig: Auto, TrigInverse, ArcTrig
+* ✓Log: Auto, LnAsNaturalLog
+* ✓ImpliedTimes: Auto, MoreImpliedTimes, None
+* ✓Paren: Auto, Speak, SpeakNestingLevel, Silent, CoordPoint, Interval
+* ✓Matrix: Auto, SpeakColNum, SilentColNum, EndMatrix, Vector, EndVector, Combinatorics
+* ✓MultiLineLabel: Auto, Case, Constraint, Equation, Line, None, Row, Step
+* ✓MultiLineOverview: Auto, None
+* ✓MultiLinePausesBetweenColumns: Short, Long
+* ✓Sets: Auto, woAll, SilentBracket
+* ✓MultSymbolX: Auto, By, Cross
+* ✓MultSymbolDot: Auto, Dot
+* ✓TriangleSymbol: Auto, Delta
+* ✓Ellipses: Auto, AndSoOn
+* ✓VerticalLine: Auto, SuchThat, Divides, Given
+* ✓SetMemberSymbol: Auto, Belongs, Element, Member
+* ✓Prime: Auto, Angle, Length
+* ✓CombinationPermutation: Auto, ChoosePermute
+* ✓Bar: Auto, Bar, Conjugate, Mean
+
+### Настройки навигации (см. [документацию по навигации](nav-commands.md))
+
+* ✓NavMode: Enhanced — Enhanced, Simple, Character.
+* ResetNavMode: false — запоминать и использовать предыдущее значение.
+* Overview: false — озвучивать выражение или давать его описание и обзор.
+* ResetOverView: true — запоминать и использовать предыдущее значение.
+* ✓NavVerbosity: Medium — Terse, Medium, Full (слова для произнесения команды навигации).
+* ✓AutoZoomOut: true — автоматически уменьшать детализацию двумерных выражений. Если настройка отключена, для принудительного уменьшения детализации используйте Shift+стрелка.
+ * `true`: если вы находитесь у края двумерного выражения, например дроби или надстрочного индекса, и пытаетесь выйти из него влево или вправо, перемещение разрешается, а уровень детализации устанавливается в соответствии с предыдущим или следующим элементом.
+ * `false`: перемещение влево или вправо за край двумерного выражения запрещено. Чтобы переместиться, необходимо уменьшить детализацию, возможно несколько раз, пока вы не перестанете находиться у края.
+* CopyMathAS: определяет формат копирования математического содержимого текущего узла навигации: MathML, LaTeX, ASCIIMath или текст для озвучивания.
+
+
+### Настройки Брайля
+
+* ✓BrailleCode: [Nemeth]
+ * Значения: любая реализованная система записи Брайля.
+ * Описание: используемая система математической записи Брайля.
+ * Состояние: сейчас поддерживаются ASCIIMath, ASCIIMath-Finnish, CMU, LaTeX, код Немета, шведская система, UEB и вьетнамская система. Поддержка других систем записи Брайля зависит от помощи новых участников.
+* ✓BrailleNavHighlight: [EndPoints]
+ * Значения: Off, FirstChar, EndPoints, All.
+ * Описание: выделяет выбранный узел навигации точками 7 и 8.
+* UEB:
+ * ✓START_MODE: [Grade2]
+ * Значения: Grade1, Grade2.
+ * Описание: предполагаемый начальный режим UEB. Значение Grade1 означает, что используется режим фрагмента Grade 1.
+ * ✓UseSpacesAroundAllOperators: [false]
+ * Значения: true/false.
+ * Описание: рекомендации UEB предполагают, что в младших классах может быть полезно добавлять пробелы вокруг таких операторов, как `+` и `-`. Обычно пробелы добавляются только вокруг операторов отношений, например `=` и `<`.
+
+Во многих системах записи Брайля можно определять пользовательские символы. MathCAT предоставляет для этого несколько настроек.
+
+В коде Немета определены начертания Bold, Italic, SansSerif и Script, но не определено начертание DoubleStruck (Blackboard Bold).
+Здесь можно задать определяемое транскрибатором изменение начертания. По умолчанию DoubleStruck сопоставляется с Italic.
+
+* Nemeth:
+ * ✓SansSerif: "⠠⠨"
+ * ✓Bold: "⠸"
+ * ✓DoubleStruck: "⠨"
+ * ✓Script: "⠈"
+ * ✓Italic: "⠨"
+
+В [руководстве UEB по техническим материалам](https://iceb.org/Guidelines_for_Technical_Material_2008-10.pdf) рекомендуется обычно обрабатывать Fraktur и DoubleStruck как Script.
+Вместо этого здесь можно задать пользовательский префиксный индикатор начертания.
+Примечание: префиксы с первого по пятый: "⠈⠼", "⠘⠼", "⠸⠼", "⠐⠼", "⠨⠼".
+
+* UEB:
+ * ✓DoubleStruck: "⠈" [script]
+ * ✓Fraktur: "⠈" [script]
+ * ✓SansSerif: "⠈⠼" [первый определяемый транскрибатором префиксный индикатор начертания]
+ * ✓GreekVariant: "⠨" [по умолчанию Greek]
+
+Набор определяемых символов для вьетнамской системы ещё обсуждается. Вероятно, некоторые значения изменятся.
+
+* Vietnam:
+ * ✓UseDropNumbers: [false]
+ * Значения: true, false.
+ * Описание: опускает цифры на строку ниже в простых числовых дробях.
+ * ✓DoubleStruck: "⠈" [script]
+ * ✓Fraktur: "⠈" [script]
+ * ✓SansSerif: "⠈⠼" [первый определяемый транскрибатором префиксный индикатор начертания]
+ * ✓GreekVariant: "⠨" [по умолчанию Greek]
+
+### Другие настройки
+
+MathCAT исправляет некачественный MathML. В MathML числа часто размечаются неправильно. Чтобы исправить их корректно, MathCAT должен знать региональные настройки: символы, которые могут разделять группы цифр, и символы десятичного разделителя. Обычно ассистивная технология задаёт эти параметры на основе кода страны в документе. Но код страны может отсутствовать, и тогда ассистивной технологии приходится определять формат по коду языка.
+
+* DecimalSeparators: "." # [по умолчанию]
+* BlockSeparators: ", \u00A0\u202F" # [по умолчанию; включает два варианта неразрывных пробелов]
From ce7c227566f6248e788a61b00d7cf251eb6eea11 Mon Sep 17 00:00:00 2001
From: Danil <81031453+Kostenkov-2021@users.noreply.github.com>
Date: Mon, 1 Jun 2026 15:04:35 +0300
Subject: [PATCH 2/2] First version of Russian Braille
This commit adds first druft version for Russian Braille.
---
Rules/Braille/Russian/Russian_Rules.yaml | 317 +++++++++++++++++++++++
Rules/Braille/Russian/definitions.yaml | 2 +
Rules/Braille/Russian/unicode-full.yaml | 301 +++++++++++++++++++++
Rules/Braille/Russian/unicode.yaml | 289 +++++++++++++++++++++
src/braille.rs | 34 +++
tests/braille.rs | 4 +
tests/braille/Russian/russian.rs | 37 +++
7 files changed, 984 insertions(+)
create mode 100644 Rules/Braille/Russian/Russian_Rules.yaml
create mode 100644 Rules/Braille/Russian/definitions.yaml
create mode 100644 Rules/Braille/Russian/unicode-full.yaml
create mode 100644 Rules/Braille/Russian/unicode.yaml
create mode 100644 tests/braille/Russian/russian.rs
diff --git a/Rules/Braille/Russian/Russian_Rules.yaml b/Rules/Braille/Russian/Russian_Rules.yaml
new file mode 100644
index 00000000..1effd8c4
--- /dev/null
+++ b/Rules/Braille/Russian/Russian_Rules.yaml
@@ -0,0 +1,317 @@
+---
+# Russian mathematical braille rules.
+
+-
+ name: whitespace
+ tag: "!*"
+ match: "not(self::m:math) and not($MatchingWhitespace) and (@data-previous-space-width >= 0.25 or @data-following-space-width >= 0.25)"
+ replace:
+ - with:
+ variables: [MatchingWhitespace: "true()"]
+ replace:
+ - test:
+ - if: "@data-previous-space-width > 1.1"
+ then: [t: "⠀"]
+ - else_if: "@data-previous-space-width >= 0.25"
+ then: [t: "W"]
+ - x: "."
+ - test:
+ - if: "@data-following-space-width > 1.1"
+ then: [t: "⠀"]
+ - else_if: "@data-following-space-width >= 0.25"
+ then: [t: "W"]
+
+-
+ name: omission-intent
+ tag: "!*"
+ match: "contains(@intent, ':blank')"
+ replace: [t: "⠀"]
+
+-
+ name: unicode-override
+ tag: "*"
+ match: "@data-unicode"
+ replace: [x: "@data-unicode"]
+
+-
+ name: default
+ tag: math
+ match: "."
+ variables:
+ - RowStart: "''"
+ - RowEnd: "''"
+ - MatchingWhitespace: "false()"
+ replace: [x: "*"]
+
+-
+ name: default
+ tag: [mrow, mstyle, semantics]
+ match: "."
+ replace: [x: "*[1] | *[position()>1]"]
+
+-
+ name: no-content
+ tag: [math, mrow]
+ match: "not(*)"
+ replace: [t: "⠀"]
+
+-
+ name: default
+ tag: msqrt
+ match: "."
+ replace:
+ - t: "⠩⠱"
+ - x: "*[1]"
+ - t: "⠹"
+
+-
+ name: default
+ tag: mroot
+ match: "."
+ replace:
+ - t: "⠩"
+ - x: "*[2]"
+ - t: "⠱"
+ - x: "*[1]"
+ - t: "⠹"
+
+-
+ name: default
+ tag: mfrac
+ match: "."
+ replace:
+ - t: "⠆"
+ - x: "*[1]"
+ - t: "⠀⠳"
+ - x: "*[2]"
+ - t: "⠰"
+
+-
+ name: binomial-frac
+ tag: mrow
+ match: "IsBracketed(., '(', ')') and *[2][self::m:mfrac][@linethickness=0]"
+ replace:
+ - x: "*[1]"
+ - t: "⠨⠉⠡"
+ - x: "*[2]/*[2]"
+ - t: "⠌"
+ - x: "*[2]/*[1]"
+ - x: "*[3]"
+
+-
+ name: binomial-table
+ tag: mrow
+ match: "IsBracketed(., '(', ')') and *[2][self::m:mtable][count(*)=2 and count(*[1])=1] and contains(@intent, 'binomial(')"
+ replace:
+ - x: "*[1]"
+ - t: "⠨⠉⠡"
+ - x: "*[2]/*[2]/*[1]/*[1]"
+ - t: "⠌"
+ - x: "*[2]/*[1]/*[1]/*[1]"
+ - x: "*[3]"
+
+-
+ name: default-matrix
+ tag: mrow
+ variables:
+ - RowStart: "*[1]"
+ - RowEnd: "*[3]"
+ match:
+ - "*[2][self::m:mtable] and"
+ - (IsBracketed(., '(', ')') or IsBracketed(., '[', ']') or IsBracketed(., '|', '|'))
+ replace: [x: "*[2]"]
+
+-
+ name: default
+ tag: mtable
+ match: "."
+ replace:
+ - t: "⠞⠃⠇"
+ - x: "*"
+
+-
+ name: default
+ tag: [mtr, mlabeledtr]
+ match: "."
+ replace:
+ - test:
+ if: "preceding-sibling::*"
+ then: [t: "⠨⠳"]
+ - x: $RowStart
+ - test:
+ if: .[self::m:mlabeledtr]
+ then: [x: "*[position()>1]"]
+ else: [x: "*"]
+ - x: $RowEnd
+
+-
+ name: default
+ tag: mtd
+ match: "."
+ replace:
+ - test:
+ if: "*"
+ then:
+ - test:
+ if: "preceding-sibling::*"
+ then: [t: "W"]
+ - x: "*"
+
+-
+ name: single-char-exceptions
+ tag: msup
+ match: "*[2][translate(., \"'*`ª°²³´¹º′″‴‵‶‷⁗\",'')='']"
+ replace: [x: "*"]
+
+-
+ name: default
+ tag: [msub, munder]
+ match: "."
+ replace:
+ - x: "*[1]"
+ - test:
+ if: "self::m:munder"
+ then: [t: "⠨⠡"]
+ else: [t: "⠡"]
+ - x: "*[2]"
+ - t: "⠱"
+
+-
+ name: default
+ tag: [msup, mover]
+ match: "."
+ replace:
+ - x: "*[1]"
+ - test:
+ if: "self::m:mover"
+ then: [t: "⠨⠌"]
+ else: [t: "⠌"]
+ - x: "*[2]"
+ - t: "⠱"
+
+-
+ name: default
+ tag: [msubsup, munderover]
+ match: "count(*)=3"
+ replace:
+ - x: "*[1]"
+ - test:
+ if: "self::m:munderover"
+ then: [t: "⠨⠡"]
+ else: [t: "⠡"]
+ - x: "*[2]"
+ - test:
+ if: "self::m:munderover"
+ then: [t: "⠨⠌"]
+ else: [t: "⠌"]
+ - x: "*[3]"
+ - t: "⠱"
+
+-
+ name: default
+ tag: mmultiscripts
+ match: "."
+ replace: [x: "*"]
+
+-
+ name: default
+ tag: menclose
+ match: "."
+ replace:
+ - t: "⠣"
+ - x: "*"
+ - t: "⠜"
+
+-
+ name: default
+ tag: mo
+ match: "."
+ replace:
+ - x: "text()"
+
+-
+ name: default
+ tag: mn
+ match: "."
+ replace:
+ - x: "BrailleChars(., 'Russian')"
+
+-
+ name: functions
+ tag: mi
+ match: "IsInDefinition(., 'Speech', 'FunctionNames')"
+ replace:
+ - test:
+ - if: ".='arcsin'"
+ then: [t: "⠫⠁⠎"]
+ - else_if: ".='arccos'"
+ then: [t: "⠫⠁⠉"]
+ - else_if: ".='arctg' or .='arctan'"
+ then: [t: "⠫⠁⠞"]
+ - else_if: ".='arcctg' or .='arccotan' or .='arccot'"
+ then: [t: "⠫⠁⠉⠞"]
+ - else_if: ".='sin'"
+ then: [t: "⠫⠎"]
+ - else_if: ".='cos'"
+ then: [t: "⠫⠉"]
+ - else_if: ".='tg' or .='tan'"
+ then: [t: "⠫⠞"]
+ - else_if: ".='ctg' or .='cotan' or .='cot'"
+ then: [t: "⠫⠉⠞"]
+ - else_if: ".='log'"
+ then: [t: "⠫⠇"]
+ - else_if: ".='ln'"
+ then: [t: "⠫⠇⠝"]
+ - else_if: ".='lg'"
+ then: [t: "⠫⠇⠛"]
+ - else_if: ".='lim'"
+ then: [t: "⠫⠇⠍"]
+ - else_if: ".='min'"
+ then: [t: "⠫⠍⠝"]
+ - else_if: ".='max'"
+ then: [t: "⠫⠍⠭"]
+ - else_if: ".='exp'"
+ then: [t: "⠫⠑"]
+ else: [x: "BrailleChars(., 'Russian')"]
+
+-
+ name: default
+ tag: [mi, mtext]
+ match: "."
+ replace:
+ - x: "BrailleChars(., 'Russian')"
+
+-
+ name: default
+ tag: ms
+ match: "."
+ replace:
+ - test:
+ if: "string(@lquote)!=''"
+ then: [x: "@lquote"]
+ else: [t: "⠦"]
+ - x: "BrailleChars(., 'Russian')"
+ - test:
+ if: "string(@rquote)!=''"
+ then: [x: "@rquote"]
+ else: [t: "⠴"]
+
+-
+ name: default-children
+ tag: "*"
+ match: "*"
+ replace:
+ - x: "*"
+
+-
+ name: default-no-children
+ tag: "*"
+ match: "text()"
+ replace:
+ - x: "text()"
+
+-
+ name: default-no-text
+ tag: "*"
+ match: "."
+ replace: [t: ""]
diff --git a/Rules/Braille/Russian/definitions.yaml b/Rules/Braille/Russian/definitions.yaml
new file mode 100644
index 00000000..4d0c77ef
--- /dev/null
+++ b/Rules/Braille/Russian/definitions.yaml
@@ -0,0 +1,2 @@
+---
+- include: "../definitions.yaml"
diff --git a/Rules/Braille/Russian/unicode-full.yaml b/Rules/Braille/Russian/unicode-full.yaml
new file mode 100644
index 00000000..f78feac3
--- /dev/null
+++ b/Rules/Braille/Russian/unicode-full.yaml
@@ -0,0 +1,301 @@
+---
+ - "⋇": [tc: "1⠌⠯⠦"] # 0x22C7 (Division times)
+ - "⊩": [tc: "⠸⠳⠿⠸⠒"] # 0x22A9 (Forces)
+ - "ℏ": [tc: "⠈⠒⠓"] # t: "B"F (reduced Plank's constant)
+ - "ⅆ": [tc: "⠙"] # 0x2146
+ - "ⅇ": [tc: "⠑"] # 0x2147
+ - "ⅈ": [tc: "⠊"] # 0x2148
+ - "⨯": [tc: "⠐⠦"] # U+2A2F(VECTOR OR CROSS PRODUCT) -- make the same as 0x00D7 (Multiplication sign)
+
+
+ - "¼": [tc: "#N⠁N⠌N⠙"] # 0x00BC (Vulgar Fraction One Quarter)
+ - "½": [tc: "#N⠁N⠌N⠃"] # 0x00BD (Vulgar Fraction One Half)
+ - "¾": [tc: "#N⠉N⠌N⠙"] # 0x00BE (Vulgar Fraction Three Quarters)
+ - "⅐": [tc: "#N⠁N⠌N⠛"] # 0x2150 (Vulgar Fraction One Seventh)
+ - "⅑": [tc: "#N⠁N⠌N⠊"] # 0x2151 (Vulgar Fraction One Ninth)
+ - "⅒": [tc: "#N⠁N⠌N⠁N⠚"] # 0x2152 (Vulgar Fraction One Tenth)
+ - "⅓": [tc: "#N⠁N⠌N⠉"] # 0x2153 (Vulgar Fraction One Third)
+ - "⅔": [tc: "#N⠃N⠌N⠉"] # 0x2154 (Vulgar Fraction Two Thirds)
+ - "⅕": [tc: "#N⠁N⠌N⠑"] # 0x2155 (Vulgar Fraction One Fifth)
+ - "⅖": [tc: "#N⠃N⠌N⠑"] # 0x2156 (Vulgar Fraction Two Fifths)
+ - "⅗": [tc: "#N⠉N⠌N⠑"] # 0x2157 (Vulgar Fraction Three Fifths)
+ - "⅘": [tc: "#N⠙N⠌N⠑"] # 0x2158 (Vulgar Fraction Four Fifths)
+ - "⅙": [tc: "#N⠁N⠌N⠋"] # 0x2159 (Vulgar Fraction One Sixth)
+ - "⅚": [tc: "#N⠑N⠌N⠋"] # 0x215A (Vulgar Fraction Five Sixths)
+ - "⅛": [tc: "#N⠁N⠌N⠓"] # 0x215B (Vulgar Fraction One Eighth)
+ - "⅜": [tc: "#N⠉N⠌N⠓"] # 0x215C (Vulgar Fraction Three Eighths)
+ - "⅝": [tc: "#N⠑N⠌N⠓"] # 0x215D (Vulgar Fraction Five Eighths)
+ - "⅞": [tc: "#N⠛N⠌N⠓"] # 0x215E (Vulgar Fraction Seven Eighths)
+ - "↉": [tc: "#N⠚N⠌N⠑"] # 0x2189 (Vulgar Fraction Zero Thirds)
+
+
+
+ - "ℊ": [tc: "TL⠛⠁"] # 0x210a (Script Small G)
+ - "ℋ": [tc: "TCL⠓⠁"] # 0x210b (Script Capital H)
+ - "ℒ": [tc: "TCL⠇"] # 0x2113 (Script Capital L)
+ - "ℓ": [tc: "TL⠇"] # 0x2113 (Script Small L)
+ - "℘": [tc: "TCL⠏"] # 0x2118 (Script Capital P)
+ - "ℛ": [tc: "TCL⠗"] # 0x211B (Script Capital R)
+ - "ℯ": [tc: "TL⠑"] # 0x212F (Script Small E)
+ - "ℰ": [tc: "TCL⠑"] # 0x2130 (Script Capital E)
+ - "ℱ": [tc: "TCL⠋"] # 0x2131 (Script Capital F)
+ - "ℳ": [tc: "TCL⠍"] # 0x2133 (Script Capital M)
+ - "ℴ": [tc: "TL⠕"] # 0x21334 (Script Small O)
+ - "ℌ": [tc: "DCL⠓"] # 0x210C (Fraktur Capital H)
+ - "ℑ": [tc: "DCL⠊"] # 0x2111 (Fraktur Capital I)
+ - "ℜ": [tc: "DCL⠗"] # 0x211C (Fraktur Capital R)
+ - "ℨ": [tc: "DCL⠵"] # 0x2128 (Fraktur Capital Z)
+ - "ℭ": [tc: "DCL⠉"] # 0x22DC (Fraktur Capital C)
+
+ - "ℂ": [tc: "𝔹CL⠉"] # 0x2102
+ - "ℍ": [tc: "𝔹CL⠓"] # 0x210d
+ - "ℕ": [tc: "𝔹CL⠝"] # 0x2115
+ - "ℙ": [tc: "𝔹CL⠏"] # 0x2119
+ - "ℚ": [tc: "𝔹CL⠟"] # 0x211a
+ - "ℝ": [tc: "𝔹CL⠗"] # 0x211d
+ - "ℤ": [tc: "𝔹CL⠵"] # 0x2124
+
+
+ - "𝚨-𝛀": # 0x1d6a8 - 0x1d6c0
+ - tc: "B"
+ - spell: "translate('.', '𝛂𝛃𝛄𝛅𝛆𝛇𝛈𝛉𝛊𝛋𝛌𝛍𝛎𝛏𝛐𝛑𝛒𝛓𝛔𝛕𝛖𝛗𝛘𝛙𝛚', 'αβγδεζηθικλμνξοπρςστυφχψω')"
+
+
+ - "𝛂-𝛚": # 0x1d6c2 - 0x1d6da
+ - tc: "BGL"
+ - spell: "translate('.', '𝛂𝛃𝛄𝛅𝛆𝛇𝛈𝛉𝛊𝛋𝛌𝛍𝛎𝛏𝛐𝛑𝛒𝛓𝛔𝛕𝛖𝛗𝛘𝛙𝛚', 'αβγδεζηθικλμνξοπρςστυφχψω')"
+
+
+ - "𝔄-𝔜": # 0x1d504 - 0x1d51d ('z' version is reserved)
+ - tc: "DC"
+ - spell: "translate('.', '𝔄𝔅𝔇𝔈𝔉𝔊𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔𝔖𝔗𝔘𝔙𝔚𝔛𝔜', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝔞-𝔷": # 0x1d51e - 0x1d537
+ - tc: "D"
+ - spell: "translate('.', '𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝕬-𝖅": # 0x1D56C - 0x1D585
+ - tc: "BDC"
+ - spell: "translate('.', '𝕬𝕭𝕮𝕯𝕰𝕱𝕲𝕳𝕴𝕵𝕶𝕷𝕸𝕹𝕺𝕻𝕼𝕽𝕾𝕿𝖀𝖁𝖂𝖃𝖄𝖅', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝖆-𝖟": # 0x1d586 - 0x1d59f
+ - tc: "D"
+ - spell: "translate('.', '𝖆𝖇𝖈𝖉𝖊𝖋𝖌𝖍𝖎𝖏𝖐𝖑𝖒𝖓𝖔𝖕𝖖𝖗𝖘𝖙𝖚𝖛𝖜𝖝𝖞𝖟', 'abcdefghijklmnopqrstuvwxyz')"
+
+ # double struck (blackboard bold) chars in math alphabetic block and also MathType private use area
+ # Some of these are reserved because they were used in Plane 0 -- that shouldn't be an issue other than causing the other chars to not display
+ - "𝔸-𝕐": # 0x1d504 - 0x1d51d ('z' version is reserved)
+ - tc: "DC"
+ - spell: "translate('.', '𝔸𝔹𝔻𝔼𝔽𝔾𝕀𝕁𝕂𝕃𝕄𝕆𝕊𝕋𝕌𝕍𝕎𝕏𝕐', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝕒-𝕫": # 0x1d552 - 0x1d56b
+ - tc: "D"
+ - spell: "translate('.', '𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝟘-𝟡": # 0x1d7d8 - 0x1d7e1
+ - tc: "D"
+ - spell: "translate('.', '𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡', '0123456789')"
+
+ # script chars in math alphabetic block and also MathType private use area
+ - "𝒜-𝒵": # 0x1d49c - 0x1d4b5
+ - tc: "TsC"
+ - spell: "translate('.', '𝒜𝒞𝒟𝒢𝒥𝒦𝒩𝒪𝒫𝒬𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵', 'abcdefghijklmnopqrstuvwxyza')"
+
+ - "𝒶-𝓏": # 0x1d4b6 - 0x1d4cf
+ - tc: "Ts"
+ - spell: "translate('.', '𝒶𝒷𝒸𝒹𝒻𝒽𝒾𝒿𝓀𝓁𝓂𝓃𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏', 'abcdefghijklmnopqrstuvwxyz')"
+
+ # bold script chars in math alphabetic block
+ - "𝓐-𝓩": # 0x1d4d0 - 0x1d4e9
+ - tc: "BTs"
+ - spell: "translate('.', '𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝓪-𝔃": # 0x1d4ea - 0x1d503
+ - tc: "BTs"
+ - spell: "translate('.', '𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝐀-𝐙": # 0x1d400 - 0x1d419
+ - tc: "B"
+ - spell: "translate('.', '𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝐚-𝐳": # 0x1d41a - 0x1d433
+ - tc: "B"
+ - spell: "translate('.', '𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝐴-𝑍": # 0x1d434 - 0x1d44d
+ # don't include italics
+ - tc: "C"
+ - spell: "translate('.', '𝐴𝐵𝐶𝐷𝐸𝐹𝐺𝐻𝐼𝐽𝐾𝐿𝑀𝑁𝑂𝑃𝑄𝑅𝑆𝑇𝑈𝑉𝑊𝑋𝑌𝑍', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝑎-𝑧": # 0x1d44e - 0x1d467
+ # don't include italics
+ - spell: "translate('.', '𝑎𝑏𝑐𝑑𝑒𝑓𝑔𝑖𝑗𝑘𝑙𝑚𝑛𝑜𝑝𝑞𝑟𝑠𝑡𝑢𝑣𝑤𝑥𝑦𝑧', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝑨-𝒁": # 0x1d468 - 0x1d481
+ - tc: "BIC"
+ - spell: "translate('.', '𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝒂-𝒛": # 0x1d482 - 0x1d49b
+ - spell: "translate('.', '𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝖠-𝖹": # 0x1d5a0 - 0x1d5b9
+ - spell: "translate('.', '𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
+
+ - "𝖺-𝗓": # 0x1d5ba - 0x1d5d3
+ - spell: "translate('.', '𝖺𝖻𝖼𝖽𝖾𝖿𝗀𝗁𝗂𝗃𝗄𝗅𝗆𝗇𝗈𝗉𝗊𝗋𝗌𝗍𝗎𝗏𝗐𝗑𝗒𝗓', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝗔-𝗭": # 0x1d5d4 - 0x1d5ed
+ - test:
+ if: "not($IgnoreBold)"
+ then: [tc: "B"]
+ - spell: "translate('.', '𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
+
+ - "𝗮-𝘇": # 0x1d5ee - 0x1d607
+ - test:
+ if: "not($IgnoreBold)"
+ then: [tc: "B"]
+ - spell: "translate('.', '𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝘈-𝘡": # 0x1d608 - 0x1d621
+ # - tc: "italic"
+ - spell: "translate('.', '𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘓𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
+
+ - "𝘢-𝘻": # 0x1d622 - 0x1d63b
+ # - tc: "italic"
+ - spell: "translate('.', '𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝘼-𝙕": # 0x1d63c - 0x1d655
+ # - tc: "bold italic"
+ - test:
+ if: "$IgnoreBold"
+ then: [tc: I]
+ else: [tc: "BI"]
+ - spell: "translate('.', '𝘼𝘽𝘾𝘿𝙀𝙁𝙂𝙃𝙄𝙅𝙆𝙇𝙈𝙉𝙊𝙋𝙌𝙍𝙎𝙏𝙐𝙑𝙒𝙓𝙔𝙕', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"
+
+ - "𝙖-𝙯": # 0x1d656 - 0x1d66f
+ # - tc: "bold italic"
+ - test:
+ if: "$IgnoreBold"
+ then: [tc: I]
+ else: [tc: "BI"]
+ - spell: "translate('.', '𝙖𝙗𝙘𝙙𝙚𝙛𝙜𝙝𝙞𝙟𝙠𝙡𝙢𝙣𝙤𝙥𝙦𝙧𝙨𝙩𝙪𝙫𝙬𝙭𝙮𝙯', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝙰-𝚉": # 0x1d670 - 0x1d689
+ - tc: "C"
+ - spell: "translate('.', '𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝚊-𝚣": # 0x1d68a - 0x1d6a3
+ - spell: "translate('.', '𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣', 'abcdefghijklmnopqrstuvwxyz')"
+
+ - "𝚤𝚥": # 0x1d6a4, 0x1d6a5
+ - spell: "translate('.', '𝚤𝚥', 'ij')" # not sure what else these should be
+
+ - "𝚨-𝛀": # 0x1d6a8 - 0x1d6c0
+ - test:
+ if: "not($IgnoreBold)"
+ then: [tc: "B"]
+ - spell: "translate('.', '𝚨𝚩𝚪𝚫𝚬𝚭𝚮𝚯𝚰𝚱𝚲𝚳𝚴𝚵𝚶𝚷𝚸𝚹𝚺𝚻𝚼𝚽𝚾𝚿𝛀', 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ')"
+
+ - "𝛂-𝛚": # 0x1d6c2 - 0x1d6da
+ - test:
+ if: "not($IgnoreBold)"
+ then: [tc: "B"]
+ - spell: "translate('.', '𝛂𝛃𝛄𝛅𝛆𝛇𝛈𝛉𝛊𝛋𝛌𝛍𝛎𝛏𝛐𝛑𝛒𝛓𝛔𝛕𝛖𝛗𝛘𝛙𝛚', 'αβγδεζηθικλμνξοπρςστυφχψω')"
+
+ - "𝛁": # 0x1d6c1
+ - test:
+ if: "not($IgnoreBold)"
+ then: [tc: "B"]
+ - spell: "translate('.', '𝜵', '∇')"
+
+ - "𝛛𝛜𝛝𝛞𝛟𝛠𝛡": # 0x1D6DB - 0x1D6E1
+ - test:
+ if: "not($IgnoreBold)"
+ then: [tc: "B"]
+ - spell: "translate('.', '𝛛𝛜𝛝𝛞𝛟𝛠𝛡', '∂εθκφρπ')"
+
+ - "𝛢-𝛺": # 0x1d6e2 - 0x1d6fa
+ - tc: "IC"
+ - spell: "translate('.', '𝛢𝛣𝛤𝛥𝛦𝛧𝛨𝛩𝛪𝛫𝛬𝛭𝛮𝛯𝛰𝛱𝛲𝛳𝛴𝛵𝛶𝛷𝛸𝛹𝛺', 'αβγδεζηθικλμνξοπρςστυφχψω')"
+
+ - "𝛼-𝜔": # 0x1d6fc - 0x1d714
+ - spell: "translate('.', '𝛼𝛽𝛾𝛿𝜀𝜁𝜂𝜃𝜄𝜅𝜆𝜇𝜈𝜉𝜊𝜋𝜌𝜍𝜎𝜏𝜐𝜑𝜒𝜓𝜔', 'αβγδεζηθικλμνξοπρςστυφχψω')"
+
+
+ - "𝛻": # 0x1d6fb
+ - spell: "translate('.', '𝜵', '∇')"
+
+ - "𝜕𝜖𝜗𝜘𝜙𝜚𝜛": # 0x1d715 - 0x1d71b
+ # - tc: "italic"
+ - spell: "translate('.', '𝜕𝜖𝜗𝜘𝜙𝜚𝜛', '∂εθκφρπ')"
+
+ - "𝜜-𝜴": # 0x1d71c - 0x1d734
+ # - tc: "bold italic"
+ - tc: 'BIC⠁'
+ - spell: "translate('.', '𝜜𝜝𝜞𝜟𝜠𝜡𝜢𝜣𝜤𝜥𝜦𝜧𝜨𝜩𝜪𝜫𝜬𝜭𝜮𝜯𝜰𝜱𝜲𝜳𝜴', 'αβγδεζηθικλμνξοπρςστυφχψω')"
+
+ - "𝜶-𝝎": # 0x1d736 - 0x1d74e
+ - tc: "BI"
+ - spell: "translate('.', '𝜶𝜷𝜸𝜹𝜺𝜻𝜼𝜽𝜾𝜿𝝀𝝁𝝂𝝃𝝄𝝅𝝆𝝇𝝈𝝉𝝊𝝋𝝌𝝍𝝎', 'αβγδεζηθικλμνξοπρςστυφχψω')"
+
+ - "𝝏𝝐𝝑𝝒𝝓𝝔𝝕": # 0x1d74f - 0x1d755
+ - tc: "BI"
+ - spell: "translate('.', '𝝏𝝐𝝑𝝒𝝓𝝔𝝕', '∂εκθφρπ')"
+
+ - "𝜵": # 0x1d735
+ - tc: "BI"
+ - spell: "translate('.', '𝜵', '∇')"
+
+ - "𝝖-𝝮": # 0x1d756 - 0x1d76e
+ - tc: "BSC"
+ - spell: "translate('.', '𝝖𝝗𝝘𝝙𝝚𝝛𝝜𝝝𝝞𝝟𝝠𝝡𝝢𝝣𝝤𝝥𝝦𝝧𝝨𝝩𝝪𝝫𝝬𝝭𝝮', 'αβγδεζηθικλμνξοπρςστυφχψω')"
+
+ - "𝝰-𝞈": # 0x1d770 - 0x1d788
+ - tc: "BS"
+ - spell: "translate('.', '𝝰𝝱𝝲𝝳𝝴𝝵𝝶𝝷𝝸𝝹𝝺𝝻𝝼𝝽𝝾𝝿𝞀𝞁𝞂𝞃𝞄𝞅𝞆𝞇𝞈', 'αβγδεζηθικλμνξοπρςστυφχψω')"
+
+ - "𝞉𝞊𝞋𝞌𝞍𝞎𝞏": # 0x1d789 - 0x1d78f
+ - tc: "B"
+ - spell: "translate('.', '𝞉𝞊𝞋𝞌𝞍𝞎𝞏', '∂εθκφρπ')"
+
+ - "𝝯": # 0x1d76f
+ - tc: "B"
+ - spell: "translate('.', '𝜵', '∇')"
+
+ - "𝞐-𝞨": # 0x1d790 - 0x1d7a8
+ # - tc: "bold italic"
+ - test:
+ if: "$IgnoreBold"
+ then: [tc: I]
+ else: [tc: "BI"]
+ - spell: "translate('.', '𝞐𝞑𝞒𝞓𝞔𝞕𝞖𝞗𝞘𝞙𝞚𝞛𝞜𝞝𝞞𝞟𝞠𝞡𝞢𝞣𝞤𝞥𝞦𝞧𝞨', 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ')"
+
+ - "𝞪-𝟂": # 0x1d7aa - 0x1d7c2
+ # - tc: "bold italic"
+ - test:
+ if: "$IgnoreBold"
+ then: [tc: I]
+ else: [tc: "BI"]
+ - spell: "translate('.', '𝞪𝞫𝞬𝞭𝞮𝞯𝞰𝞱𝞲𝞳𝞴𝞵𝞶𝞷𝞸𝞹𝞺𝞻𝞼𝞽𝞾𝞿𝟀𝟁𝟂', 'αβγδεζηθικλμνξοπρςστυφχψω')"
+
+ - "𝟃𝟄𝟅𝟆𝟇𝟈𝟉": # 0x1d7c3 - 0x1d7c9
+ - tc: "B"
+ - spell: "translate('.', '𝟃𝟄𝟅𝟆𝟇𝟈𝟉', '∂εθκφρπ')"
+
+ - "𝞩": [tc: "B"] # 0x1d7a9
+
+ - "𝟎-𝟗": # 0x1d7ce - 0x1d7d7
+ - tc: "B"
+ - spell: "translate('.', '𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗', '0123456789')"
+
+ - "𝟬-𝟵": # 0x1D7EC - 0x1D7F5
+ - tc: "BS"
+ - spell: "translate('.', '𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵', '0123456789')"
+
+ - "𝟢-𝟫": # 0x1d7e2 - 0x1d7eb
+ - tc: "S"
+ - spell: "translate('.', '𝟶𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿', '0123456789')"
+
+ - "𝟶-𝟿": # 0x1d7f6 - 0x1d7ff
+ - spell: "translate('.', '𝟶𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿', '0123456789')"
+
diff --git a/Rules/Braille/Russian/unicode.yaml b/Rules/Braille/Russian/unicode.yaml
new file mode 100644
index 00000000..1f2c733c
--- /dev/null
+++ b/Rules/Braille/Russian/unicode.yaml
@@ -0,0 +1,289 @@
+---
+# Russian mathematical braille characters
+- ".": [t: "⠠⠲"]
+- ",": [t: "⠠⠂"]
+- "!": [t: "⠠⠖"]
+- "?": [t: "⠠⠢"]
+- ":": [t: "⠠⠒"]
+- ";": [t: "⠠⠆"]
+- "{": [t: "⠠⠪"]
+- "}": [t: "⠠⠕"]
+- "+": [t: "⠀⠖"]
+- "-": [t: "⠀⠤"]
+- "=": [t: "⠀⠶"]
+- ">": [t: "⠀⠕⠀"]
+- "<": [t: "⠀⠪⠀"]
+- "%": [t: "⠼⠴"]
+- "±": [t: "⠀⠖⠤"]
+- ";": [t: "⠠⠢"]
+- "Ϳ": [t: "⠰⠊"]
+- "·": [t: "⠰"]
+- "α": [t: "⠰⠁"]
+- "β": [t: "⠰⠃"]
+- "γ": [t: "⠰⠛"]
+- "δ": [t: "⠰⠙"]
+- "ε": [t: "⠰⠑"]
+- "ζ": [t: "⠰⠵"]
+- "η": [t: "⠰⠚"]
+- "θ": [t: "⠰⠓"]
+- "ι": [t: "⠰⠊"]
+- "κ": [t: "⠰⠅"]
+- "λ": [t: "⠰⠇"]
+- "μ": [t: "⠰⠍"]
+- "ν": [t: "⠰⠝"]
+- "ξ": [t: "⠰⠭"]
+- "ο": [t: "⠰⠕"]
+- "π": [t: "⠰⠏"]
+- "ρ": [t: "⠰⠗"]
+- "σ": [t: "⠰⠎"]
+- "τ": [t: "⠰⠞"]
+- "υ": [t: "⠰⠥"]
+- "φ": [t: "⠰⠋"]
+- "χ": [t: "⠰⠉"]
+- "ψ": [t: "⠰⠽"]
+- "ω": [t: "⠰⠺"]
+- "Α": [t: "⠸⠁"]
+- "Β": [t: "⠸⠃"]
+- "Γ": [t: "⠸⠛"]
+- "Δ": [t: "⠸⠙"]
+- "Ε": [t: "⠸⠑"]
+- "Ζ": [t: "⠸⠵"]
+- "Η": [t: "⠸⠚"]
+- "Θ": [t: "⠸⠓"]
+- "Ι": [t: "⠸⠊"]
+- "Κ": [t: "⠸⠅"]
+- "Λ": [t: "⠸⠇"]
+- "Μ": [t: "⠸⠍"]
+- "Ν": [t: "⠸⠝"]
+- "Ξ": [t: "⠸⠭"]
+- "Ο": [t: "⠸⠕"]
+- "Π": [t: "⠸⠏"]
+- "Ρ": [t: "⠸⠗"]
+- "Σ": [t: "⠸⠎"]
+- "Τ": [t: "⠸⠞"]
+- "Υ": [t: "⠸⠥"]
+- "Φ": [t: "⠸⠋"]
+- "Χ": [t: "⠸⠉"]
+- "Ψ": [t: "⠸⠽"]
+- "Ω": [t: "⠸⠺"]
+- "ϐ": [t: "⠰⠃"]
+- "ϑ": [t: "⠰⠓"]
+- "ϕ": [t: "⠰⠋"]
+- "ϖ": [t: "⠰⠏"]
+- "ϱ": [t: "⠰⠗"]
+- "′": [t: "⠔"]
+- "″": [t: "⠔⠔"]
+- "‴": [t: "⠔⠔⠔"]
+- "∀": [t: "⠫⠄"]
+- "∂": [t: "⠹"]
+- "∃": [t: "⠫⠢"]
+- "∅": [t: "⠈⠴"]
+- "∇": [t: "⠫⠴"]
+- "∈": [t: "⠀⠐⠪⠀"]
+- "∉": [t: "⠘⠪"]
+- "∋": [t: "⠀⠕⠂⠀"]
+- "∌": [t: "⠈⠕⠂⠄"]
+- "∎": [t: "⠀⠸⠇⠀"]
+- "∏": [t: "⠸⠏"]
+- "∑": [t: "⠸⠎"]
+- "−": [t: "⠀⠤"]
+- "∓": [t: "⠀⠤⠖"]
+- "∔": [t: "⠈⠖"]
+- "∕": [t: "⠠⠌"]
+- "∖": [t: "⠀⠰⠤"]
+- "∗": [t: "⠔"]
+- "∘": [t: "⠴"]
+- "√": [t: "⠩⠱"]
+- "∛": [t: "⠩⠒⠱"]
+- "∜": [t: "⠩⠲⠱"]
+- "∞": [t: "⠻"]
+- "∠": [t: "⠸⠪"]
+- "∣": [t: "⠸"]
+- "∤": [t: "⠀⠼⠀"]
+- "∥": [t: "⠸⠸"]
+- "∧": [t: "⠀⠰⠢"]
+- "∨": [t: "⠀⠰⠔"]
+- "∩": [t: "⠀⠰⠲"]
+- "∪": [t: "⠀⠰⠴"]
+- "∫": [t: "⠮"]
+- "∬": [t: "⠮⠮"]
+- "∭": [t: "⠮⠮⠮"]
+- "∮": [t: "⠮⠴"]
+- "∯": [t: "⠮⠮⠴"]
+- "∴": [t: "⠀⠠⠡⠀"]
+- "∵": [t: "⠀⠈⠌⠀"]
+- "∶": [t: "⠀⠳"]
+- "∸": [t: "⠈⠤"]
+- "∼": [t: "⠀⠢"]
+- "≅": [t: "⠀⠢"]
+- "≈": [t: "⠀⠢⠢"]
+- "≐": [t: "⠀⠒⠕"]
+- "≔": [t: "⠀⠶⠒"]
+- "≕": [t: "⠀⠶⠒"]
+- "≖": [t: "⠀⠶⠴"]
+- "≗": [t: "⠀⠶⠴"]
+- "≛": [t: "⠀⠶⠆"]
+- "≟": [t: "⠶⠢"]
+- "≠": [t: "⠀⠾"]
+- "≡": [t: "⠀⠰⠶"]
+- "≢": [t: "⠀⠰⠾"]
+- "≤": [t: "⠀⠪⠶"]
+- "≥": [t: "⠀⠕⠶"]
+- "≪": [t: "⠀⠪⠪⠀"]
+- "≫": [t: "⠀⠕⠕⠀"]
+- "≺": [t: "⠀⠒⠪⠀"]
+- "≻": [t: "⠀⠕⠒⠀"]
+- "≼": [t: "⠀⠒⠪⠶"]
+- "≽": [t: "⠀⠕⠒⠶"]
+- "⊂": [t: "⠀⠯⠀"]
+- "⊃": [t: "⠀⠹⠀"]
+- "⊄": [t: "⠈⠯"]
+- "⊅": [t: "⠈⠹"]
+- "⊆": [t: "⠀⠯⠶"]
+- "⊇": [t: "⠀⠹⠶"]
+- "⊕": [t: "⠀⠰⠖"]
+- "⊥": [t: "⠼⠄"]
+- "⊨": [t: "⠼⠎"]
+- "⊼": [t: "⠰⠌"]
+- "⊽": [t: "⠘⠡"]
+- "⋅": [t: "⠄"]
+- "⋆": [t: "⠔"]
+- "⋮": [t: "⠠⠲⠲⠲"]
+- "⋯": [t: "⠠⠲⠲⠲"]
+- "⋰": [t: "⠠⠲⠲⠲"]
+- "⋱": [t: "⠠⠲⠲⠲"]
+- "△": [t: "⠸⠙"]
+- "◻": [t: "⠶"]
+- "♮": [t: "⠻⠨⠝⠻"]
+
+# Latin letters and digits used for mathematical identifiers.
+- "a": [t: "⠁"]
+- "A": [t: "C⠁"]
+- "b": [t: "⠃"]
+- "B": [t: "C⠃"]
+- "c": [t: "⠉"]
+- "C": [t: "C⠉"]
+- "d": [t: "⠙"]
+- "D": [t: "C⠙"]
+- "e": [t: "⠑"]
+- "E": [t: "C⠑"]
+- "f": [t: "⠋"]
+- "F": [t: "C⠋"]
+- "g": [t: "⠛"]
+- "G": [t: "C⠛"]
+- "h": [t: "⠓"]
+- "H": [t: "C⠓"]
+- "i": [t: "⠊"]
+- "I": [t: "C⠊"]
+- "j": [t: "⠚"]
+- "J": [t: "C⠚"]
+- "k": [t: "⠅"]
+- "K": [t: "C⠅"]
+- "l": [t: "⠇"]
+- "L": [t: "C⠇"]
+- "m": [t: "⠍"]
+- "M": [t: "C⠍"]
+- "n": [t: "⠝"]
+- "N": [t: "C⠝"]
+- "o": [t: "⠕"]
+- "O": [t: "C⠕"]
+- "p": [t: "⠏"]
+- "P": [t: "C⠏"]
+- "q": [t: "⠟"]
+- "Q": [t: "C⠟"]
+- "r": [t: "⠗"]
+- "R": [t: "C⠗"]
+- "s": [t: "⠎"]
+- "S": [t: "C⠎"]
+- "t": [t: "⠞"]
+- "T": [t: "C⠞"]
+- "u": [t: "⠥"]
+- "U": [t: "C⠥"]
+- "v": [t: "⠧"]
+- "V": [t: "C⠧"]
+- "w": [t: "⠺"]
+- "W": [t: "C⠺"]
+- "x": [t: "⠭"]
+- "X": [t: "C⠭"]
+- "y": [t: "⠽"]
+- "Y": [t: "C⠽"]
+- "z": [t: "⠵"]
+- "Z": [t: "C⠵"]
+- "0": [t: "N⠚"]
+- "1": [t: "N⠁"]
+- "2": [t: "N⠃"]
+- "3": [t: "N⠉"]
+- "4": [t: "N⠙"]
+- "5": [t: "N⠑"]
+- "6": [t: "N⠋"]
+- "7": [t: "N⠛"]
+- "8": [t: "N⠓"]
+- "9": [t: "N⠊"]
+
+# Russian literary braille letters for mtext and Cyrillic identifiers.
+- "а": [t: "⠁"]
+- "А": [t: "C⠁"]
+- "б": [t: "⠃"]
+- "Б": [t: "C⠃"]
+- "в": [t: "⠺"]
+- "В": [t: "C⠺"]
+- "г": [t: "⠛"]
+- "Г": [t: "C⠛"]
+- "д": [t: "⠙"]
+- "Д": [t: "C⠙"]
+- "е": [t: "⠑"]
+- "Е": [t: "C⠑"]
+- "ё": [t: "⠡"]
+- "Ё": [t: "C⠡"]
+- "ж": [t: "⠚"]
+- "Ж": [t: "C⠚"]
+- "з": [t: "⠵"]
+- "З": [t: "C⠵"]
+- "и": [t: "⠊"]
+- "И": [t: "C⠊"]
+- "й": [t: "⠯"]
+- "Й": [t: "C⠯"]
+- "к": [t: "⠅"]
+- "К": [t: "C⠅"]
+- "л": [t: "⠇"]
+- "Л": [t: "C⠇"]
+- "м": [t: "⠍"]
+- "М": [t: "C⠍"]
+- "н": [t: "⠝"]
+- "Н": [t: "C⠝"]
+- "о": [t: "⠕"]
+- "О": [t: "C⠕"]
+- "п": [t: "⠏"]
+- "П": [t: "C⠏"]
+- "р": [t: "⠗"]
+- "Р": [t: "C⠗"]
+- "с": [t: "⠎"]
+- "С": [t: "C⠎"]
+- "т": [t: "⠞"]
+- "Т": [t: "C⠞"]
+- "у": [t: "⠥"]
+- "У": [t: "C⠥"]
+- "ф": [t: "⠋"]
+- "Ф": [t: "C⠋"]
+- "х": [t: "⠓"]
+- "Х": [t: "C⠓"]
+- "ц": [t: "⠉"]
+- "Ц": [t: "C⠉"]
+- "ч": [t: "⠟"]
+- "Ч": [t: "C⠟"]
+- "ш": [t: "⠱"]
+- "Ш": [t: "C⠱"]
+- "щ": [t: "⠭"]
+- "Щ": [t: "C⠭"]
+- "ъ": [t: "⠷"]
+- "Ъ": [t: "C⠷"]
+- "ы": [t: "⠮"]
+- "Ы": [t: "C⠮"]
+- "ь": [t: "⠾"]
+- "Ь": [t: "C⠾"]
+- "э": [t: "⠪"]
+- "Э": [t: "C⠪"]
+- "ю": [t: "⠳"]
+- "Ю": [t: "C⠳"]
+- "я": [t: "⠫"]
+- "Я": [t: "C⠫"]
diff --git a/src/braille.rs b/src/braille.rs
index b944578f..6b259776 100644
--- a/src/braille.rs
+++ b/src/braille.rs
@@ -50,6 +50,7 @@ pub fn braille_mathml(mathml: Element, nav_node_id: &str) -> Result<(String, usi
"CMU" => cmu_cleanup(pref_manager, braille_string),
"Finnish" => finnish_cleanup(pref_manager, braille_string),
"Swedish" => swedish_cleanup(pref_manager, braille_string),
+ "Russian" => russian_cleanup(pref_manager, braille_string),
"LaTeX" => LaTeX_cleanup(pref_manager, braille_string),
"ASCIIMath" => ASCIIMath_cleanup(pref_manager, braille_string),
"ASCIIMath-fi" => ASCIIMath_cleanup(pref_manager, braille_string),
@@ -2167,6 +2168,38 @@ fn swedish_cleanup(pref_manager: Ref