---
layout: default
lang: ru
ref: callers
title: Интеграция MathCAT
---
# MathCAT: средство обеспечения доступности математики
<img src="../logo.png" style="position: relative; top: 16px; z-index: -1;">

## Информация для разработчиков ассистивных технологий и пользователей библиотеки

При использовании 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<String>

/// Возвращает текст для озвучивания ранее заданного MathML.
/// При озвучивании учитываются настройки ассистивной технологии и пользователя.
pub fn get_spoken_text() -> Result<String>

/// Возвращает текст для озвучивания обзора ранее заданного MathML.
/// При озвучивании учитываются настройки ассистивной технологии и пользователя.
/// Примечание: сейчас реализация минимальна, и использовать её не следует.
pub fn get_overview_text() -> Result<String>

/// Возвращает значение указанной настройки.
/// Если `name` не является известной настройкой, возвращается None.
pub fn get_preference(name: impl AsRef<str>) -> Result<String>

/// Задаёт настройку MathCAT. Имя настройки должно быть известным MathCAT именем.
/// В зависимости от настройки значением должна быть строка или число.
/// Список известных пользовательских настроек приведён в пользовательской документации MathCAT.
/// Ниже перечислены распространённые настройки, которые задаются программами и недоступны пользователю:
/// * TTS -- SSML, SAPI5, None
/// * Pitch -- нормализовано относительно '1.0'
/// * Rate -- слов в минуту; должно соответствовать текущей скорости речи.
///       Отдельная пользовательская настройка "MathRate" задаёт относительное изменение этой скорости в процентах.
/// * Volume -- по умолчанию 100
/// * Voice -- используемый голос (не реализовано)
/// * Gender -- выбор любого голоса указанного пола (не реализовано)
/// * Bookmark -- задайте `true`, если возвращаемая речь должна содержать `mark`/`bookmark`
///               для синхронного выделения
/// * CheckRuleFiles -- проверяет, изменились ли файлы правил после предыдущего вызова. Возможные значения:
///                    "All", "Prefs" (по умолчанию; только системный и пользовательский файлы prefs.yaml)
///                    и "None". Переход от "All" к "None" ускоряет работу примерно на 40 %,
///                    а переход от "Prefs" к "None" -- примерно на 10 %.
///
/// Эти настройки управляют озвучиванием и изменением высоты тона для прописных букв:
/// * CapitalLetters_UseWord -- произносить "cap" или подходящий для языка аналог [по умолчанию: true]
/// * CapitalLetters_Pitch -- изменять высоту тона вокруг прописной буквы
///                          (нормализовано относительно '1.0'; значение '1.0' по умолчанию ничего не меняет)
/// * CapitalLetters_Beep -- создаёт фиктивный тег SSML audio с audio src='beep.mp4';
///                         в NVDA он служит признаком для воспроизведения сигнала
///
/// * IntentErrorRecovery -- определяет поведение при недопустимых значениях `intent` в MathML.
///                          Возможные значения: "Error" и "IgnoreIntent" (по умолчанию)
///
/// Важно: имена и значения настроек чувствительны к регистру.
///
/// Эту функцию можно вызывать несколько раз, чтобы задать разные значения.
/// Значения сохраняются и действуют после вызовов [`set_mathml`].
/// Значение можно перезаписать, повторно вызвав функцию с другим аргументом.
///
/// FIXME: Некоторые настройки относятся и к API, и к пользователю. Для переопределения следует
///        использовать что-то наподобие '!name'. Пока не реализовано.
pub fn set_preference(name: impl AsRef<str>, value: impl AsRef<str>) -> Result<()>

/// Перемещает текущий узел в соответствии с кодом клавиши и клавишами-модификаторами
/// или в некоторых случаях сообщает значение.
/// `key` -- [код клавиши](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#constants_for_keycode_value)
/// для клавиши (в JavaScript -- `ev.key_code`).
/// Возвращает текст для озвучивания нового текущего узла.
pub fn do_navigate_keypress(key: usize, shift_key: bool, control_key: bool, alt_key: bool, meta_key: bool) -> Result<String>

