2026-06-15 14:37:25 +07:00
|
|
|
|
using MathBase = DocumentFormat.OpenXml.Math.Base;
|
|
|
|
|
|
using MathControlProperties = DocumentFormat.OpenXml.Math.ControlProperties;
|
|
|
|
|
|
using MathDegree = DocumentFormat.OpenXml.Math.Degree;
|
|
|
|
|
|
using MathDenominator = DocumentFormat.OpenXml.Math.Denominator;
|
|
|
|
|
|
using MathFraction = DocumentFormat.OpenXml.Math.Fraction;
|
|
|
|
|
|
using MathFractionProperties = DocumentFormat.OpenXml.Math.FractionProperties;
|
|
|
|
|
|
using MathHideDegree = DocumentFormat.OpenXml.Math.HideDegree;
|
2026-06-05 15:58:03 +07:00
|
|
|
|
using MathRun = DocumentFormat.OpenXml.Math.Run;
|
|
|
|
|
|
using MathRunProperties = DocumentFormat.OpenXml.Math.RunProperties;
|
|
|
|
|
|
using MathText = DocumentFormat.OpenXml.Math.Text;
|
2026-06-15 14:37:25 +07:00
|
|
|
|
using MathNumerator = DocumentFormat.OpenXml.Math.Numerator;
|
|
|
|
|
|
using MathRadical = DocumentFormat.OpenXml.Math.Radical;
|
|
|
|
|
|
using MathRadicalProperties = DocumentFormat.OpenXml.Math.RadicalProperties;
|
|
|
|
|
|
using MathSubArgument = DocumentFormat.OpenXml.Math.SubArgument;
|
|
|
|
|
|
using MathSubscript = DocumentFormat.OpenXml.Math.Subscript;
|
|
|
|
|
|
using MathSubSuperscript = DocumentFormat.OpenXml.Math.SubSuperscript;
|
|
|
|
|
|
using MathSuperArgument = DocumentFormat.OpenXml.Math.SuperArgument;
|
|
|
|
|
|
using MathSuperscript = DocumentFormat.OpenXml.Math.Superscript;
|
2026-06-05 15:58:03 +07:00
|
|
|
|
|
2026-06-08 14:31:31 +07:00
|
|
|
|
namespace QWERTYkez.WordProcessor;
|
2026-06-05 15:58:03 +07:00
|
|
|
|
|
|
|
|
|
|
internal static class FormulaHelper
|
|
|
|
|
|
{
|
|
|
|
|
|
// Вспомогательный метод для создания MathRun с форматированием
|
|
|
|
|
|
public static MathRun CreateMathRun(FontProps? font)
|
|
|
|
|
|
{
|
|
|
|
|
|
var mathRun = new MathRun();
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Математический стиль (полужирный/курсив) – добавляем первым
|
|
|
|
|
|
if (font is not null && font.TryExtractForMath(out var mathStyleElements))
|
|
|
|
|
|
{
|
|
|
|
|
|
var mathPr = new MathRunProperties(mathStyleElements);
|
|
|
|
|
|
mathRun.AppendChild(mathPr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Wordprocessing: цвет, размер, подчёркивание (без семейства шрифта)
|
|
|
|
|
|
var wordPr = new RunProperties();
|
|
|
|
|
|
if (font is not null && font.TryExtractWithoutFamily(out var wordElements))
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var elem in wordElements)
|
|
|
|
|
|
wordPr.AppendChild(elem.CloneNode(true));
|
|
|
|
|
|
}
|
|
|
|
|
|
mathRun.AppendChild(wordPr);
|
|
|
|
|
|
|
|
|
|
|
|
return mathRun;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void AddText(OpenXmlElement parent, string text, FontProps? font)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (parent is MathRun mathRun)
|
|
|
|
|
|
{
|
|
|
|
|
|
mathRun.AppendChild(new MathText(text));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var run = CreateMathRun(font);
|
|
|
|
|
|
run.AppendChild(new MathText(text));
|
|
|
|
|
|
parent.AppendChild(run);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-15 14:37:25 +07:00
|
|
|
|
public static MathFraction CreateFraction(
|
2026-06-05 15:58:03 +07:00
|
|
|
|
Action<IFormula> numeratorBuilder,
|
|
|
|
|
|
Action<IFormula> denominatorBuilder,
|
|
|
|
|
|
IFormula builder)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var fraction = new MathFraction();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
var font = builder.BaseFont;
|
|
|
|
|
|
|
|
|
|
|
|
// Добавляем свойства дроби с форматированием (для черты дроби)
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var fPr = new MathFractionProperties();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
if (font.TryExtractWithoutFamily(out var wordElements))
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var ctrlPr = new MathControlProperties();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
var rPr = new RunProperties();
|
|
|
|
|
|
foreach (var elem in wordElements)
|
|
|
|
|
|
rPr.AppendChild(elem.CloneNode(true));
|
|
|
|
|
|
rPr.AppendChild(new RunFonts { Ascii = "Cambria Math", HighAnsi = "Cambria Math" });
|
|
|
|
|
|
ctrlPr.AppendChild(rPr);
|
|
|
|
|
|
fPr.AppendChild(ctrlPr);
|
|
|
|
|
|
}
|
|
|
|
|
|
fraction.AppendChild(fPr);
|
|
|
|
|
|
|
|
|
|
|
|
// Числитель
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var numeratorElem = new MathNumerator();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
fraction.AppendChild(numeratorElem);
|
|
|
|
|
|
builder.PushContext(numeratorElem);
|
|
|
|
|
|
numeratorBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
|
|
|
|
|
// Знаменатель
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var denominatorElem = new MathDenominator();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
fraction.AppendChild(denominatorElem);
|
|
|
|
|
|
builder.PushContext(denominatorElem);
|
|
|
|
|
|
denominatorBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
|
|
|
|
|
return fraction;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-15 14:37:25 +07:00
|
|
|
|
public static MathRadical CreateRadical(
|
2026-06-05 15:58:03 +07:00
|
|
|
|
Action<IFormula> radicandBuilder,
|
|
|
|
|
|
Action<IFormula>? degreeBuilder,
|
|
|
|
|
|
IFormula builder)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var radical = new MathRadical();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
var font = builder.BaseFont;
|
|
|
|
|
|
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var radPr = new MathRadicalProperties();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
|
|
|
|
|
|
// Цвет, размер и подчёркивание для знака корня (ControlProperties)
|
|
|
|
|
|
if (font is not null && font.TryExtractWithoutFamily(out var wordElements))
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var ctrlPr = new MathControlProperties();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
var rPr = new RunProperties();
|
|
|
|
|
|
foreach (var elem in wordElements)
|
|
|
|
|
|
rPr.AppendChild(elem.CloneNode(true));
|
|
|
|
|
|
rPr.AppendChild(new RunFonts { Ascii = "Cambria Math", HighAnsi = "Cambria Math" });
|
|
|
|
|
|
ctrlPr.AppendChild(rPr);
|
|
|
|
|
|
radPr.AppendChild(ctrlPr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Если степень не задана, скрываем её
|
|
|
|
|
|
if (degreeBuilder is null)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
radPr.HideDegree = new MathHideDegree { Val = DocumentFormat.OpenXml.Math.BooleanValues.One };
|
2026-06-05 15:58:03 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Добавляем свойства радикала (с цветом)
|
|
|
|
|
|
radical.AppendChild(radPr);
|
|
|
|
|
|
|
|
|
|
|
|
// Степень (если есть)
|
|
|
|
|
|
if (degreeBuilder is not null)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var degree = new MathDegree();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
radical.AppendChild(degree);
|
|
|
|
|
|
builder.PushContext(degree);
|
|
|
|
|
|
degreeBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Подкоренное выражение
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var radicand = new MathBase();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
radical.AppendChild(radicand);
|
|
|
|
|
|
builder.PushContext(radicand);
|
|
|
|
|
|
radicandBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
|
|
|
|
|
return radical;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void AddIntegral(
|
|
|
|
|
|
OpenXmlElement currentContext,
|
|
|
|
|
|
Action<IFormula> functionBuilder,
|
|
|
|
|
|
Action<IFormula> differentialBuilder,
|
|
|
|
|
|
Action<IFormula>? lowerLimitBuilder,
|
|
|
|
|
|
Action<IFormula>? upperLimitBuilder,
|
|
|
|
|
|
IFormula builder)
|
|
|
|
|
|
{
|
|
|
|
|
|
var font = builder.BaseFont;
|
|
|
|
|
|
|
|
|
|
|
|
if (lowerLimitBuilder is null && upperLimitBuilder is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var integralRun = CreateMathRun(font);
|
|
|
|
|
|
integralRun.AppendChild(new MathText("∫"));
|
|
|
|
|
|
currentContext.AppendChild(integralRun);
|
|
|
|
|
|
|
|
|
|
|
|
var funcRun = CreateMathRun(font);
|
|
|
|
|
|
currentContext.AppendChild(funcRun);
|
|
|
|
|
|
builder.PushContext(funcRun);
|
|
|
|
|
|
functionBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
|
|
|
|
|
var diffRun = CreateMathRun(font);
|
|
|
|
|
|
currentContext.AppendChild(diffRun);
|
|
|
|
|
|
builder.PushContext(diffRun);
|
|
|
|
|
|
differentialBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var integralWithLimits = new MathSubSuperscript();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
currentContext.AppendChild(integralWithLimits);
|
|
|
|
|
|
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var baseElem = new MathBase();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
var integralRun = CreateMathRun(font);
|
|
|
|
|
|
integralRun.AppendChild(new MathText("∫"));
|
|
|
|
|
|
baseElem.AppendChild(integralRun);
|
|
|
|
|
|
integralWithLimits.AppendChild(baseElem);
|
|
|
|
|
|
|
|
|
|
|
|
if (lowerLimitBuilder is not null)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var subArg = new MathSubArgument();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
integralWithLimits.AppendChild(subArg);
|
|
|
|
|
|
builder.PushContext(subArg);
|
|
|
|
|
|
lowerLimitBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (upperLimitBuilder is not null)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var superArg = new MathSuperArgument();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
integralWithLimits.AppendChild(superArg);
|
|
|
|
|
|
builder.PushContext(superArg);
|
|
|
|
|
|
upperLimitBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var funcRun = CreateMathRun(font);
|
|
|
|
|
|
currentContext.AppendChild(funcRun);
|
|
|
|
|
|
builder.PushContext(funcRun);
|
|
|
|
|
|
functionBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
|
|
|
|
|
var diffRun = CreateMathRun(font);
|
|
|
|
|
|
currentContext.AppendChild(diffRun);
|
|
|
|
|
|
builder.PushContext(diffRun);
|
|
|
|
|
|
differentialBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void AddSum(
|
|
|
|
|
|
OpenXmlElement currentContext,
|
|
|
|
|
|
Action<IFormula> expressionBuilder,
|
|
|
|
|
|
Action<IFormula>? lowerLimitBuilder,
|
|
|
|
|
|
Action<IFormula>? upperLimitBuilder,
|
|
|
|
|
|
IFormula builder)
|
|
|
|
|
|
{
|
|
|
|
|
|
var font = builder.BaseFont;
|
|
|
|
|
|
|
|
|
|
|
|
if (lowerLimitBuilder is null && upperLimitBuilder is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var sumRun = CreateMathRun(font);
|
|
|
|
|
|
sumRun.AppendChild(new MathText("∑"));
|
|
|
|
|
|
currentContext.AppendChild(sumRun);
|
|
|
|
|
|
|
|
|
|
|
|
var exprRun = CreateMathRun(font);
|
|
|
|
|
|
currentContext.AppendChild(exprRun);
|
|
|
|
|
|
builder.PushContext(exprRun);
|
|
|
|
|
|
expressionBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var sumWithLimits = new MathSubSuperscript();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
currentContext.AppendChild(sumWithLimits);
|
|
|
|
|
|
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var baseElem = new MathBase();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
var sumRun = CreateMathRun(font);
|
|
|
|
|
|
sumRun.AppendChild(new MathText("∑"));
|
|
|
|
|
|
baseElem.AppendChild(sumRun);
|
|
|
|
|
|
sumWithLimits.AppendChild(baseElem);
|
|
|
|
|
|
|
|
|
|
|
|
if (lowerLimitBuilder is not null)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var subArg = new MathSubArgument();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
sumWithLimits.AppendChild(subArg);
|
|
|
|
|
|
builder.PushContext(subArg);
|
|
|
|
|
|
lowerLimitBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (upperLimitBuilder is not null)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var superArg = new MathSuperArgument();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
sumWithLimits.AppendChild(superArg);
|
|
|
|
|
|
builder.PushContext(superArg);
|
|
|
|
|
|
upperLimitBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var exprRun = CreateMathRun(font);
|
|
|
|
|
|
currentContext.AppendChild(exprRun);
|
|
|
|
|
|
builder.PushContext(exprRun);
|
|
|
|
|
|
expressionBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Создаёт степень.</summary>
|
2026-06-15 14:37:25 +07:00
|
|
|
|
public static MathSuperscript CreateSuperscript(
|
2026-06-05 15:58:03 +07:00
|
|
|
|
Action<IFormula> baseBuilder,
|
|
|
|
|
|
Action<IFormula> supBuilder,
|
|
|
|
|
|
IFormula builder)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var superscript = new MathSuperscript();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
|
|
|
|
|
|
// Основание
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var baseElem = new MathBase();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
superscript.AppendChild(baseElem);
|
|
|
|
|
|
builder.PushContext(baseElem);
|
|
|
|
|
|
baseBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
|
|
|
|
|
// Показатель
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var superArg = new MathSuperArgument();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
superscript.AppendChild(superArg);
|
|
|
|
|
|
builder.PushContext(superArg);
|
|
|
|
|
|
supBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
|
|
|
|
|
return superscript;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Создаёт нижний индекс.</summary>
|
2026-06-15 14:37:25 +07:00
|
|
|
|
public static MathSubscript CreateSubscript(
|
2026-06-05 15:58:03 +07:00
|
|
|
|
Action<IFormula> baseBuilder,
|
|
|
|
|
|
Action<IFormula> subBuilder,
|
|
|
|
|
|
IFormula builder)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var subscript = new MathSubscript();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var baseElem = new MathBase();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
subscript.AppendChild(baseElem);
|
|
|
|
|
|
builder.PushContext(baseElem);
|
|
|
|
|
|
baseBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var subArg = new MathSubArgument();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
subscript.AppendChild(subArg);
|
|
|
|
|
|
builder.PushContext(subArg);
|
|
|
|
|
|
subBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
|
|
|
|
|
return subscript;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Создаёт одновременные нижний и верхний индексы.</summary>
|
2026-06-15 14:37:25 +07:00
|
|
|
|
public static MathSubSuperscript CreateSubSuperscript(
|
2026-06-05 15:58:03 +07:00
|
|
|
|
Action<IFormula> baseBuilder,
|
|
|
|
|
|
Action<IFormula> subBuilder,
|
|
|
|
|
|
Action<IFormula> supBuilder,
|
|
|
|
|
|
IFormula builder)
|
|
|
|
|
|
{
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var subSup = new MathSubSuperscript();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var baseElem = new MathBase();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
subSup.AppendChild(baseElem);
|
|
|
|
|
|
builder.PushContext(baseElem);
|
|
|
|
|
|
baseBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var subArg = new MathSubArgument();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
subSup.AppendChild(subArg);
|
|
|
|
|
|
builder.PushContext(subArg);
|
|
|
|
|
|
subBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
2026-06-15 14:37:25 +07:00
|
|
|
|
var superArg = new MathSuperArgument();
|
2026-06-05 15:58:03 +07:00
|
|
|
|
subSup.AppendChild(superArg);
|
|
|
|
|
|
builder.PushContext(superArg);
|
|
|
|
|
|
supBuilder(builder);
|
|
|
|
|
|
builder.PopContext();
|
|
|
|
|
|
|
|
|
|
|
|
return subSup;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|