Meridian Math — редактор формул
Документ приложения по соглашениям раздела 8 библии (
docs/00-foundation.md). Версия 0.1 · 2026-10-03 · фаза 4 · редакция: Free и выше (встраивается во все приложения пакета). Цвет приложения--app-math#495057, значок — ∑, формат документа.meq(urn:meridian:office:math:1, MathML + своя разметка), код ядра —kernel/crates/meridian-math/, оболочка —shell/src/math/.
1. Назначение и аналоги
1.1 Назначение
Meridian Math — единый движок и редактор математической записи для
всего пакета. Он существует в двух видах, использующих один код
(meridian-math: AST, парсеры, раскладка, рендер,
конвертеры):
| Вид | Где | Что делает |
|---|---|---|
| Встраиваемый компонент | Write (формулы в тексте: встроенные и выключные с нумерацией и ссылками), Sheets (объект-формула на листе, подписи осей диаграмм), Slides (формулы на слайдах, с анимацией по частям — P2), Draw (формула как объект схемы), Notes (формулы в заметках, рукописный ввод — P2), Forms (формулы в вопросах и вариантах ответов), Mail (в письме: HTML с MathML + PNG-резерв), Publisher (фаза 7) | контекстная вкладка ribbon «Формула», редактирование на месте в документе, те же палитры и линейный ввод, экспорт в форматы документа-хозяина (OMML в docx, MathML в odt, SVG/PNG в html) |
| Отдельное приложение | из Hub, по ассоциации
.meq/.mml/.tex (формульные
файлы) |
окно редактора с большим холстом, библиотекой формул, экспортом во все форматы, пакетной конверсией |
Назначение: набрать формулу любой сложности быстрее, чем в Word, с качеством печати как в LaTeX, и чтобы она без потерь ходила между docx/odt/HTML/PDF и была доступна (озвучивание, PDF/UA).
1.2 Аналоги
| Продукт | Сильные стороны | Слабые стороны |
|---|---|---|
| Word Equation Editor (OMML, UnicodeMath) | быстрый линейный ввод, интеграция с Word, Cambria Math | нет LaTeX полностью (с 2017 — частично), раскладка хуже TeX, экспорт MathML/LaTeX ограничен |
| LibreOffice Math (StarMath) | свой текстовый синтаксис, визуальный предпросмотр, MathML | устаревший синтаксис, слабая раскладка (без OpenType MATH до 7.x), нет линейного ввода «как в Word» |
| MathType | палитры, совместимость с Word/Pages, рукописный ввод | платный, Windows/macOS, закрытый формат MTEF |
| LaTeX / amsmath | эталон качества, расширяемость | не визуальный, долгая компиляция, порог входа |
| KaTeX / MathJax | быстрый рендер в вебе, MathML | только отображение |
| Apple Pages/Keynote (LaTeX/MathML) | ввод LaTeX/MathML | только macOS, нет визуального редактора |
| LyX | визуальный LaTeX | отдельная экосистема |
1.3 Чем лучше
- Три способа ввода одновременно: палитры (визуально), UnicodeMath (как в Word) и LaTeX (подмножество amsmath) — в одном поле, с мгновенным построением и обратной сериализацией в любой синтаксис.
- Раскладка по TeX (TeXbook, приложение G) с таблицей OpenType MATH — качество LaTeX, шрифты STIX Two Math и Latin Modern Math в комплекте, Cambria Math — если установлен.
- Одна формула — одинаковый вид везде: в Write, Slides, PDF, печати и SVG (единый рендер Meridian Render).
- Совместимость без потерь: OMML (docx/pptx/xlsx),
MathML (odt, HTML, буфер обмена), StarMath (LibreOffice), LaTeX;
round-trip docx →
.mwx→ docx структурно эквивалентен. - Доступность: озвучивание по правилам MathSpeak/ClearSpeak, MathML в PDF/UA, навигация по дереву с клавиатуры.
- Нумерация и ссылки в Write как поля (по главам, перекрёстные ссылки, автообновление).
- Скрипты: формулы как данные (AST) — генерация, замена, экспорт пакетами.
2. Функции
Приоритеты: P0 — без этого нельзя выпустить 1.0; P1 — в 1.x; P2 — позже.
2.1 Визуальный ввод с палитрами
| Палитра | Содержимое | Приоритет |
|---|---|---|
| Дроби | обычная, наклонная (bevelled), маленькая (текстовый стиль), без черты (биномиальные/стековые), ⅟ (линейная) | P0 |
| Радикалы | √, ∛, ∜, корень n-й степени с полем степени | P0 |
| Индексы | нижний, верхний, оба, левые (пред-индексы,
mmultiscripts), индексы под/над
(munder/mover) |
P0 |
| Интегралы | ∫ ∬ ∭ ∮ ∯ ∰ с пределами (под/над или сбоку), без пределов;
d x с правильным интервалом (\,) |
P0 |
| Большие операторы | ∑ ∏ ∐ ⋃ ⋂ ⋁ ⋀ ⨁ ⨂ ⨀ ⨆ с пределами под/над или как индексы | P0 |
| Матрицы | 1×1…10×10 (сетка-выбор + числовой диалог MT-14), пустые/с точками (⋯ ⋮ ⋱), определители, со скобками любого типа, выравнивание столбцов, разделители (линии между строками/столбцами) | P0 |
| Скобки | ( ) [ ] { } ⟨ ⟩ ⌊ ⌋ ⌈ ⌉ | ‖ — пары и одиночные,
растягиваемые/фиксированного размера, невидимые; системы уравнений
(«случаи» { с условиями); скобка с разделителем
(`\middle |
`) |
| Акценты | вектор →, крышка ^, тильда ~, черта ‾, точка ˙, две точки ¨, три
точки, дуга, стрелка над/под (двусторонние), подчёркивание,
надчёркивание, фигурные скобки над/под с подписью
(\overbrace/\underbrace), широкие
(растягиваемые) варианты |
P0 |
| Операторы | бинарные (+ − × ÷ ± ∓ ⋅ ∘ ∗ ⊕ ⊗ …), отношения (= ≠ < > ≤ ≥ ≈ ≡
∝ ≃ ≅ ∼ ⊂ ⊆ ∈ ∉ …), стрелки (→ ← ↔︎ ⇒ ⇐ ⇔ ↦ ↑ ↓ ⟶ …, растягиваемые с
текстом над/под), логические (∧ ∨ ¬ ∀ ∃ ∄ ⊢ ⊨), теоретико-множественные
(∪ ∩ ∖ ∅ ⊂ ⊃ ℘), отрицания (\not) |
P0 |
| Греческие | строчные, прописные, вариантные (ϵ ε ϑ θ ϖ π ϱ ρ ς σ ϕ φ), прямые/курсивные | P0 |
| Функции | sin cos tan cot sec csc, sinh…, arcsin…, log ln lg exp, lim limsup
liminf, max min sup inf, det dim ker deg gcd, arg, Pr, mod/pmod;
пользовательские имена функций (\operatorname) — прямым
шрифтом с правильными интервалами |
P0 |
| Пределы и дифференциалы | lim с подписью, d/dx, ∂/∂x, ∇, Δ, dx, производные Лагранжа (′ ″ ‴), Ньютона (точки) | P0 |
| Символы | ℝ ℕ ℤ ℚ ℂ ℍ, ∞ ∂ ∇ ℏ ℓ ℘ ℜ ℑ ℵ, ° ‰ ′ ″, …, ⋯ ⋮ ⋱, □ ■ △ ∠ ⊥ ∥, ± ∓ √ ∛ ∜, ✓ | P0 |
| Шрифтовые стили | mathvariant: normal, bold, italic, bold-italic,
double-struck (𝔸), fraktur (𝔄), script (𝒜), sans-serif, monospace,
bold-fraktur, bold-script, sans-serif-bold/italic; текст
(\text) в шрифте документа |
P0 |
| Оформление | цвет, цвет фона, рамка (\boxed), подчёркивание,
зачёркивание (\cancel, диагональ), пробелы (тонкий,
средний, толстый, quad, qquad, отрицательный), фантомы
(\phantom), выравнивание (\stackrel,
\overset, \underset) |
P0 (цвет/рамка/пробелы), P1 (cancel, фантомы) |
| Многострочные | выравнивание по =/& (aligned/align),
несколько уравнений (gather), разрыв длинной формулы (split/multline),
нумерация строк |
P0 (aligned, gather, cases), P1 (multline, split, подномера) |
| Химия | \ce{} (mhchem-подобное подмножество: формулы, стрелки
реакций, состояния) |
P2 |
| Единицы | \si{}/\SI{} (подмножество siunitx: числа,
единицы прямым шрифтом, интервалы) |
P1 |
| Автозамена | \alpha → α, <= → ≤, ->
→ →, != → ≠, ... → …, oo → ∞
(настраиваемый словарь ~1200 записей из Word и LaTeX) при вводе в
визуальном режиме; автодополнение команд (\fr →
\frac, \sq…) с всплывающим списком |
P0 |
| Автоскобки и слоты | ввод ( создаёт растягиваемую пару с местозаполнителем;
пустые слоты показываются пунктирными квадратами и обходятся
Tab |
P0 |
| Шаблоны формул | галерея готовых: квадратное уравнение, бином Ньютона, ряд Тейлора, Фурье, матрица поворота, формулы физики/статистики (по категориям, локализованные), пользовательские («Сохранить как шаблон») | P0 |
2.2 Линейный ввод
| Функция | Описание | Приоритет |
|---|---|---|
| UnicodeMath (как в Word) | синтаксис по UTN #28 (Murray Sargent): a/b,
(a+b)/c, x^2, x_i,
x_i^2, ∫_0^1 f(x) dx,
∑_(i=1)^n i^2, √(x), ∛,
√(n&x), ` |
x |
| LaTeX | подмножество amsmath/amssymb (список в 5.3): \frac,
\dfrac, \tfrac, \binom,
\sqrt[n]{}, ^/_,
\int_0^1, \sum_{i=1}^n,
\lim_{x\to 0}, \left(…\right),
\big…\Bigg, `\begin{pmatrix |
bmatrix |
| AsciiMath | как UnicodeMath, ASCII-только: sum_(i=1)^n,
sqrt(x), root(n)(x),
((a,b),(c,d)) матрицы |
P1 |
| Переключатель синтаксиса | линейное поле показывает формулу в выбранном синтаксисе (UnicodeMath / LaTeX / AsciiMath); редактирование в поле → AST → визуальный; редактирование визуально → поле обновляется (сериализация); сохранение исходного текста неизменённых узлов (стабильный round-trip) | P0 |
| Вставка из буфера | автоопределение: LaTeX (\frac, $…$),
MathML (<math), OMML (<m:oMath),
UnicodeMath, StarMath — диалог при неоднозначности |
P0 |
| Прямое отображение | «Линейный вид» (как в Word «Linear») — вся формула как текст UnicodeMath прямо на холсте | P1 |
2.3 Рукописный ввод (P2)
Панель рукописного ввода (планшет/мышь/сенсор): штрихи → локальная модель распознавания (ONNX-кодировщик- декодер, обучение на CROHME + синтетика; ~20 МБ) → LaTeX → AST → предпросмотр; исправление: выделить штрихи → «это 2, не z», «удалить», «вставить»; как в Word «Ink Equation» — P2.
2.4 Нумерация и ссылки (в Write)
| Функция | Описание | Приоритет |
|---|---|---|
| Выключная формула с номером | абзац стиля «Формула» (таблица без границ: пусто / формула по центру
/ номер справа — или слева по настройке стиля); номер — поле
SEQ Equation с форматом (1),
(2.3) (глава.номер по заголовкам 1 уровня),
(A.1) в приложениях; перенумерация автоматическая;
\tag{*} — ручной номер; \nonumber |
P0 |
| Перекрёстные ссылки | закладка на формулу (\label), поле REF в
тексте («см. формулу (3)»), диалог MT-15, обновление при изменении;
гиперссылка в PDF |
P0 |
| Подномера | (1a), (1b) для строк окружения
subequations |
P1 |
| Экспорт в docx | как в Word: m:oMathPara в таблице 3 колонки (без
границ) с полем SEQ — Word показывает и перенумеровывает; импорт обратно
распознаёт эту структуру и структуру Word 365 (#(1) в
UnicodeMath → m:eqArr с таб-выравниванием) |
P0 |
| Экспорт в odt | text:sequence + draw:frame с
MathML-объектом (как LibreOffice) |
P0 |
2.5 Стили и размеры
| Функция | Описание | Приоритет |
|---|---|---|
| Встроенная / выключная | display (большие операторы, пределы над/под, дроби
полного размера) и inline (textstyle); авто:
отдельный абзац → display |
P0 |
| Размер | базовый размер = размер шрифта абзаца; script =
ScriptPercentScaleDown (обычно 70 %), scriptscript =
ScriptScriptPercentScaleDown (50 %) — из таблицы MATH шрифта; ручной
масштаб формулы (%, pt) |
P0 |
| Шрифты | STIX Two Math (по умолчанию), Latin Modern Math, Fira Math, Noto
Sans Math, Cambria Math (если установлен), Asana Math, XITS Math,
Libertinus Math, Garamond Math, любой с таблицей MATH; текст
(\text) — шрифт абзаца; подстановка глифов: цепочка
резервов (STIX Two → Noto Sans Math → системный) |
P0 |
| Интервалы | по TeX (атомы Ord/Op/Bin/Rel/Open/Close/Punct/Inner), настраиваемые
множители; «плотный режим» (\!-подобное сжатие) |
P0 |
| Стиль формулы в документе | стиль абзаца «Формула» + стиль символов «Формула (встроенная)» в Write; настройки по умолчанию для документа (шрифт, размеры индексов, положение номера) | P0 |
| Цвет и тема | цвет формулы наследуется от текста; тёмная тема — инверсия автоматически (формула — векторная) | P0 |
2.6 Экспорт
| Формат | Описание | Приоритет |
|---|---|---|
| MathML | Presentation MathML 3 / MathML Core (режим «Core» — только элементы
Core, остальное эмулируется), с semantics/annotation (LaTeX
и UnicodeMath исходник), namespace
http://www.w3.org/1998/Math/MathML; Content MathML —
P2 |
P0 |
| LaTeX | фрагмент
$…$/\[…\]/\begin{equation};
минимальные скобки; \left/\right только для
растягиваемых; стили через \mathbb и т.д. |
P0 |
| OMML | m:oMath/m:oMathPara для docx/pptx/xlsx
(через кодек OOXML Write) |
P0 |
| UnicodeMath / AsciiMath | текст | P0 / P1 |
| SVG | векторный: глифы как <path> (по умолчанию, не
зависит от шрифтов) или <text> с
@font-face (встроенный подмножеством шрифт, WOFF2);
baseline в атрибуте data-baseline, размер в
em/px; цвет currentColor |
P0 |
| PNG | DPI 96–600, фон прозрачный/белый/цвет, масштаб ×1/×2/×3, baseline в
tEXt:baseline и pHYs |
P0 |
| через Write/Render (векторно, шрифты встроены) | P0 | |
| Буфер обмена | одновременно: text/plain (LaTeX или UnicodeMath — по
настройке), MathML (как у Word: формат «MathML» в
CF_UNICODETEXT + application/mathml+xml), OMML
(Native XML Word для вставки в Word как уравнение),
image/png, image/svg+xml |
P0 |
| Озвучивание (текст) | MathSpeak (verbose/brief/superbrief), ClearSpeak, язык (ru/en) — для буфера и a11y | P1 |
2.7 Импорт
| Источник | Описание | Приоритет |
|---|---|---|
| OMML (docx/pptx/xlsx) | все элементы: m:f (type
bar/noBar/skw/lin), m:rad (degHide),
m:sSub, m:sSup, m:sSubSup,
m:sPre, m:nary (chr,
limLoc subSup/undOvr, subHide,
supHide, grow), m:d
(begChr, endChr, sepChr,
grow, shp), m:m (матрица:
mcs, mcJc, plcHide),
m:acc (chr), m:bar
(pos), m:box (opEmu,
noBreak, aln, brk),
m:borderBox (стороны, зачёркивания),
m:groupChr (chr, pos,
vertJc), m:limLow, m:limUpp,
m:func (fName), m:eqArr
(maxDist, objDist, rSp,
baseJc), m:phant (show,
zeroWid, zeroAsc, zeroDesc,
transp), m:r (m:t,
m:rPr: sty p/b/i/bi, scr
roman/script/fraktur/double-struck/sans-serif/monospace,
nor, aln, brk, lit),
m:oMathPara (jc), w:rPr внутри
(цвет, размер, шрифт); документные настройки m:mathPr
(шрифт, brkBin, dispDef, lMargin,
intLim, naryLim, smallFrac,
wrapIndent, interSp,
intraSp) |
P0 |
MathML (odt, html, буфер, .mml) |
Presentation MathML 1–4: все элементы раздела 4.1 + устаревшие
(mfenced → mrow с mo,
semantics, maction → первый ребёнок,
mglyph, mlabeledtr,
maligngroup/malignmark), атрибуты устаревшие
(fontstyle, fontweight, color),
Content MathML (apply, ci, cn, …)
→ Presentation по стандартным правилам (P1); annotation-xml с другими
формами — сохраняется |
P0 |
StarMath (LibreOffice Math, ODF annotation в
math:semantics) |
синтаксис StarMath 5: over, frac,
sqrt, nroot, sum from{} to{},
int, lim, left( right),
langle, lbrace,
matrix{a # b ## c # d}, stack{},
binom, sub/sup/csub/csup/lsub/lsup/rsub/rsup,
bold, ital, size,
font, color, phantom,
nospace, widevec, widehat,
widetilde, overline, underline,
overbrace, underbrace, func,
oper, uoper, boper,
%alpha/%ALPHA/%ialpha греческие,
newline, alignl/alignc/alignr,
"текст", пробелы ~ и `,
{} группировка, none, evaluate
(7.x), nospace; при расхождении с MathML в файле —
приоритет MathML, StarMath — для редактирования исходника |
P0 |
LaTeX (.tex, буфер) |
подмножество 2.2; файл .tex с несколькими формулами
($…$, \[…\], equation,
align) → библиотека/несколько объектов; преамбула
игнорируется, \newcommand — P1 |
P0 |
Equation Editor 3.0 / MathType (MTEF в OLE-объектах старых
.doc/.docx) |
бинарный MTEF v3–5: записи LINE/CHAR/TMPL/PILE/MATRIX/EMBELL/RULER/FONT/SIZE → AST; шаблоны (fractions, radicals, fences, …) по таблице селекторов | P2 |
| Изображение формулы (OCR формул) | через Assist (фаза 7) | P2 |
2.8 Доступность
| Функция | Описание | Приоритет |
|---|---|---|
| Озвучивание | генерация текста по MathSpeak (en, ru-правила — свои) / ClearSpeak;
экранные дикторы получают текст через роль «math» + MathML (UIA
MathML pattern на Windows, AXMathEquation на macOS — P1,
AT-SPI math на Linux) |
P1 |
| Навигация по дереву | курсор по узлам с озвучиванием подвыражения («числитель: x плюс 1»),
Ctrl+стрелки — по узлам, Ctrl+↑/↓ —
родитель/ребёнок |
P0 |
| PDF/UA | формулы как /Formula в дереве структуры с
/ActualText (линейный текст) и /Alt
(озвучивание), MathML как Associated File (PDF 2.0) — через экспорт
Write |
P1 |
| HTML | <math> + alt-текст для
PNG-резерва |
P0 |
| Высокий контраст, масштаб | векторный рендер, цвета темы | P0 |
2.9 Отдельное приложение
Окно (MT-01): холст формулы с большим предпросмотром (масштаб 50–800
%), линейное поле, палитры на ribbon, панель «Элементы» (дерево),
«Библиотека» (сохранённые формулы по папкам с поиском, синхронизация
через Meridian Cloud — фаза 6), пакетный экспорт («все формулы файла →
SVG»), конвертер (вставил LaTeX → получил MathML/OMML), «Копировать как
…», печать (лист с формулой/набором формул), документ .meq
может содержать несколько формул (список-страница с заголовками) — P1;
базово .meq = одна формула — P0.
3. Экраны
Обозначения: MT-NN. Макеты — mockups/math.html. Каркас
окна — по разделу 5.3 библии; ribbon-вкладки отдельного приложения:
«Файл · Главная · Структуры · Символы · Вид · Скрипты»; во встроенном
режиме — одна контекстная вкладка «Формула» в приложении-хозяине с теми
же группами.
MT-01 Отдельное окно
┌────────────────────────────────────────────────────────────────────────────┐
│ ● Квадратное уравнение.meq — Meridian Math [⌘K] [☁] [👤] │
├────────────────────────────────────────────────────────────────────────────┤
│ Файл Главная Структуры Символы Вид Скрипты [⌄] │
│ [Дробь ▾][Индекс ▾][Радикал ▾][Интеграл ▾][Оператор ▾][Скобка ▾][Матрица ▾][Акцент ▾] │
│ [α β γ…][± × ÷ …][≤ ≥ ≠ …][→ ⇒ …][∀ ∃ …][ℝ ℕ …] [Стиль: Выключная ▾][STIX Two Math ▾] │
├────────────┬───────────────────────────────────────────────┬───────────────┤
│ Элементы │ │ Библиотека │
│ ▾ mrow │ −b ± √(b² − 4ac) │ ▸ Физика │
│ mi x │ x = ───────────────── │ ▸ Статистика │
│ mo = │ 2a │ ▾ Мои │
│ ▾ mfrac │ │ Квадратное… │
│ … │ (каретка, слоты, подсветка узла при наведении)│ Ряд Тейлора │
│ ├───────────────────────────────────────────────┤ │
│ │ LaTeX ▾ │ x = \frac{-b \pm \sqrt{b^2-4ac}}{2a} │ ← линейное │
├────────────┴───────────────────────────────────────────────┴───────────────┤
│ Узел: mfrac › числитель · 24 узла · Выключная · 11 pt · [масштаб ───●── 200 %] │
└────────────────────────────────────────────────────────────────────────────┘
| Зона | Содержимое |
|---|---|
| Ribbon «Главная» | буфер (копировать как…), стиль (встроенная/выключная), размер, шрифт, цвет, нумерация, озвучить |
| Ribbon «Структуры» | галереи 2.1: дробь, индекс, радикал, интеграл, большой оператор, скобка, функция, акцент, предел, матрица, оператор-стрелка, шаблоны формул |
| Ribbon «Символы» | группы символов с прокручиваемыми галереями и поиском по имени
(\alpha, «альфа»), недавние |
| Ribbon «Вид» | масштаб, линейное поле (показать/синтаксис), панель «Элементы»,
«Библиотека», показ пустых слотов/невидимых операторов (
⁢, ⁡),
«режим отладки раскладки» (боксы) |
| Холст | формула по центру с «шахматкой»-отсутствием: фон поверхности; каретка (вертикальная черта высотой текущего слота), выделение (заливка цвета приложения 20 %), слоты — пунктирные квадраты, подсветка узла при наведении (рамка), мини-панель над выделением (стиль, цвет, «обернуть в…») |
| Линейное поле | выпадающий выбор синтаксиса, поле с подсветкой синтаксиса и ошибок
(красное подчёркивание), синхронизация с холстом в обе стороны;
Enter — применить, Esc — вернуться к
холсту |
| Панель «Элементы» | дерево AST с именами узлов и кратким содержимым, щелчок — выбор узла на холсте, перетаскивание — перестановка (P1) |
| Панель «Библиотека» | папки, поиск, предпросмотр, вставить двойным щелчком, «сохранить выделенное» |
| Строка состояния | путь к узлу (как breadcrumbs, кликабельный), число узлов, стиль, размер, масштаб, ошибки разбора |
MT-02 Встроенный редактор (в документе-хозяине)
Формула редактируется на месте: рамка объекта с лёгкой подсветкой,
каретка внутри, контекстная вкладка «Формула», плавающая мини-панель
(линейное поле при F2, стиль, «Выход»), в строке состояния
хозяина — путь к узлу. Выход — Esc или щелчок вне формулы;
Enter в выключной — новая строка (eqArr), в встроенной —
выход. Размер соответствует тексту абзаца.
MT-03 Галереи палитр
Всплывающие сетки 8×N с большими предпросмотрами (28 px), подсказка с
именем команды LaTeX и UnicodeMath (\frac ·
a/b), поиск по названию, «недавние» вверху; клавиатурная
навигация стрелками, Enter — вставить.
MT-04 Линейное поле ввода
Поле с подсветкой: команды (\frac) — цвет приложения,
скобки парные — подсветка пары, ошибки — волнистое подчёркивание с
подсказкой «ожидается }», автодополнение команд (список при
\ + 2 символа), автозамена, показ курсора-соответствия на
холсте (узел под кареткой линейного поля подсвечивается).
MT-05 Панель «Элементы»
Дерево узлов; контекстное меню: «Обернуть в дробь/скобки/индекс»,
«Удалить структуру, оставить содержимое», «Преобразовать (дробь →
наклонная)», «Свойства узла» (атрибуты mo: stretchy, form,
lspace/rspace; mtable: выравнивание; mstyle:
вариант, размер, цвет).
MT-06 Свойства формулы
Стиль (встроенная/выключная), размер (как в тексте / фиксированный pt
/ %), шрифт, выравнивание выключной (центр/лево/право, отступ),
нумерация (нет / авто / свой \tag), метка
(\label), озвучивание (автотекст / свой), «применить ко
всем формулам документа».
MT-07 Экспорт
Формат (MathML / LaTeX / OMML / UnicodeMath / AsciiMath / SVG / PNG / PDF), параметры (MathML: Core/full, с аннотациями; SVG: глифы как пути/текст; PNG: DPI, фон, масштаб; LaTeX: окружение), предпросмотр текста/ картинки, «Копировать», «Сохранить».
MT-08 Вставить из текста
Поле многострочного ввода, автоопределение синтаксиса (индикатор), предпросмотр результата, ошибки списком, «Вставить», «Вставить как текст».
MT-09 Библиотека формул
Папки, поиск по имени/содержимому (LaTeX), сортировка, предпросмотры;
импорт/экспорт библиотеки (.meqlib — ZIP с
.meq), «Из файла .tex» (все формулы файла).
MT-10 Рукописный ввод (P2)
Холст штрихов с предпросмотром распознавания сверху, кнопки «Исправить / Стереть / Очистить / Вставить».
MT-11 Нумерация формул (в Write)
Формат номера ((1), (1.1) по главам,
[1], 1)), положение (справа/слева), отступ,
начало отсчёта, «нумеровать только с меткой», стиль абзаца
«Формула».
MT-12 Параметры Math
Линейный синтаксис по умолчанию, автозамена (редактор словаря),
автодополнение, шрифт по умолчанию, размеры индексов (авто из MATH /
свои), «вставлять как выключную в отдельный абзац», формат буфера обмена
(text/plain = LaTeX/UnicodeMath), озвучивание (правила,
язык).
MT-13 Автодополнение и автозамена
Всплывающий список (команда · символ · описание),
Tab/Enter — принять, Esc —
закрыть; подсказка автозамены «α (нажмите пробел)».
MT-14 Матрица
Строки/столбцы (числа, 1–30), тип скобок, выравнивание столбцов (центр/лево/право/по десятичной), линии (между строками/столбцами/рамка), заполнение (пусто/нули/единичная/точки), «определитель».
MT-15 Ссылка на формулу (в Write)
Список формул документа с номерами и предпросмотром, формат ссылки («(3)», «формула (3)», «стр. N»), гиперссылка.
MT-16 Озвучивание (предпросмотр a11y)
Текст озвучивания в выбранных правилах, «воспроизвести» через TTS ОС,
«свой текст» для /Alt.
MT-17 Подсказка ошибок
Всплывающая подсказка у подчёркнутого фрагмента линейного поля / у
узла merror на холсте: «Неизвестная команда \frc — вы имели
в виду \frac?», «Незакрытая скобка», действия «Исправить»,
«Игнорировать».
4. Модель данных
4.1 AST (ядро — MathML Core + расширения)
// kernel/crates/meridian-math/src/ast.rs
pub type NodeId = u32; // индекс в арене / arena index
pub struct Formula {
pub nodes: Vec<Node>, // арена; nodes[0] — корневой Row / arena; nodes[0] is the root Row
pub root: NodeId,
pub display: bool, // выключная / display style
pub style: FormulaStyle, // шрифт, размер, цвет, выравнивание, нумерация
pub number: Option<EqNumber>, // авто / ручной \tag / нет
pub label: Option<String>,
pub source: Option<(Syntax, String)>, // исходный текст для round-trip / original source text
pub alt_text: Option<String>,
}
pub struct Node { pub kind: NodeKind, pub attrs: Attrs /* mathcolor, mathbackground, mathsize, mathvariant, displaystyle, scriptlevel, href, class */, pub src: Option<Span> }
pub enum NodeKind {
Row(Vec<NodeId>), // mrow
Ident { text: String, variant: Option<MathVariant> }, // mi (один символ по умолчанию italic, слово — normal)
Number(String), // mn
Operator { text: String, form: Option<Form>, stretchy: Option<bool>, symmetric: Option<bool>, fence: bool, separator: bool,
largeop: Option<bool>, movablelimits: Option<bool>, lspace: Option<Em>, rspace: Option<Em>, minsize: Option<Len>, maxsize: Option<Len> }, // mo
Text(String), // mtext
Space { width: Em, height: Option<Len>, depth: Option<Len> }, // mspace
StringLit(String), // ms
Frac { num: NodeId, den: NodeId, thickness: Option<Len>, bevelled: bool, num_align: Align, den_align: Align }, // mfrac
Sqrt(Vec<NodeId>), // msqrt (неявный mrow)
Root { base: NodeId, index: NodeId }, // mroot
Style { child: NodeId }, // mstyle (атрибуты в attrs)
Error(Vec<NodeId>), // merror
Padded { child: NodeId, width: Option<Len>, height: Option<Len>, depth: Option<Len>, lspace: Option<Len>, voffset: Option<Len> }, // mpadded
Phantom(NodeId), // mphantom
Enclose { child: NodeId, notation: Vec<Notation> }, // menclose (box, roundedbox, circle, updiagonalstrike, …) — не Core, эмулируется
Sub { base: NodeId, sub: NodeId }, // msub
Sup { base: NodeId, sup: NodeId }, // msup
SubSup { base: NodeId, sub: NodeId, sup: NodeId }, // msubsup
Under { base: NodeId, under: NodeId, accentunder: bool }, // munder
Over { base: NodeId, over: NodeId, accent: bool }, // mover
UnderOver { base: NodeId, under: NodeId, over: NodeId, accentunder: bool, accent: bool }, // munderover
MultiScripts { base: NodeId, post: Vec<(Option<NodeId>, Option<NodeId>)>, pre: Vec<(Option<NodeId>, Option<NodeId>)> }, // mmultiscripts (sub, sup)
Table { rows: Vec<TableRow>, column_align: Vec<Align>, row_align: Vec<VAlign>, column_spacing: Em, row_spacing: Em,
frame: Line, column_lines: Vec<Line>, row_lines: Vec<Line>, equal_rows: bool, equal_columns: bool, width: Option<Len> }, // mtable
Semantics { child: NodeId, annotations: Vec<(String /* encoding */, String)> }, // semantics/annotation
// Расширения Meridian (сериализуются в MathML как элементы Core + атрибуты m:*) / Meridian extensions
AlignMark, // точка выравнивания & в aligned (→ malignmark)
Slot, // пустой местозаполнитель (→ <mi class="m-slot"></mi>)
EqNumber(String), // номер строки в многострочной (→ mlabeledtr)
Cancel { child: NodeId, kind: CancelKind }, // \cancel (→ menclose updiagonalstrike)
}
pub struct TableRow { pub cells: Vec<NodeId> /* mtd: каждый — Row */, pub label: Option<NodeId> /* mlabeledtr */, pub spans: Vec<(u8, u8)> }
pub enum Form { Prefix, Infix, Postfix }
pub enum MathVariant { Normal, Bold, Italic, BoldItalic, DoubleStruck, BoldFraktur, Script, BoldScript, Fraktur, SansSerif, BoldSansSerif, SansSerifItalic, SansSerifBoldItalic, Monospace, Initial, Tailed, Looped, Stretched }Инварианты: дети структурных узлов (num,
den, base, …) — всегда Row (даже
из одного элемента) — упрощает каретку; Row может быть
пустым (показывается слот); mi из одного символа без
mathvariant рисуется курсивом (математический курсив через
отображение в Mathematical Alphanumeric Symbols U+1D400…);
mo без явных атрибутов берёт их из операторного словаря
MathML Core по (text, form), где form
выводится по позиции в Row (первый — prefix, последний —
postfix, иначе infix).
4.2 Стили и настройки формулы
pub struct FormulaStyle {
pub font: FontId, // математический шрифт (с таблицей MATH) / math font (with MATH table)
pub text_font: Option<FontId>, // для mtext / for mtext; None = шрифт абзаца
pub size_pt: f32, // базовый размер / base size
pub script_scale: Option<[f32; 2]>, // переопределение ScriptPercentScaleDown / ScriptScriptPercentScaleDown
pub color: Option<Color>,
pub align: DisplayAlign, // Center | Left | Right, indent
pub spacing_scale: f32, // множитель интервалов TeX (1.0) / TeX spacing multiplier
pub number_side: Side, // Right | Left
pub break_width: Option<Len>, // ширина для разбиения выключных формул / line-breaking width for display
}
pub enum EqNumber { Auto, Manual(String), None }4.3 Каретка и выделение
pub struct Caret { pub row: NodeId, pub index: usize } // позиция между детьми Row / position between Row children
pub struct TokenCaret { pub node: NodeId, pub offset: usize } // внутри mi/mn/mtext — между символами / inside a token
pub enum CaretPos { Row(Caret), Token(TokenCaret) }
pub struct Selection { pub anchor: CaretPos, pub focus: CaretPos } // нормализуется до диапазона в общем Row / normalized to a range within the common RowПравила: выделение всегда — непрерывный диапазон детей одного
Row (при расширении за границы — поднимается к общему
предку и охватывает целые узлы); внутри токена — диапазон символов;
редактирование — команды над аренами
(insert(row, idx, nodes), remove(row, range),
wrap(range, template), unwrap(node),
set_attr), undo/redo — журнал команд с обратными операциями
(+ снимки Formula каждые 50 команд — AST маленький).
4.4 Дерево раскладки (box tree)
pub struct LayoutBox {
pub kind: BoxKind, // Glyph { gid, font, size } | Rule { w, h } | Space | HBox(children) | VBox(children) | Assembly(parts)
pub width: f32, pub ascent: f32, pub descent: f32,
pub italic_correction: f32, // для курсивных глифов / italic correction
pub shift: f32, // вертикальный сдвиг относительно базовой линии родителя / vertical shift vs parent baseline
pub offset_x: f32,
pub node: Option<NodeId>, // обратная связь для hit-test и каретки / back-link for hit testing and caret
pub atom: Atom, // Ord | Op | Bin | Rel | Open | Close | Punct | Inner | Acc | Rad | Vcent — для интервалов
pub breakpoints: Vec<Breakpoint>, // допустимые точки разрыва строки (после Rel/Bin) с штрафом / allowed line breaks with penalty
}Кэш раскладки: ключ
(hash(AST), style, font, size, display, break_width) →
LayoutBox + display list; при редактировании
перекладывается только изменённое поддерево (запоминаем хэши
поддеревьев; родители до корня перекладываются с готовыми детьми).
4.5 Формат
.meq и встраивание в документы
.meq — ZIP-пакет по правилам MOP (раздел 3.1
библии):
mimetype application/vnd.meridian.math
manifest.xml
meta.xml Dublin Core, alt-text
content.xml формула (ниже); при нескольких формулах — <m:formulas> с <m:formula> (P1)
settings.xml вид, синтаксис линейного поля, масштаб
thumbnail.png
content.xml:
<m:formula xmlns:m="urn:meridian:office:math:1" xmlns="http://www.w3.org/1998/Math/MathML"
display="block" number="auto" label="eq:quadratic" font="STIX Two Math" size="11pt" align="center">
<math display="block">
<semantics>
<mrow>
<mi>x</mi><mo>=</mo>
<mfrac>
<mrow><mo>−</mo><mi>b</mi><mo>±</mo><msqrt><msup><mi>b</mi><mn>2</mn></msup><mo>−</mo><mn>4</mn><mi>a</mi><mi>c</mi></msqrt></mrow>
<mrow><mn>2</mn><mi>a</mi></mrow>
</mfrac>
</mrow>
<annotation encoding="application/x-tex">x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}</annotation>
<annotation encoding="application/x-unicodemath">x=(−b±√(b^2−4ac))/2a</annotation>
</semantics>
</math>
<m:speech lang="ru">x равно дробь: минус b плюс-минус корень из b в квадрате минус 4 a c, делить на 2 a</m:speech>
</m:formula>Во встроенных документах (.mwx, .mpx,
.msx, .mvx, .mnx,
.mfx) формула хранится как объект
objects/eq-<n>.xml с тем же m:formula, в
тексте — ссылка
<text:object ref="eq-12" kind="math" inline="true"/>
(разметка хозяина); растровый резерв не хранится (рендер всегда
векторный), кроме экспорта в HTML/Mail (PNG/SVG).
5. Алгоритмы
5.1 Раскладка по TeXbook (приложение G) с таблицей OpenType MATH
Исходные данные из шрифта
(ttf-parser/skrifa: таблица
MATH):
| Часть таблицы | Используется для |
|---|---|
MathConstants (56 констант): ScriptPercentScaleDown,
ScriptScriptPercentScaleDown, DelimitedSubFormulaMinHeight,
DisplayOperatorMinHeight, MathLeading, AxisHeight, AccentBaseHeight,
FlattenedAccentBaseHeight, SubscriptShiftDown, SubscriptTopMax,
SubscriptBaselineDropMin, SuperscriptShiftUp, SuperscriptShiftUpCramped,
SuperscriptBottomMin, SuperscriptBaselineDropMax, SubSuperscriptGapMin,
SuperscriptBottomMaxWithSubscript, SpaceAfterScript, UpperLimitGapMin,
UpperLimitBaselineRiseMin, LowerLimitGapMin, LowerLimitBaselineDropMin,
StackTopShiftUp, StackTopDisplayStyleShiftUp, StackBottomShiftDown,
StackBottomDisplayStyleShiftDown, StackGapMin, StackDisplayStyleGapMin,
StretchStackTopShiftUp, StretchStackBottomShiftDown,
StretchStackGapAboveMin, StretchStackGapBelowMin,
FractionNumeratorShiftUp, FractionNumeratorDisplayStyleShiftUp,
FractionDenominatorShiftDown, FractionDenominatorDisplayStyleShiftDown,
FractionNumeratorGapMin, FractionNumDisplayStyleGapMin,
FractionRuleThickness, FractionDenominatorGapMin,
FractionDenomDisplayStyleGapMin, SkewedFractionHorizontalGap,
SkewedFractionVerticalGap, OverbarVerticalGap, OverbarRuleThickness,
OverbarExtraAscender, UnderbarVerticalGap, UnderbarRuleThickness,
UnderbarExtraDescender, RadicalVerticalGap,
RadicalDisplayStyleVerticalGap, RadicalRuleThickness,
RadicalExtraAscender, RadicalKernBeforeDegree, RadicalKernAfterDegree,
RadicalDegreeBottomRaisePercent |
все формулы ниже (вместо «fontdimen» TeX) |
MathGlyphInfo: MathItalicsCorrectionInfo,
MathTopAccentAttachment, ExtendedShapeCoverage, MathKernInfo
(TopRight/TopLeft/BottomRight/BottomLeft по высотам) |
italic correction, положение акцентов, кернинг индексов, «вытянутые» глифы |
MathVariants: MinConnectorOverlap,
VertGlyphConstruction / HorizGlyphConstruction (варианты по возрастанию
+ GlyphAssembly из частей с extender-флагами) |
растягивание скобок, радикалов, акцентов, стрелок, больших операторов |
Шрифты без таблицы MATH — набор эвристических констант (как в MathJax/Firefox: AxisHeight = x-height/2, толщины = underline thickness, сдвиги в долях em) и растягивание только через Unicode-варианты/масштаб.
Стили (TeX): D display, T text,
S script, SS scriptscript и «тесные» (cramped)
D' T' S' SS'. Переходы:
| Контекст | Стиль ребёнка |
|---|---|
| числитель | D→T, T→S, S→SS, SS→SS (не cramped) |
| знаменатель | D→T', T→S', S→SS', SS→SS' (cramped) |
| верхний индекс | D,T→S; S,SS→SS; cramped наследуется |
| нижний индекс | D,T→S'; S,SS→SS' |
| подкоренное, под акцентом | тот же стиль, cramped |
| пределы (над/под) | как индексы |
mstyle displaystyle/scriptlevel |
явное переопределение |
Размер шрифта: size(D,T) = base,
size(S) = base·ScriptPercentScaleDown/100,
size(SS) = base·ScriptScriptPercentScaleDown/100 (минимум 5
pt).
/// Раскладка узла в стиле / Lay out a node in the given style
fn layout(f: &Formula, id: NodeId, st: Style, ctx: &FontCtx) -> LayoutBox {
match &f.nodes[id].kind {
Row(children) => layout_row(f, children, st, ctx), // атомы + интервалы (5.1.7) + растяжение fence-операторов
Ident{..}|Number(_)|Text(_) => glyph_run(f, id, st, ctx), // шейпинг через rustybuzz (математический курсив: отображение в U+1D400…)
Operator{..} => operator_box(f, id, st, ctx), // largeop в D — больший вариант; stretchy — после раскладки Row
Frac{num, den, ..} => layout_frac(f, id, st, ctx), // 5.1.1
Sqrt(_)|Root{..} => layout_radical(f, id, st, ctx), // 5.1.3
Sub{..}|Sup{..}|SubSup{..} => layout_scripts(f, id, st, ctx), // 5.1.2
Under{..}|Over{..}|UnderOver{..} => layout_limits_or_accents(f, id, st, ctx), // 5.1.4–5.1.5
Table{..} => layout_table(f, id, st, ctx), // 5.1.8
MultiScripts{..} => layout_multiscripts(f, id, st, ctx),
Enclose{..} => layout_enclose(f, id, st, ctx),
Style{child} => layout(f, *child, st.with(&f.nodes[id].attrs), ctx),
Space{width, ..} => LayoutBox::space(width.to_px(st, ctx)),
Phantom(c) => layout(f, *c, st, ctx).invisible(),
Padded{..} | Error(_) | Semantics{..} | Cancel{..} | Slot | AlignMark | EqNumber(_) | StringLit(_) => /* тривиально / trivial */,
}
}5.1.1 Дроби (TeX Rule 15 + MATH). Для числителя
N (стиль по таблице) и знаменателя D:
θ = FractionRuleThickness (или linethickness из атрибута; 0 → стек без черты, Rule 15c: StackTop/BottomShift, StackGapMin)
a = AxisHeight
u = display ? FractionNumeratorDisplayStyleShiftUp : FractionNumeratorShiftUp
v = display ? FractionDenominatorDisplayStyleShiftDown : FractionDenominatorShiftDown
φ = display ? FractionNumDisplayStyleGapMin : FractionNumeratorGapMin
ψ = display ? FractionDenomDisplayStyleGapMin : FractionDenominatorGapMin
// зазоры до черты / gaps to the rule
if (u − N.descent) − (a + θ/2) < φ: u = a + θ/2 + φ + N.descent
if (a − θ/2) − (D.ascent − v) < ψ: v = D.ascent + ψ − (a − θ/2)
width = max(N.width, D.width) (+ 2·thin для скобочной дроби не добавляем — интервалы по атому Inner)
box = HBox[ N сдвинут вверх на u, центрирован (num_align); Rule(width, θ) на высоте a; D сдвинут вниз на v ]
ascent = max(u + N.ascent, a + θ/2); descent = max(v + D.descent, θ/2 − a)
bevelled: N и D в ряд с наклонной чертой «/» (растянутый вариант по высоте N+D) и зазорами SkewedFractionHorizontalGap/VerticalGap
5.1.2 Индексы (Rule 18). База B,
верхний P, нижний Q (в соответствующих
стилях):
δ = italic_correction(B) (если B — один глиф или Row из глифа; иначе 0)
// верхний / superscript
u = max(cramped ? SuperscriptShiftUpCramped : SuperscriptShiftUp,
B.ascent − SuperscriptBaselineDropMax, // Rule 18a (p в TeX)
SuperscriptBottomMin + P.descent) // Rule 18c
// нижний / subscript
v = max(SubscriptShiftDown,
B.descent + SubscriptBaselineDropMin,
Q.ascent − SubscriptTopMax)
// оба: минимальный зазор между ними / both: minimal gap
if sup && sub:
gap = (u − P.descent) − (Q.ascent − v)
if gap < SubSuperscriptGapMin: v += SubSuperscriptGapMin − gap
// затем попытка поднять верхний, чтобы не превысить SuperscriptBottomMaxWithSubscript / then raise sup if possible
ψ = SuperscriptBottomMaxWithSubscript − (u − P.descent)
if ψ > 0 { u += ψ; v −= ψ }
kern_sup = math_kern(B.TopRight, P.BottomLeft, высоты) // MathKernInfo: суммарный кернинг на высоте стыка
kern_sub = math_kern(B.BottomRight, Q.TopLeft, высоты)
box = HBox[ B, (P сдвинут на u, x = B.width + δ + kern_sup), (Q сдвинут на −v, x = B.width + kern_sub) ]; после — SpaceAfterScript
Пред-индексы (mmultiscripts): зеркально слева (кернинг
TopLeft/BottomLeft базы), база не получает italic correction. Если база
— большой оператор в стиле T с movablelimits,
пределы ставятся как индексы.
5.1.3 Радикалы (Rule 11).
X = layout(radicand, cramped(st))
θ = RadicalRuleThickness; ψ = display ? RadicalDisplayStyleVerticalGap : RadicalVerticalGap
h_t = X.ascent + X.descent + ψ + θ // требуемая высота знака / required sign height
sign = stretch_vertical('√', h_t, ctx) // вариант или сборка (5.1.6), выровнен так, чтобы верх черты = X.ascent + ψ + θ
если sign выше требуемого — лишнее распределяется: черта на высоте X.ascent + ψ (+ половина излишка в display)
rule = Rule(X.width, θ) от правого верха sign; ascent = черта + θ + RadicalExtraAscender
degree (mroot): layout(index, SS) сдвинут вверх на RadicalDegreeBottomRaisePercent % высоты sign,
x: RadicalKernBeforeDegree, после — RadicalKernAfterDegree (отрицательный — заходит под знак)
5.1.4 Большие операторы и пределы (Rule 13, 13a). В
стиле D: берётся следующий по размеру вертикальный вариант
глифа с высотой ≥ DisplayOperatorMinHeight (если есть), центрируется по
оси (shift = (ascent − descent)/2 − AxisHeight). Пределы
над/под (munderover, или msubsup при
largeop в D без
movablelimits=false):
над: gap_u = max(UpperLimitGapMin, UpperLimitBaselineRiseMin − over.descent) → over.baseline = op.ascent + gap_u + over.descent
под: gap_l = max(LowerLimitGapMin, LowerLimitBaselineDropMin − under.ascent) → under.baseline = −(op.descent + gap_l + under.ascent)
ширина = max(всех), центрирование; italic correction оператора: над — сдвиг вправо на δ/2, под — влево на δ/2 (Rule 13a)
5.1.5 Акценты (Rule 12). Акцент A над
базой B:
x_A = MathTopAccentAttachment(B) (или B.width/2) − MathTopAccentAttachment(A) (или A.width/2)
если B.width > A.width·1.1 и у A есть горизонтальные варианты → A = stretch_horizontal(A, B.width)
δ = max(0, B.ascent − AccentBaseHeight) // низкие базы: акцент опускается к x-height / lower accent over short bases
если B.ascent > FlattenedAccentBaseHeight и есть «плоский» вариант (flac feature) → использовать его
A.baseline = B.ascent − AccentBaseHeight + δ ... итог: низ акцента на max(B.ascent, AccentBaseHeight)
ascent = A.baseline + A.ascent; подчёркивающие акценты (accentunder) — симметрично снизу с UnderbarVerticalGap
не-акцентные munder/mover (например, \overset) — как пределы 5.1.4 с StretchStack*-константами
5.1.6 Растягиваемые разделители (Rule 19) и сборка
глифов. Для Row с fence-операторами
(\left…\right, stretchy=true): после раскладки
содержимого h = max ascent, d = max descent
(без самих скобок):
a = AxisHeight
δ = max(h − a, d + a) // половина требуемой симметричной высоты
target = max(2·δ · DelimiterFactor(0.901), 2·δ − DelimiterShortfall(5pt)) // TeX \delimiterfactor, \delimitershortfall
target = max(target, DelimitedSubFormulaMinHeight?) — только для «\big» ручных размеров
glyph = первый из VertGlyphConstruction.variants с высотой ≥ target
если нет → GlyphAssembly: части [bottom, extender, middle?, extender, top];
n_ext = ceil((target − Σ non-extender heights + overlap·(parts−1)) / (ext_height − overlap)), overlap ≥ MinConnectorOverlap,
фактический overlap подбирается так, чтобы суммарная высота ≥ target при равномерном распределении
центрирование по оси: shift = (glyph.ascent − glyph.descent)/2 − a (symmetric=true); для несимметричных (mo minsize/maxsize) — по границам
Горизонтальное растягивание (стрелки, скобки над/под, черты) —
аналогично по HorizGlyphConstruction до ширины базы.
5.1.7 Интервалы между атомами (Rule 20). Класс атома
узла: mi/mn/mtext/mrow → Ord; mo по
операторному словарю (MathML Core: категории → Bin для
+ − × ⋅ …, Rel для
= < > ≤ ∈ → …, Op для largeop,
Open/Close для fence, Punct для ,
;, Inner — дроби/\left…\right).
Матрица TeX (единицы: 0 — нет, 1 — thin (3 mu), 2 — medium (4 mu), 3 —
thick (5 mu); в скобках — только в стилях D/T; 1 mu = 1/18 em):
| слева \ справа | Ord | Op | Bin | Rel | Open | Close | Punct | Inner |
|---|---|---|---|---|---|---|---|---|
| Ord | 0 | 1 | (2) | (3) | 0 | 0 | 0 | (1) |
| Op | 1 | 1 | — | (3) | 0 | 0 | 0 | (1) |
| Bin | (2) | (2) | — | — | (2) | — | — | (2) |
| Rel | (3) | (3) | — | 0 | (3) | 0 | 0 | (3) |
| Open | 0 | 0 | — | 0 | 0 | 0 | 0 | 0 |
| Close | 0 | 1 | (2) | (3) | 0 | 0 | 0 | (1) |
| Punct | (1) | (1) | — | (1) | (1) | (1) | (1) | (1) |
| Inner | (1) | 1 | (2) | (3) | (1) | 0 | (1) | (1) |
Правила 5–6: Bin превращается в Ord, если
стоит первым/последним в Row или после
Bin/Op/Rel/Open/Punct (унарный минус) или перед
Rel/Close/Punct. Явные lspace/rspace у
mo переопределяют матрицу (режим «MathML-интервалы» для
точного соответствия браузерам — настройка). mspace,
\, и т.п. — добавляются как есть.
5.1.8 Таблицы и выравнивание. mtable:
ширина столбца = max ширин ячеек (при equalcolumns —
общая), выравнивание по columnalign; высота строки — max
ascent/descent; rowspacing/columnspacing (по
умолчанию 1ex/0.8em); базовая линия таблицы — ось (центр по AxisHeight)
или align=baseline N; AlignMark (из
& в aligned): ячейка делится на части до и
после марки, столбцы выравниваются так, чтобы марки совпали
(классический «rl»-режим: нечётные столбцы вправо, чётные влево);
mlabeledtr — номер справа за пределами ширины.
5.1.9 Разбиение длинных формул на строки. Для
выключных формул шире break_width (колонка Write): точки
разрыва — после Rel (штраф 0), после Bin
(штраф 20), перед Open верхнего уровня (штраф 50), внутри
Row верхнего уровня и внутри самых внешних скобок (штраф
+100 за уровень), никогда — внутри дробей, радикалов, индексов, таблиц.
Алгоритм — динамическое программирование (как Knuth–Plass в одномерном
варианте): минимизируем сумму штрафов + квадрат недобора ширины; вторая
и далее строки — с отступом wrapIndent (1 em) и оператор
переносится в начало строки (стиль ГОСТ/русский: знак повторяется на
обеих строках — настройка repeat_operator). Встроенные
формулы: отдаём хозяину список «кусков» с допустимыми разрывами и
штрафами; Write переносит в точках Rel/Bin как между словами.
5.1.10 Italic correction (Rule 17). После курсивного
глифа добавляется δ из
MathItalicsCorrectionInfo перед: верхним индексом (всегда),
прямым символом/Rel/Punct/Close (в T/D), концом Row (если
дальше — рамка/скобка); не добавляется перед нижним индексом (TeX Rule
18: только для верхнего).
5.2 Парсер UnicodeMath → AST и обратно
Токенизация: Unicode-символы (операторы по словарю), управляющие
слова \name (из таблицы ~1000: имена символов и структур),
числа (\d+([.,]\d+)?), идентификаторы (буквы по одной —
каждая mi; слова-функции sin, lim
— mi normal + ⁡), пробел —
разделитель построения, спецсимволы
/ ^ _ ( ) [ ] { } | 〖 〗 ■ & @ ┬ ┴ ▒ ▭.
Грамматика (по UTN #28, упрощённо, приоритеты от высокого к низкому):
exp := element*
element := factor (('^' | '_') script)* ('┬' operand)? ('┴' operand)? // индексы, под/над
factor := operand ('/' operand)? // дробь: ближайшие операнды, скобки снимаются (〖〗 — невидимые)
operand := atom | '(' exp ')' | '[' exp ']' | '{' exp '}' | '|' exp '|' | '〖' exp '〗' | '\open' exp '\close'
| nary | function | sqrt | matrix | cases | '\of' …
nary := naryop ('_' operand)? ('^' operand)? naryand // ∑_(i=1)^n a_i : naryand — следующий operand
sqrt := '√' operand | '√(' exp '&' exp ')' | '∛' operand | '∜' operand // √(n&x) — корень степени n
matrix := '■(' row ('@' row)* ')' | '\matrix(' … ')' // row := exp ('&' exp)*
cases := '{' exp ('@' exp)* '█' … // системы (Word: Ⓒ 'cases')
function := fname ('^' | '_' script)* '▒' operand // ▒ — funcapply (невидимый); пробел тоже
Build-up: при вводе пробела/оператора после позиции каретки парсер
пытается свернуть только незавершённый линейный хвост
(от последнего собранного узла до каретки) — как в Word;
Ctrl+= — собрать всё; Ctrl+Shift+= —
«разобрать» (линеаризовать) выделенное обратно в текст (сериализация).
Ошибки (незакрытые скобки) — хвост остаётся линейным (символы в
mi/mo), без merror.
Сериализация AST → UnicodeMath: Frac →
num/den с добавлением ( ) только если операнд
содержит > 1 узла (используем 〖…〗 для невидимых
скобок); Sup/Sub → ^/_ с
( ) при составном индексе; Sqrt →
√(…); Root → √(n&x); большие
операторы → символ с _/^ и ▒
перед операндом; Table → ■(a&b@c&d)
или скобочный вариант; Text → "…"; стили →
\mathbf(…); интервалы → \thinsp и т.д.;
исходный текст неизменённых поддеревьев берётся из src
(стабильность).
5.3 Парсер LaTeX (подмножество amsmath) → AST и обратно
Токенизация: \command (буквы) или
\<символ>, { },
^ _ & \\ ~ % $, символы; пробелы значимы только в
\text{}; комментарии % до конца строки.
Рекурсивный спуск с таблицей команд
CommandDef { name, args: Vec<ArgKind /* Group | Optional | Token | Math */>, build: fn(args) -> NodeKind }:
| Группа | Команды |
|---|---|
| Структуры | \frac \dfrac \tfrac \cfrac \binom \dbinom \tbinom \genfrac \sqrt[n] \over (устар.),
^ _,
\left \right \middle \big \Big \bigg \Bigg (\bigl \bigr …),
\overset \underset \stackrel \substack \sideset \overbrace \underbrace \overline \underline \overrightarrow \overleftarrow \overleftrightarrow \underrightarrow \xrightarrow[]{} \xleftarrow \widehat \widetilde \hat \check \tilde \acute \grave \dot \ddot \dddot \breve \bar \vec \mathring |
| Окружения | matrix pmatrix bmatrix Bmatrix vmatrix Vmatrix smallmatrix array (с преамбулой колонок l c r |) cases rcases aligned alignedat gathered split align align* gather gather* multline equation equation* eqnarray(устар.) subequations |
| Стили | \mathbb \mathbf \mathit \mathrm \mathcal \mathscr \mathfrak \mathsf \mathtt \boldsymbol \bm \pmb \text \textbf \textit \textrm \mathnormal \operatorname \operatorname* \DeclareMathOperator (P1) \displaystyle \textstyle \scriptstyle \scriptscriptstyle \limits \nolimits \color{} \textcolor{}{} \colorbox \boxed \fbox \cancel \bcancel \xcancel \phantom \hphantom \vphantom \smash \mathstrut \strut \tag \tag* \label \ref \eqref \nonumber \notag \allowbreak \\[len] \hline \cline \hdashline (P1) |
| Интервалы | \, \: \; \! \quad \qquad \ \enspace \thinspace \medspace \thickspace \negthinspace \hspace{} \hskip \kern \mkern \mspace \phantom |
| Символы | греческие (\alpha…\Omega, \varepsilon…),
операторы
(\pm \mp \times \div \cdot \ast \star \circ \bullet \oplus \otimes \odot \ominus \oslash \cap \cup \sqcap \sqcup \vee \wedge \setminus \wr \amalg \dagger \ddagger),
отношения
(\le \ge \ne \neq \leq \geq \ll \gg \equiv \sim \simeq \approx \cong \propto \prec \succ \subset \supset \subseteq \supseteq \in \notin \ni \mid \nmid \parallel \perp \models \vdash \dashv \asymp \doteq \bowtie),
стрелки
(\to \gets \leftarrow \rightarrow \Leftarrow \Rightarrow \leftrightarrow \Leftrightarrow \mapsto \longmapsto \longrightarrow \longleftarrow \uparrow \downarrow \Uparrow \Downarrow \updownarrow \nearrow \searrow \swarrow \nwarrow \hookrightarrow \rightharpoonup \leftharpoondown \iff \implies \impliedby),
большие
(\sum \prod \coprod \int \iint \iiint \oint \oiint \bigcup \bigcap \bigvee \bigwedge \bigoplus \bigotimes \bigodot \biguplus \bigsqcup),
функции
(\sin \cos \tan \cot \sec \csc \arcsin \arccos \arctan \sinh \cosh \tanh \coth \ln \log \lg \exp \lim \limsup \liminf \max \min \sup \inf \det \dim \ker \deg \gcd \hom \arg \Pr \mod \bmod \pmod \pod),
прочие
(\infty \partial \nabla \forall \exists \nexists \neg \lnot \emptyset \varnothing \aleph \hbar \ell \wp \Re \Im \prime \backprime \angle \measuredangle \triangle \square \blacksquare \diamond \lozenge \star \bigstar \checkmark \dots \ldots \cdots \vdots \ddots \iddots \therefore \because \top \bot \ohm \degree \celsius \%
\# \& \_ \{
\} \$ |
\langle \rangle \lfloor \rfloor \lceil \rceil \lvert \rvert \lVert \rVert),
amssymb
(\leqslant \geqslant \lesssim \gtrsim \varsubsetneq \nsubseteq \square \blacktriangleright \twoheadrightarrow \rightrightarrows \circledast \boxplus \boxtimes \mathbb{…}
и др. — полный список в
kernel/crates/meridian-math/data/latex_symbols.csv) |
| Макросы | \newcommand{\name}[n]{body} /
\renewcommand / \def (простые) — раскрытие на
этапе токенизации с защитой от рекурсии (глубина 32) — P1 |
Ошибки: неизвестная команда → merror с текстом команды
(подсказка по расстоянию Левенштейна ≤ 2), незакрытая группа →
автозакрытие в конце с пометкой, \right без
\left — невидимая \left.; разбор никогда не
прерывается целиком.
Сериализация AST → LaTeX: таблица «узел → команда» (обратная к
таблице выше), \left(/\right) только при
stretchy и неодносимвольных высоких содержимых,
{} только где нужно (однобуквенные аргументы без скобок —
по настройке «компактный LaTeX»), \mathbb{R} по
mathvariant, \text{} для mtext,
окружение по типу таблицы (pmatrix при (, bmatrix при
[, cases при { + 2 столбца, aligned при
AlignMark), &/\\;
src-текст неизменённых узлов — как есть.
5.4 Навигация каретки по дереву
Right (→):
Token(node, off) при off < len → off+1; при off == len → Row(parent, idx(node)+1)
Row(row, i): если i < len(row): child = row[i]
если child — токен → Token(child, 0)... (вход в токен: только если он редактируемый (mi/mn/mtext))
если child — структура → первый слот: Row(child.first_slot, 0) (mfrac → num; msub → base; mtable → [0][0])
иначе (mo/mspace) → Row(row, i+1)
если i == len(row): если row — корень → стоп; иначе parent_structure = parent(row):
следующий слот структуры (num → den; base → sub → sup; строка/ячейка таблицы → следующая ячейка)
если слотов нет → Row(parent(parent_structure), idx(parent_structure)+1) // выход за структуру
Left (←): зеркально (вход в последний слот, Token(child, len))
Up/Down: в msub/msup/msubsup/munderover/mfrac/mtable — переход между слотами по вертикали
(den ↔ num; sub ↔ base ↔ sup; строки таблицы); иначе — к ближайшей по x позиции в родительском Row строкой выше/ниже
(многострочные таблицы/разрывы) — hit-test по box tree
Tab / Shift+Tab: следующий/предыдущий пустой Slot (обход в порядке документа), если нет — следующий слот
Home/End: Row(row, 0) / Row(row, len); Ctrl+Home/End — корень
Ctrl+→/←: по узлам (пропуская вход в токены и структуры): Row(row, i±1)
Ctrl+↑: выделить родительскую структуру целиком; Ctrl+↓: сузить до первого ребёнка
Backspace в Row(row, i>0): если row[i−1] — токен → удалить последний символ (пустой токен — удалить узел);
если структура → первое нажатие выделяет её (подсветка «удалить структуру?»), второе — unwrap:
заменяет структуру содержимым слотов подряд (как Word), Ctrl+Backspace — удалить целиком
Delete — симметрично вперёд
Enter: в Row верхнего уровня выключной формулы → обернуть в Table (eqArr) / добавить строку; во встроенной — выход
Щелчок мыши: hit-test по box tree → ближайший LayoutBox с node → позиция между детьми по x (половина ширины)
Выделение Shift+стрелки расширяет focus по
тем же правилам, затем нормализуется до общего Row
(поднимаемся, пока anchor и focus не в одном Row; охватываем целые
узлы). Двойной щелчок — выделить узел структуры; тройной — всю
формулу.
5.5 Конвертация OMML ↔︎ AST ↔︎ MathML ↔︎ LaTeX
Единая таблица соответствий (фрагмент; полная —
data/omml_map.csv):
| OMML | AST | MathML | LaTeX |
|---|---|---|---|
m:f (type=bar) |
Frac | mfrac |
\frac{}{} |
m:f type=noBar |
Frac{thickness: 0} | mfrac linethickness="0" |
\binom{}{} (в скобках) /
\genfrac{}{}{0pt}{} |
m:f type=skw / lin |
Frac{bevelled} / Row(a, mo /, b) | mfrac bevelled="true" / mrow |
a/b / {}^{a}/{}_{b} |
m:rad (degHide=1) |
Sqrt / Root | msqrt / mroot |
\sqrt{} / \sqrt[n]{} |
m:sSub/m:sSup/m:sSubSup |
Sub/Sup/SubSup | msub/msup/msubsup |
_{}/^{} |
m:sPre |
MultiScripts{pre} | mmultiscripts + mprescripts |
{}_{a}^{b}X |
m:nary chr=∑ limLoc=undOvr |
UnderOver{base: Operator largeop} + naryand в Row | munderover |
\sum_{}^{} … |
m:nary limLoc=subSup |
SubSup{base: Operator largeop, movablelimits=false} | msubsup |
\sum\nolimits_{}^{} / \int_{}^{} |
m:d begChr=( endChr=) sepChr=, |
Row(mo ( stretchy fence, e1, mo , separator, e2, mo )) | mrow (или mfenced при чтении) |
\left( … \right) |
m:m |
Table | mtable |
\begin{matrix} (скобки — по внешнему m:d:
pmatrix/bmatrix/…) |
m:acc chr=̂ |
Over{accent} | mover accent="true" |
\hat{} (\widehat при ширине > 1
символа) |
m:bar pos=top/bot |
Over/Under с ‾/_ |
mover/munder |
\overline/\underline |
m:box (opEmu) |
Row (атом Op) / Padded | mrow/mpadded |
\operatorname{} |
m:borderBox (hideTop …
strikeH/V/BLTR/TLBR) |
Enclose{box, strikes} | menclose notation="box …" |
\boxed{} / \cancel{} |
m:groupChr chr=⏟ pos=bot vertJc=top |
Under{under: mo ⏟ stretchy} → с подписью Under(Under) | munder |
\underbrace{}_{} |
m:limLow/m:limUpp |
Under/Over (не accent) | munder/mover |
\underset{}{} / \overset{}{} (для lim —
\lim_{}) |
m:func (fName, e) |
Row(fname, mo (ApplyFunction), e) | mrow с mo ⁡ |
\sin x, \operatorname{f} |
m:eqArr |
Table с AlignMark (из & в тексте, # →
EqNumber) |
mtable + malignmark |
\begin{aligned} |
m:phant (show=0,
zeroWid/zeroAsc/zeroDesc) |
Phantom / Padded | mphantom/mpadded |
\phantom/\hphantom/\vphantom |
m:r + m:rPr sty=p + nor |
Ident(normal)/Text | mi mathvariant="normal"/mtext |
\mathrm{}/\text{} |
m:r sty=b/bi,
scr=double-struck/script/fraktur/sans-serif/monospace |
Ident{variant} | mi mathvariant (или символы U+1D400…) |
\mathbf \boldsymbol \mathbb \mathcal \mathfrak \mathsf \mathtt |
m:oMathPara jc=center/left/right |
Formula{display, align} | math display="block" |
\[ … \] / equation |
m:mathPr (документ) |
FormulaStyle по умолчанию | — | — |
Текст m:r/m:t в OMML — нетокенизированная строка
(x+1): при импорте разбивается на mi/mn/mo по
категориям Unicode и операторному словарю (цифры подряд с разделителем —
одно mn; буквы — по одной mi, кроме случаев
nor/функций); при экспорте соседние токены одного стиля
сливаются в один m:r. MathML → AST — прямое отображение с
нормализацией (устаревшие элементы, mfenced →
mrow, неявные mrow для
msqrt/mstyle/mtd, атрибуты
fontstyle/fontweight → mathvariant); AST →
MathML — обратное, в режиме Core menclose →
mrow с mpadded + CSS-классами,
mmultiscripts/mtable — есть в Core;
semantics с аннотациями — по настройке. Round-trip-тесты:
OMML → AST → OMML должен давать канонически эквивалентный XML
(нормализация пробелов и порядка атрибутов).
5.6 Рендер
LayoutBox → display list Meridian Render:
GlyphRun { font, size, glyphs: [(gid, x, y)], color } для
глифов (контуры через skrifa, растеризация Skia с хинтингом
выкл., субпиксельное позиционирование), Rect для черт
(толщина не менее 1 px на экране — snapping к пиксельной сетке при
масштабе < 150 %, чтобы черта дроби не «пропадала»),
Path для сборок (части — те же глифы с наложением по
overlap), Clip для menclose. Каретка и
выделение — оверлей. Для PDF/печати — тот же display list (шрифты
встраиваются подмножеством, сборки — как глифы, не контуры); SVG —
<path> из контуров глифов (с дедупликацией одинаковых
глифов через <defs>/<use>) или
<text>; PNG — Skia растр с суперсэмплингом ×2 при DPI
< 150. В Write формула — inline-объект с
InlineBox { width, ascent, descent, breakpoints };
изменение размера абзаца → перекладка с кэшем (4.4). Производительность:
формула из 200 узлов — раскладка < 1 мс, 1000 формул в документе —
первичная раскладка < 300 мс (параллельно по формулам,
rayon).
5.7 Озвучивание (MathSpeak/ClearSpeak)
Обход AST с правилами: Frac → «дробь: {num}, делить на
{den}» (ClearSpeak: «{num} над {den}» для простых), Sup с
mn 2/3 → «в квадрате/в кубе», иначе «в степени {sup}»,
Sub → «нижний индекс {sub}» (сокращённо «{base} {sub}» для
одной буквы/цифры в brief), Sqrt → «корень из {x}»,
Root → «корень степени {n} из {x}», большие операторы —
«сумма от {under} до {over} {naryand}», скобки — «открывается скобка …
закрывается скобка» (verbose) или паузы (brief), матрицы — «матрица 2 на
2, строка 1: …», символы — по таблице имён (∞ →
«бесконечность», ≤ → «меньше или равно»), греческие по
названиям, mtext — как есть. Языки: ru/en (таблицы в
data/speech/<lang>.csv), стиль
verbose/brief/superbrief.
6. MeridianScript API
declare namespace math {
function parse(src: string, syntax?: "latex" | "unicodemath" | "asciimath" | "mathml" | "omml" | "starmath" | "auto"): Equation;
function fromSelection(): Equation | null; // формула под кареткой в документе-хозяине / equation at caret in host document
function equations(doc?: Document): Equation[]; // все формулы документа (Write/Slides/…)
function fonts(): MathFontInfo[]; // шрифты с таблицей MATH
const library: EquationLibrary; // list(folder?), get(name), save(name, eq), remove
const ui: MathUi; // insertDialog(), palettes, showError
}
interface Equation {
readonly ast: MathNode; // корень (неизменяемый снимок; изменения — через методы) / root snapshot
display: boolean; size: number | "inherit"; font: string; color: string | null; align: "center" | "left" | "right";
number: "auto" | string | null; label: string | null; altText: string | null;
toLatex(opts?: { env?: "none" | "inline" | "display" | "equation" | "align"; compact?: boolean }): string;
toUnicodeMath(): string; toAsciiMath(): string;
toMathML(opts?: { core?: boolean; annotations?: ("latex" | "unicodemath")[]; namespace?: boolean }): string;
toOmml(opts?: { para?: boolean }): string;
toSvg(opts?: { size?: number; color?: string; glyphs?: "paths" | "text"; display?: boolean }): string;
toPng(opts?: { dpi?: number; background?: string | "transparent"; scale?: number }): Promise<Uint8Array>;
speak(opts?: { rules?: "mathspeak" | "clearspeak"; verbosity?: "verbose" | "brief" | "superbrief"; lang?: "ru" | "en" }): string;
layout(opts?: { width?: number }): { width: number; ascent: number; descent: number };
find(selector: string): MathNode[]; // CSS-подобный селектор по типу/атрибутам: "mfrac", "mi[mathvariant=bold]", "msup > mn"
replace(selector: string, fn: (node: MathNode) => MathNode | string /* LaTeX */): number;
walk(fn: (node: MathNode, path: number[]) => void): void;
insertAt(doc: Document, position: Position, opts?: { display?: boolean; numbered?: boolean }): EquationObject; // в документ-хозяин
update(): void; // применить изменения к объекту в документе / commit to the host object
clone(): Equation;
}
interface MathNode { readonly type: MathNodeType; readonly children: MathNode[]; readonly attrs: Record<string, string>; readonly text?: string;
toLatex(): string; with(attrs: Record<string, string>): MathNode; }
type MathNodeType = "mrow" | "mi" | "mn" | "mo" | "mtext" | "mspace" | "ms" | "mfrac" | "msqrt" | "mroot" | "mstyle" | "merror" | "mpadded"
| "mphantom" | "menclose" | "msub" | "msup" | "msubsup" | "munder" | "mover" | "munderover" | "mmultiscripts" | "mtable" | "mtr" | "mtd" | "semantics";
declare function m(strings: TemplateStringsArray, ...values: (string | number | Equation | MathNode)[]): Equation; // тег-шаблон LaTeX / LaTeX tagged templateПримеры:
// 1. Заменить все \frac на \dfrac в документе Write и перенумеровать выключные
// 1. Replace all \frac with \dfrac in a Write document and renumber display equations
import { write } from "meridian/apps";
const doc = write.activeDocument()!;
for (const eq of math.equations(doc)) {
eq.replace("mfrac", n => `\\dfrac{${n.children[0].toLatex()}}{${n.children[1].toLatex()}}`);
if (eq.display && eq.number === null) eq.number = "auto";
eq.update();
}
// 2. Генерация формул из данных: таблица умножения матриц для слайда
// 2. Generate equations from data: matrix product for a slide
import { slides } from "meridian/apps";
const A = [[1, 2], [3, 4]], B = [[0, 1], [1, 0]];
const mat = (M: number[][]) => `\\begin{pmatrix}${M.map(r => r.join(" & ")).join(" \\\\ ")}\\end{pmatrix}`;
const C = A.map((r, i) => B[0].map((_, j) => r.reduce((s, a, k) => s + a * B[k][j], 0)));
const eq = m`${mat(A)} \cdot ${mat(B)} = ${mat(C)}`;
eq.display = true;
eq.insertAt(slides.activeSlide(), { x: 100, y: 200 });
// 3. Экспорт всех формул документа в SVG и список LaTeX в файл
// 3. Export all equations to SVG plus a LaTeX listing
import { fs, path } from "meridian/host";
const out = await math.ui.pickFolder("Куда сохранить");
const lines: string[] = [];
math.equations(write.activeDocument()!).forEach((eq, i) => {
fs.write(path.join(out, `eq-${i + 1}.svg`), eq.toSvg({ glyphs: "paths", color: "#000" }));
lines.push(`% eq ${i + 1}${eq.label ? ` (${eq.label})` : ""}\n${eq.toLatex({ env: "display" })}`);
});
fs.write(path.join(out, "equations.tex"), lines.join("\n\n"));
// 4. Проверка доступности: формулы без alt-текста получают автоозвучивание
// 4. Accessibility pass: equations without alt text get generated speech
for (const eq of math.equations()) if (!eq.altText) { eq.altText = eq.speak({ rules: "clearspeak", lang: "ru" }); eq.update(); }7. Форматы
| Формат | Чтение | Запись | Особенности |
|---|---|---|---|
.meq |
✔ | ✔ | пакет MOP (4.5); несколько формул — P1 |
.mml (MathML) |
✔ | ✔ | Presentation 1–4 и Core; Content MathML — чтение с конверсией (P1);
semantics/annotation сохраняются; кодировка UTF-8, сущности
HTML/MathML (⁢ …) |
.tex (фрагмент/файл) |
✔ | ✔ | формулы в $…$, \(…\), \[…\],
окружениях; файл с несколькими формулами → библиотека; запись — фрагмент
или полный документ с преамбулой
(amsmath, amssymb, unicode-math при шрифте STIX) |
| OMML (в docx/pptx/xlsx) | ✔ | ✔ | через кодек OOXML (Write/Slides/Sheets): m:oMath,
m:oMathPara, m:mathPr; Equation Editor
3.0/MathType OLE (MTEF) — P2 |
ODF формулы (.odf, объекты в
.odt/.odp/.ods) |
✔ | ✔ | content.xml с MathML +
annotation encoding="StarMath 5.0"; запись — MathML +
сгенерированный StarMath (для редактирования в LibreOffice) |
| StarMath (текст) | ✔ | ✔ | синтаксис 2.7; запись — для аннотаций ODF и буфера |
| UnicodeMath / AsciiMath (текст) | ✔ | ✔ / ✔(P1) | буфер обмена, линейное поле |
HTML (<math>, <img alt>) |
✔ | ✔ | экспорт Write/Mail: <math> + PNG/SVG-резерв с
alt = LaTeX |
| SVG / PNG | — | ✔ | 2.6; PNG хранит LaTeX в tEXt:Description — при вставке
PNG обратно в Meridian формула восстанавливается (P1) |
| — | ✔ | через Write/Render; теги PDF/UA — P1 | |
| Буфер обмена | ✔ | ✔ | text/plain (LaTeX/UnicodeMath по настройке),
application/mathml+xml + «MathML» (Word/LibreOffice), OMML
(application/vnd.openxmlformats-officedocument.wordprocessingml
фрагмент — Word принимает), image/svg+xml,
image/png |
.meqlib |
✔ | ✔ | библиотека формул (ZIP с .meq и
index.xml) |
8. Горячие клавиши
Совместимость с Word там, где у Word есть сочетания; остальное — наши
(Ctrl → ⌘ на macOS).
| Действие | Клавиши | Действие | Клавиши |
|---|---|---|---|
| Вставить формулу (в любом приложении) | Alt+= |
Выйти из формулы / вернуться в текст | Esc / Ctrl+Enter |
| Линейное поле (фокус) / закрыть | F2 / Esc |
Переключить синтаксис линейного поля | Ctrl+Shift+L |
| Собрать (build-up) всё / линеаризовать выделенное | Ctrl+= / Ctrl+Shift+= |
Встроенная ↔︎ выключная | Ctrl+Shift+D |
| Дробь / наклонная дробь | Ctrl+Shift+F / Ctrl+Alt+F |
Радикал / корень n-й степени | Ctrl+Shift+R / Ctrl+Alt+R |
| Верхний / нижний индекс / оба | Ctrl+Shift+=→ занято: Ctrl+^
(Ctrl+Shift+6) / Ctrl+_
(Ctrl+Shift+-) / Ctrl+Shift+B |
Пред-индексы | Ctrl+Alt+_ |
| Интеграл / сумма / произведение / предел | Ctrl+Shift+I / Ctrl+Shift+S /
Ctrl+Shift+P / Ctrl+Shift+M→ занято матрицей:
Ctrl+Alt+L |
Матрица (диалог) | Ctrl+Shift+M |
| Скобки (растягиваемые) / невидимые | Ctrl+Shift+(, [, { /
Ctrl+Shift+| |
Система (cases) | Ctrl+Shift+{ затем Enter |
| Акценты: вектор / крышка / черта / точка / тильда | Ctrl+Alt+V / Ctrl+Alt+H /
Ctrl+Alt+B / Ctrl+Alt+D /
Ctrl+Alt+T |
Над/подчёркивающая скобка | Ctrl+Alt+O / Ctrl+Alt+U |
| Стили: жирный / прямой / ажурный / каллиграфический / фрактур / текст | Ctrl+B / Ctrl+Shift+N /
Ctrl+Alt+Shift+B / Ctrl+Alt+Shift+C /
Ctrl+Alt+Shift+F / Ctrl+Shift+T |
Цвет | Ctrl+Shift+C |
| Следующий / предыдущий слот | Tab / Shift+Tab |
По узлам ← → | Ctrl+← / Ctrl+→ |
| Выбрать родителя / сузить | Ctrl+↑ / Ctrl+↓ |
Выделить узел / всё | двойной щелчок / Ctrl+A |
| Удалить структуру, оставить содержимое (unwrap) | Backspace ×2 на структуре /
Ctrl+Shift+Backspace |
Удалить структуру целиком | Ctrl+Backspace |
| Новая строка (aligned) / точка выравнивания | Enter (в выключной) / Ctrl+&
(Ctrl+Shift+7) |
Номер / метка формулы | Ctrl+Shift+# / Ctrl+Shift+Alt+L |
| Автодополнение: принять / закрыть | Tab, Enter / Esc |
Озвучить формулу | Ctrl+Alt+S |
| Копировать как LaTeX / MathML / изображение | Ctrl+Shift+Alt+C (меню) |
Вставить из текста (диалог) | Ctrl+Shift+V |
| Масштаб холста (отдельное окно) | Ctrl++ / Ctrl+− / Ctrl+0 |
Командная палитра | Ctrl+K |
| Шаблоны формул | Ctrl+Shift+E→ занято экспортом в других приложениях:
Ctrl+Alt+E |
Библиотека | Ctrl+Shift+Alt+B→ Ctrl+L в отдельном
окне |
Конфликты (стрелки) разрешаются в общем реестре
docs/shell/keymap.md; во встроенном режиме приоритет у
сочетаний формулы только при каретке внутри формулы.
9. Критерии готовности
| # | Критерий | Метрика / способ проверки |
|---|---|---|
| 1 | Качество раскладки | корпус 2000 формул (Wikipedia math, amsmath user guide, тесты MathML
WPT, формулы из реальных docx): рендер при 300 dpi сравнивается с
XeLaTeX + unicode-math (STIX Two Math) — различие ≤ 2 %
площади (после совмещения), визуальная экспертиза 200 формул без
замечаний «уровня TeX» |
| 2 | MathML Core | тесты web-platform-tests/mathml (раскладка и
операторный словарь) проходят ≥ 95 % применимых |
| 3 | OMML round-trip | 500 docx с формулами (Word 2010–365): docx → .mwx →
docx — канонически эквивалентный OMML в ≥ 99 % формул; визуально в Word
— без изменений |
| 4 | MathML/ODF | 300 odt-формул LibreOffice: открытие и сохранение без потерь (MathML и StarMath-аннотация), LibreOffice открывает наш результат |
| 5 | UnicodeMath | 300 примеров UTN #28 строятся как в Word (сверка с эталонными OMML); build-up по пробелу — поведение совпадает на 50 сценариях |
| 6 | LaTeX | 400 примеров amsmath/amssymb разбираются; ошибки — локальны; сериализация AST → LaTeX → AST идемпотентна |
| 7 | Производительность | 200 узлов — раскладка < 1 мс; документ с 1000 формул открывается с задержкой < 300 мс на раскладку; ввод символа — обновление < 8 мс |
| 8 | Каретка | все переходы 5.4 покрыты тестами (матрица «позиция × клавиша»), нет
«потерянных» позиций; IME для mtext |
| 9 | Шрифты | STIX Two Math, Latin Modern Math, Cambria Math, Fira Math, Noto Sans Math — раскладка корректна (сборки, варианты), fallback-глифы для 500 символов |
| 10 | Доступность | NVDA/VoiceOver читают 100 формул по сгенерированному тексту; PDF/UA
экспорт с /Formula проходит проверку |
| 11 | Нумерация в Write | 100-страничный документ с 300 формулами — перенумерация и ссылки обновляются < 100 мс; экспорт в docx — Word показывает номера и ссылки |
| 12 | Скрипты | API раздела 6 покрыт тестами; примеры работают |
10. Риски
| Риск | Вероятность | Влияние | Снижение |
|---|---|---|---|
| Расхождения раскладки с TeX/Word (ожидания двух разных групп пользователей) | высокая | среднее | режим интервалов «TeX» (по умолчанию) и «Word/MathML» (настройка документа); корпус сравнений с обоими |
Неоднозначность OMML (линейный текст в m:r,
Word-специфичные m:box/aln) |
высокая | среднее | нормализация при импорте, сохранение src для
round-trip, тесты на реальных документах |
| Объём LaTeX: пользователи ждут «весь LaTeX» | высокая | среднее | чёткое подмножество в документации, локальные ошибки,
\newcommand P1, расширение по статистике неизвестных команд
(телеметрия по согласию) |
| Шрифты: Cambria Math проприетарный; Latin Modern Math (GUST FL) и STIX Two (OFL) — лицензии совместимы, но нужен контроль при встраивании в PDF | низкая | низкое | в комплекте только OFL/GUST; Cambria — если установлен в системе |
| Производительность в Write при тысячах формул | средняя | среднее | кэш раскладки по хэшу, ленивая раскладка вне viewport, параллельность |
| Рукописный ввод (P2): качество модели | средняя | низкое | P2, локальная модель, обратная связь пользователя |
UnicodeMath: различия версий Word (новые команды,
\cases) |
средняя | низкое | ориентир UTN #28 v3.x, тест-корпус от Word 365 |
| Доступность: нет стандартного API для MathML у Qt на всех ОС | средняя | среднее | текст озвучивания в accessibleDescription как резерв;
UIA MathML pattern на Windows — P1 |
11. Вопросы к библии
- Плоский вариант
.meq. Формульные файлы маленькие; для систем контроля версий и буфера удобнее плоский XML. Предлагается допустить.meqf(как.mwxf) либо сделать.meqплоским XML по умолчанию (ZIP — только при наличии миниатюры/нескольких формул). Нужно решение владельца (раздел 3.2 библии). - Шрифты в комплекте. В стеке назван STIX Two; для Math нужны также Latin Modern Math (GUST Font License) и Noto Sans Math (OFL, резерв глифов). Предлагается добавить в раздел 4 библии.
- Общий реестр горячих клавиш
docs/shell/keymap.md(конфликтыCtrl+Shift+E,Ctrl+Shift+Mи др. между приложениями при каретке внутри формулы). - Формулы в Sheets. Отображение формулы ячейки как
математической записи (
=A1/B1→ дробь) и формулы-объекты на листе — требуется решение, входит ли в фазу 2/4 (здесь — P2). - Теги PDF/UA для формул (
/Formula, Associated Files MathML по PDF 2.0) требуют поддержки в Meridian Print/meridian-pdf-write— зафиксировать вdocs/apps/pdf.md(там учтено как P1) и в Print.