/// Перемещает текущий узел в соответствии с командой навигации.
/// Это более высокоуровневый интерфейс, чем `do_navigate_keypress`, предназначенный для приложений,
/// которые самостоятельно интерпретируют нажатия клавиш.
/// Допустимые команды:
/// * Стандартные команды перемещения:
/// `MovePrevious`, `MoveNext`, `MoveStart`, `MoveEnd`, `MoveLineStart`, `MoveLineEnd`
/// * Перемещение в таблице или элементарных математических выражениях:
/// `MoveCellPrevious`, `MoveCellNext`, `MoveCellUp`, `MoveCellDown`, `MoveColumnStart`, `MoveColumnEnd`
/// * Переход к дочерним узлам или выход к родительским:
/// `ZoomIn`, `ZoomOut`, `ZoomOutAll`, `ZoomInAll`
/// * Отмена последней команды перемещения:
/// `MoveLastLocation`
/// * Команды чтения (стандартное озвучивание):
/// `ReadPrevious`, `ReadNext`, `ReadCurrent`, `ReadCellCurrent`, `ReadStart`, `ReadEnd`, `ReadLineStart`, `ReadLineEnd`
/// * Команды описания (обзор):
/// `DescribePrevious`, `DescribeNext`, `DescribeCurrent`
/// * Информация о позиции:
/// `WhereAmI`, `WhereAmIAll`
/// * Изменение режима навигации с циклическим переходом вверх или вниз:
/// `ToggleZoomLockUp`, `ToggleZoomLockDown`
/// * Озвучивание текущего режима навигации:
/// `ToggleSpeakMode`
///
/// Доступны 10 меток позиции, которые можно устанавливать, читать, описывать и использовать для перехода.
/// * Установка:
/// `SetPlacemarker0`, `SetPlacemarker1`, `SetPlacemarker2`, `SetPlacemarker3`, `SetPlacemarker4`, `SetPlacemarker5`, `SetPlacemarker6`, `SetPlacemarker7`, `SetPlacemarker8`, `SetPlacemarker9`
/// * Чтение:
/// `Read0`, `Read1`, `Read2`, `Read3`, `Read4`, `Read5`, `Read6`, `Read7`, `Read8`, `Read9`
/// * Описание:
/// `Describe0`, `Describe1`, `Describe2`, `Describe3`, `Describe4`, `Describe5`, `Describe6`, `Describe7`, `Describe8`, `Describe9`
/// * Переход:
/// `MoveTo0`, `MoveTo1`, `MoveTo2`, `MoveTo3`, `MoveTo4`, `MoveTo5`, `MoveTo6`, `MoveTo7`, `MoveTo8`, `MoveTo9`
///
/// После завершения навигации вызовите функцию с командой `Exit`.
pub fn do_navigate_command(command: impl AsRef<str>) -> Result<String>

/// Возвращает MathML, связанный с текущим узлом навигации.
/// Результат содержит `id` узла и смещение относительно этого узла, начиная с нуля (пока не реализовано).
/// Смещение требуется для токенов из нескольких символов.
pub fn get_navigation_mathml() -> Result<(String, usize)> 

/// Возвращает `id` и смещение, начиная с нуля, связанные с текущим узлом навигации.
/// `offset` пока не реализовано.
/// Смещение требуется для токенов из нескольких символов.
pub fn get_navigation_mathml_id() -> Result<(String, usize)>


/// Преобразует ошибку, возвращённую set_mathml и другими функциями, в полезную для отображения строку.
pub fn errors_to_string(e:&Error) -> String 

```

## Для пользователей Python
Можно создать собственный интерфейс Python или воспользоваться интерфейсом из связанного проекта [MathCATForPython](https://github.com/NSoiffer/MathCATForPython). В нём используется пакет Rust pyo3.

Интерфейс Python в целом аналогичен интерфейсу Rust. В Python используется CamelCase, а не принятый в Rust snake_case. Например, функция `set_rules_dir` в интерфейсе Python называется `SetRulesDir`. Вызов функции следует оборачивать в конструкцию:
```
try:
    ...
except Exception as e:
    ...  # зарегистрировать ошибку 'e' в журнале
```


## Для веб-разработчиков
Я собрал версию WebAssembly. В ней есть несколько компромиссов, а в процессе сборки требуется вручную вносить некоторые изменения. Этот процесс необходимо автоматизировать. Версия находится в проекте [MathCatDemo](https://github.com/NSoiffer/MathCATDemo). Проект собирает веб-страницу для демонстрационных целей, поэтому это не чистая сборка для веба. Тем не менее он показывает, как можно реализовать такую сборку.

## Для пользователей C/C++
Интерфейс C/C++ находится в связанном проекте [MathCatForC](https://github.com/NSoiffer/MathCATForC). У Rust и C разные менеджеры памяти, поэтому интерфейс несколько неудобен: память необходимо освобождать. Эту особенность можно скрыть, обернув вызовы небольшой функцией, как это сделано для `SetMathCatPreference` в [примере кода](https://github.com/NSoiffer/MathCATForC/blob/main/c-example/test.cpp). В остальном пользоваться интерфейсом просто. Если вы знаете более удачный способ решить проблему с памятью, сообщите мне или отправьте pull request. Для меня как программиста Rust это новая область.
