diff --git a/frontend/js/trainer/_trainer_engine.js b/frontend/js/trainer/_trainer_engine.js index f362901..e0c831a 100644 --- a/frontend/js/trainer/_trainer_engine.js +++ b/frontend/js/trainer/_trainer_engine.js @@ -226,7 +226,11 @@ if (_isNeg(node.a)) return '-' + _latex({ k: 'bin', op: '*', a: _negate(node.a), b: node.b }); // -5*x -> «-5x» // · между числами и числовыми множителями (2·3, 2·7²); иначе соседство (2x, 3(x+1)) var sep = ((node.b.k === 'num' && node.b.v >= 0) || (_isNumFactor(node.a) && _isNumFactor(node.b))) ? ' \\cdot ' : ''; - return _mulOperand(node.a) + sep + _mulOperand(node.b); + var la = _mulOperand(node.a), lb = _mulOperand(node.b); + // буквенная команда (\pi, \tau) или переменная слева, буква справа → тонкий пробел, + // иначе склейка даёт «\pir» (KaTeX не знает такой команды). Цифру слева (2x) не трогаем. + if (!sep && /[A-Za-z]$/.test(la) && /^[A-Za-z]/.test(lb)) sep = '\\,'; + return la + sep + lb; } if (op === '%') return _wrapL(node.a, 2) + ' \\bmod ' + _wrapL(node.b, 3); // + или - (схлопываем a + (-b) -> a - b и a - (-b) -> a + b) diff --git a/frontend/trainer.html b/frontend/trainer.html index c3ae587..4315c10 100644 --- a/frontend/trainer.html +++ b/frontend/trainer.html @@ -623,15 +623,21 @@ // (через exprToLatex), остальное экранируется. Так формулы видны и в текст-условиях // (проценты/дроби/verify/choice), где нет единого latex уравнения. // Возврат «косметики» prettyMath к виду, понятному SimExpr-парсеру. + // Приведение «красивой» математики к виду, понятному SimExpr-парсеру: + // ·×→*, −→-, ÷→/, ²³→^2^3, √→sqrt, π→pi (с явным умножением к соседям: 2πr → 2*pi*r). function _unpretty(s) { - return String(s).replace(/[·×]/g, '*').replace(/−/g, '-').replace(/÷/g, '/').replace(/²/g, '^2').replace(/³/g, '^3'); + s = String(s).replace(/[·×]/g, '*').replace(/÷/g, '/').replace(/−/g, '-') + .replace(/²/g, '^2').replace(/³/g, '^3').replace(/√/g, 'sqrt'); + s = s.replace(/π/g, '@P@'); + s = s.replace(/([0-9A-Za-z)\]])\s*@P@/g, '$1*@P@').replace(/@P@\s*([0-9A-Za-z(])/g, '@P@*$1'); + return s.replace(/@P@/g, 'pi'); } // Один математический фрагмент → KaTeX-html или null (если не математика / не разобрался). function _mathRun(run) { var suffix = '', core = run, t = core.match(/(%|°)$/); if (t) { suffix = (t[1] === '%') ? '\\%' : '^{\\circ}'; core = core.slice(0, -1); } - // рендерим только если есть оператор/степень/дробь/проценты-градусы (одиночные числа/слова — текстом) - var worth = /[\/^=<>+\-−·×÷√]|[0-9][A-Za-z]|²|³/.test(run) || suffix; + // рендерим только если есть оператор/степень/дробь/π/проценты-градусы (одиночные числа/слова — текстом) + var worth = /[\/^=<>+\-−·×÷√π]|[0-9][A-Za-z]|²|³/.test(run) || suffix; if (!worth) return null; core = _unpretty(core); if (!/[0-9A-Za-z]/.test(core)) return null; @@ -639,9 +645,9 @@ if (lx == null) return null; return kat(lx + suffix, false); } - // Авто-рендер математики в прозе: латино-цифровые «прогоны» → KaTeX, кириллица/слова — текст. + // Авто-рендер математики в прозе: латино-цифровые «прогоны» (вкл. π) → KaTeX, кириллица/слова — текст. function _autoProse(s) { - var out = '', re = /[0-9A-Za-z(][\w ().,^*\/+\-=<>≤≥≠·×÷√²³°%]*/g, last = 0, m; + var out = '', re = /[0-9A-Za-zπ(][\w ().,^*\/+\-=<>≤≥≠·×÷√²³°%π]*/g, last = 0, m; while ((m = re.exec(s))) { out += esc(s.slice(last, m.index)); var run = m[0], tail = '', tm = run.match(/[ ,.;:]+$/); @@ -1057,11 +1063,12 @@ function stepHtml(st, n) { if (!st) return ''; var num = '' + n + ''; - var note = st.note ? '' + num + esc(st.note) + '' + // пояснение: формулы внутри прозы (S = πr², дроби, степени, π·r…) тоже рендерятся в KaTeX + var note = st.note ? '' + num + renderMixed(st.note) + '' : '' + num + ''; var math = ''; - if (st.latex) { var h = kat(st.latex, false); math = '' + (h || esc(st.tex || '')) + ''; } - else if (st.tex) { math = '' + esc(st.tex) + ''; } + if (st.latex) { var h = kat(st.latex, false); math = '' + (h || renderMixed(st.tex || '')) + ''; } + else if (st.tex) { math = '' + renderMixed(st.tex) + ''; } return '
' + note + math + '
'; }