Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.WordProcessor/Builders/FormulaHelper.cs
2026-06-05 15:58:03 +07:00

357 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Base = DocumentFormat.OpenXml.Math.Base;
using ControlProperties = DocumentFormat.OpenXml.Math.ControlProperties;
using Degree = DocumentFormat.OpenXml.Math.Degree;
using Denominator = DocumentFormat.OpenXml.Math.Denominator;
using Fraction = DocumentFormat.OpenXml.Math.Fraction;
using FractionProperties = DocumentFormat.OpenXml.Math.FractionProperties;
using HideDegree = DocumentFormat.OpenXml.Math.HideDegree;
using MathRun = DocumentFormat.OpenXml.Math.Run;
using MathRunProperties = DocumentFormat.OpenXml.Math.RunProperties;
using MathText = DocumentFormat.OpenXml.Math.Text;
using Numerator = DocumentFormat.OpenXml.Math.Numerator;
using Radical = DocumentFormat.OpenXml.Math.Radical;
using RadicalProperties = DocumentFormat.OpenXml.Math.RadicalProperties;
using SubArgument = DocumentFormat.OpenXml.Math.SubArgument;
using Subscript = DocumentFormat.OpenXml.Math.Subscript;
using SubSuperscript = DocumentFormat.OpenXml.Math.SubSuperscript;
using SuperArgument = DocumentFormat.OpenXml.Math.SuperArgument;
using Superscript = DocumentFormat.OpenXml.Math.Superscript;
namespace QWERTYkez.WordProcessor.Builders;
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);
}
}
public static Fraction CreateFraction(
Action<IFormula> numeratorBuilder,
Action<IFormula> denominatorBuilder,
IFormula builder)
{
var fraction = new Fraction();
var font = builder.BaseFont;
// Добавляем свойства дроби с форматированием (для черты дроби)
var fPr = new FractionProperties();
if (font.TryExtractWithoutFamily(out var wordElements))
{
var ctrlPr = new ControlProperties();
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);
// Числитель
var numeratorElem = new Numerator();
fraction.AppendChild(numeratorElem);
builder.PushContext(numeratorElem);
numeratorBuilder(builder);
builder.PopContext();
// Знаменатель
var denominatorElem = new Denominator();
fraction.AppendChild(denominatorElem);
builder.PushContext(denominatorElem);
denominatorBuilder(builder);
builder.PopContext();
return fraction;
}
public static Radical CreateRadical(
Action<IFormula> radicandBuilder,
Action<IFormula>? degreeBuilder,
IFormula builder)
{
var radical = new Radical();
var font = builder.BaseFont;
var radPr = new RadicalProperties();
// Цвет, размер и подчёркивание для знака корня (ControlProperties)
if (font is not null && font.TryExtractWithoutFamily(out var wordElements))
{
var ctrlPr = new ControlProperties();
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)
{
radPr.HideDegree = new HideDegree { Val = DocumentFormat.OpenXml.Math.BooleanValues.One };
}
// Добавляем свойства радикала (с цветом)
radical.AppendChild(radPr);
// Степень (если есть)
if (degreeBuilder is not null)
{
var degree = new Degree();
radical.AppendChild(degree);
builder.PushContext(degree);
degreeBuilder(builder);
builder.PopContext();
}
// Подкоренное выражение
var radicand = new Base();
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
{
var integralWithLimits = new SubSuperscript();
currentContext.AppendChild(integralWithLimits);
var baseElem = new Base();
var integralRun = CreateMathRun(font);
integralRun.AppendChild(new MathText("∫"));
baseElem.AppendChild(integralRun);
integralWithLimits.AppendChild(baseElem);
if (lowerLimitBuilder is not null)
{
var subArg = new SubArgument();
integralWithLimits.AppendChild(subArg);
builder.PushContext(subArg);
lowerLimitBuilder(builder);
builder.PopContext();
}
if (upperLimitBuilder is not null)
{
var superArg = new SuperArgument();
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
{
var sumWithLimits = new SubSuperscript();
currentContext.AppendChild(sumWithLimits);
var baseElem = new Base();
var sumRun = CreateMathRun(font);
sumRun.AppendChild(new MathText("∑"));
baseElem.AppendChild(sumRun);
sumWithLimits.AppendChild(baseElem);
if (lowerLimitBuilder is not null)
{
var subArg = new SubArgument();
sumWithLimits.AppendChild(subArg);
builder.PushContext(subArg);
lowerLimitBuilder(builder);
builder.PopContext();
}
if (upperLimitBuilder is not null)
{
var superArg = new SuperArgument();
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>
public static Superscript CreateSuperscript(
Action<IFormula> baseBuilder,
Action<IFormula> supBuilder,
IFormula builder)
{
var superscript = new Superscript();
// Основание
var baseElem = new Base();
superscript.AppendChild(baseElem);
builder.PushContext(baseElem);
baseBuilder(builder);
builder.PopContext();
// Показатель
var superArg = new SuperArgument();
superscript.AppendChild(superArg);
builder.PushContext(superArg);
supBuilder(builder);
builder.PopContext();
return superscript;
}
/// <summary>Создаёт нижний индекс.</summary>
public static Subscript CreateSubscript(
Action<IFormula> baseBuilder,
Action<IFormula> subBuilder,
IFormula builder)
{
var subscript = new Subscript();
var baseElem = new Base();
subscript.AppendChild(baseElem);
builder.PushContext(baseElem);
baseBuilder(builder);
builder.PopContext();
var subArg = new SubArgument();
subscript.AppendChild(subArg);
builder.PushContext(subArg);
subBuilder(builder);
builder.PopContext();
return subscript;
}
/// <summary>Создаёт одновременные нижний и верхний индексы.</summary>
public static SubSuperscript CreateSubSuperscript(
Action<IFormula> baseBuilder,
Action<IFormula> subBuilder,
Action<IFormula> supBuilder,
IFormula builder)
{
var subSup = new SubSuperscript();
var baseElem = new Base();
subSup.AppendChild(baseElem);
builder.PushContext(baseElem);
baseBuilder(builder);
builder.PopContext();
var subArg = new SubArgument();
subSup.AppendChild(subArg);
builder.PushContext(subArg);
subBuilder(builder);
builder.PopContext();
var superArg = new SuperArgument();
subSup.AppendChild(superArg);
builder.PushContext(superArg);
supBuilder(builder);
builder.PopContext();
return subSup;
}
}