Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/Format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,14 @@ jobs:
- uses: actions/checkout@v7
with:
submodules: recursive
- name: Remove Uno.Example from solution
# CSharpMath.Uno.Example must be removed rather than excluded: dotnet format's
# --exclude only skips formatting analysis, but the project is still restored
# and its Uno.Resizetizer splash-screen task crashes inside dotnet format due
# to a SkiaSharp assembly-version conflict (the tool hosts SkiaSharp 2.88
# while Uno.Resizetizer requires 3.x / native libSkiaSharp 116.0).
shell: bash
run:
dotnet sln CSharpMath.slnx remove CSharpMath.Uno.Example/CSharpMath.Uno.Example.csproj
- name: Check formatting (Fix with "dotnet format --exclude Typography" at repository root)
run: dotnet format --exclude Typography --verify-no-changes --verbosity diagnostic
8 changes: 8 additions & 0 deletions CSharpMath.Core.Example/BackEnd/JsonMathTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ public override float LowerLimitGapMin(TFont font) =>

public override float LowerLimitBaselineDropMin(TFont font) =>
ConstantFromTable(font, nameof(LowerLimitBaselineDropMin));
public override float StretchStackTopShiftUp(TFont font) =>
ConstantFromTable(font, nameof(StretchStackTopShiftUp));
public override float StretchStackBottomShiftDown(TFont font) =>
ConstantFromTable(font, nameof(StretchStackBottomShiftDown));
public override float StretchStackGapAboveMin(TFont font) =>
ConstantFromTable(font, nameof(StretchStackGapAboveMin));
public override float StretchStackGapBelowMin(TFont font) =>
ConstantFromTable(font, nameof(StretchStackGapBelowMin));
#region overline/underline
public override float UnderbarVerticalGap(TFont font) =>
ConstantFromTable(font, nameof(UnderbarVerticalGap));
Expand Down
554 changes: 554 additions & 0 deletions CSharpMath.Core.Tests/Atom/IosMath2026PortTests.cs

Large diffs are not rendered by default.

247 changes: 154 additions & 93 deletions CSharpMath.Core.Tests/Atom/LaTeXParserTest.cs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions CSharpMath.Core.Tests/Atom/MathAtomTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public void TestAtomInit() {
.DefinedTypes
.Where(t => t.Namespace == typeof(Accent).Namespace && t.GetCustomAttributes(
typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), false).Length == 0)
// Only atoms themselves; helper value types (e.g. StackConstruction rows) are not MathAtoms.
.Where(t => typeof(MathAtom).IsAssignableFrom(t.AsType()))
.SelectMany(t =>
new[] { t.GetConstructor(new System.Type[0]), t.GetConstructor(new[] { typeof(string) }) })
.Where(c => c != null)
Expand Down
32 changes: 20 additions & 12 deletions CSharpMath.Core.Tests/Editor/PointForIndexTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,26 @@ public void RadicalDegree(PointF point, MathListIndex expected) =>
};
[Theory, MemberData(nameof(ExponentData))]
public void Exponent(PointF point, MathListIndex expected) => Test("2^3", point, expected);
public static TestData ExponentsData =>
new TestData {
{ (0, 0), 0 },
{ (10, 0), 0, (SubIndex.BetweenBaseAndScripts, 1) },
{ (10, -4.94), 0, (SubIndex.Subscript, 0) },
{ (17, -4.94), 0, (SubIndex.Subscript, 1) },
{ (18.12, 0), 1 },
{ (28.12, 0), 1, (SubIndex.BetweenBaseAndScripts, 1) },
{ (28.12, -4.94), 1, (SubIndex.Subscript, 0) },
{ (35.12, -4.94), 1, (SubIndex.Subscript, 1) },
{ (35.12, 0), 2 },
};
public static TestData ExponentsData {
get {
// {2_3}{3_2}: braces are Ord groups now — the groups render transparently
// (their displays are spliced into the parent), but atoms inside a group do
// not fuse across the brace boundary, so each glyph carries its own italic
// correction and the pen advances by 1.12pt per boundary.
var data = new TestData {
{ (0, 0), 0 },
{ (10, 0), 0, (SubIndex.BetweenBaseAndScripts, 1) },
{ (10, -4.94), 0, (SubIndex.Subscript, 0) },
{ (17, -4.94), 0, (SubIndex.Subscript, 1) },
{ (17, 0), 1 },
{ (27, 0), 1, (SubIndex.BetweenBaseAndScripts, 1) },
{ (27, -4.94), 1, (SubIndex.Subscript, 0) },
{ (34, -4.94), 1, (SubIndex.Subscript, 1) },
{ (34, 0), 2 },
};
return data;
}
}
[Theory, MemberData(nameof(ExponentsData))]
public void Subscripts(PointF point, MathListIndex expected) => Test("{2_3}{3_2}", point, expected);

Expand Down
53 changes: 51 additions & 2 deletions CSharpMath.Evaluation/Evaluation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,46 @@ public IEnumerator<MathItem> GetEnumerator() {
}
public static MathList Visualize(MathItem entity) =>
LaTeXParser.MathListFromLaTeX(entity.Latexise())
// CSharpMath must handle all LaTeX coming from AngouriMath or a bug is present!
.Match(list => list, e => throw new InvalidCodePathException(e));
// AngouriMath's Latexise emits braces around every compound ({\sin x}^{1});
// dissolve them so the visual output stays identical to the pre-grouping
// parser. Grouping remains available for user-authored LaTeX.
.Match(list => DissolveGroups(list.Clone(true)), e => throw new InvalidCodePathException(e));

/// <summary>Recursively removes Group wrappers: their content is spliced inline.
/// Scripts attached to a group are hoisted onto its last inner atom.</summary>
static MathList DissolveGroups(MathList list) {
var result = new MathList();
foreach (var atom in list) {
if (atom is Atoms.Group group) {
var inner = DissolveGroups(group.InnerList);
if ((group.Superscript.IsNonEmpty() || group.Subscript.IsNonEmpty())
&& inner.LastOrDefault() is { } last && last.ScriptsAllowed) {
last.Superscript.Append(DissolveGroups(group.Superscript));
last.Subscript.Append(DissolveGroups(group.Subscript));
}
result.Append(inner);
} else {
if (atom.Superscript.IsNonEmpty()) {
var superscript = DissolveGroups(atom.Superscript);
atom.Superscript.Clear();
atom.Superscript.Append(superscript);
}
if (atom.Subscript.IsNonEmpty()) {
var subscript = DissolveGroups(atom.Subscript);
atom.Subscript.Clear();
atom.Subscript.Append(subscript);
}
if (atom is IMathListContainer container)
foreach (var subList in container.InnerLists) {
var flattened = DissolveGroups(subList);
subList.Clear();
subList.Append(flattened);
}
result.Add(atom);
}
}
return result;
}
public static Result<MathItem> Evaluate(MathList mathList) =>
Transform(mathList.Clone(true)).Bind(result => result is { } r ? Result.Ok(r) : Result.Err("There is nothing to evaluate"));
static Result<MathItem?> Transform(MathList mathList) {
Expand Down Expand Up @@ -208,6 +246,17 @@ Result HandleSuperscript(ref MathItem? @this, ref int i, MathList superscript) {
switch (atom) {
case Atoms.Placeholder _:
return "Placeholders should be filled";
// A brace group is an Ord subformula: evaluate its content in place and
// splice the result so grouping stays transparent (iosMath 086d345).
case Atoms.Group group: {
var innerList = new MathList(group.InnerList.Select(a => a.Clone(true)));
int j = 0;
var (groupResult, groupError) = Transform(innerList, ref j, prec).Bind(r => r is { } item ? Result.Ok(item) : Result.Err("Empty group"));
if (groupError != null) return groupError;
@this = groupResult;
subscriptAllowed = true;
goto handleThis;
}
case Atoms.Number { Subscript: [Atoms.Number numericBase] } n:
if (int.TryParse(numericBase.Nucleus, out var @base)) {
try { @this = MathS.FromBaseN(atom.Nucleus, @base); } catch (Exception e) { return e.Message; }
Expand Down
Binary file modified CSharpMath.Rendering.Tests/MathDisplay/Abs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/MathDisplay/Cases.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/MathDisplay/ItalicAlignment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/MathDisplay/Logic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/MathDisplay/SolveEquations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/MathInline/Abs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/MathInline/Cases.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/MathInline/ItalicAlignment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/MathInline/Logic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/MathInline/SolveEquations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions CSharpMath.Rendering.Tests/TestRenderingMathData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public sealed class TestRenderingMathData : TestRenderingSharedData<TestRenderin
public const string IntegralScripts = @"\int\int\int^{\infty}\int_0\int^{\infty}_0\int";
public const string ItalicAlignment = @"\colorbox{yellow}P\\\begin{array}{r}\colorbox{yellow}{PF}\\\colorbox{yellow}F\end{array}";
public const string ItalicScripts = @"U_3^2UY_3^2U_3Y^2f_1f^2ff";
// iosMath c9afaad: the correction of a script base must come from the face that
// drew its nucleus; \mathit digits keep their upright face, so 1^2 must not shift.
public const string ItalicCorrectionStyles = @"1\mathit{2}^3 x_\ell \mathrm{P}^2 \mathbf{f}_1";
// Ruled arrays must honour the column alignment spec: cells sit at shared
// offsets, then r/c/l shifts are applied within the full column width.
public const string RuledArrayAlignment = @"\begin{array}{|r|c|} 1 & 2 \\ 1000 & x \end{array}";

public const string KetSum = @"\frac{1}{\sqrt{2^n}} \sum_{i=0}^{2^n-1} \Ket{i}";

Expand Down
Binary file modified CSharpMath.Rendering.Tests/TextCenter/IntegrationByParts.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/TextLeft/IntegrationByParts.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified CSharpMath.Rendering.Tests/TextRight/IntegrationByParts.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions CSharpMath.Rendering/BackEnd/MathTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ public override float MinConnectorOverlap(Fonts fonts) =>
public override float SuperscriptShiftUpCramped(Fonts fonts) => ReadRecord(fonts.MathConsts.SuperscriptShiftUpCramped, fonts);
public override float UpperLimitBaselineRiseMin(Fonts fonts) => ReadRecord(fonts.MathConsts.UpperLimitBaselineRiseMin, fonts);
public override float UpperLimitGapMin(Fonts fonts) => ReadRecord(fonts.MathConsts.UpperLimitGapMin, fonts);
public override float StretchStackTopShiftUp(Fonts fonts) => ReadRecord(fonts.MathConsts.StretchStackTopShiftUp, fonts);
public override float StretchStackBottomShiftDown(Fonts fonts) => ReadRecord(fonts.MathConsts.StretchStackBottomShiftDown, fonts);
public override float StretchStackGapAboveMin(Fonts fonts) => ReadRecord(fonts.MathConsts.StretchStackGapAboveMin, fonts);
public override float StretchStackGapBelowMin(Fonts fonts) => ReadRecord(fonts.MathConsts.StretchStackGapBelowMin, fonts);
public override float UnderbarVerticalGap(Fonts fonts) => ReadRecord(fonts.MathConsts.UnderbarVerticalGap, fonts);
public override float AccentBaseHeight(Fonts fonts) => ReadRecord(fonts.MathConsts.AccentBaseHeight, fonts);
public override float GetTopAccentAdjustment(Fonts fonts, Glyph glyph) =>
Expand Down
4 changes: 4 additions & 0 deletions CSharpMath.Rendering/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@
override CSharpMath.Rendering.BackEnd.MathTable.StackGapMin(CSharpMath.Rendering.BackEnd.Fonts! fonts) -> float
override CSharpMath.Rendering.BackEnd.MathTable.StackTopDisplayStyleShiftUp(CSharpMath.Rendering.BackEnd.Fonts! fonts) -> float
override CSharpMath.Rendering.BackEnd.MathTable.StackTopShiftUp(CSharpMath.Rendering.BackEnd.Fonts! fonts) -> float
override CSharpMath.Rendering.BackEnd.MathTable.StretchStackBottomShiftDown(CSharpMath.Rendering.BackEnd.Fonts! fonts) -> float
override CSharpMath.Rendering.BackEnd.MathTable.StretchStackGapAboveMin(CSharpMath.Rendering.BackEnd.Fonts! fonts) -> float
override CSharpMath.Rendering.BackEnd.MathTable.StretchStackGapBelowMin(CSharpMath.Rendering.BackEnd.Fonts! fonts) -> float
override CSharpMath.Rendering.BackEnd.MathTable.StretchStackTopShiftUp(CSharpMath.Rendering.BackEnd.Fonts! fonts) -> float
override CSharpMath.Rendering.BackEnd.MathTable.SubscriptBaselineDropMin(CSharpMath.Rendering.BackEnd.Fonts! fonts) -> float
override CSharpMath.Rendering.BackEnd.MathTable.SubscriptShiftDown(CSharpMath.Rendering.BackEnd.Fonts! fonts) -> float
override CSharpMath.Rendering.BackEnd.MathTable.SubscriptTopMax(CSharpMath.Rendering.BackEnd.Fonts! fonts) -> float
Expand Down Expand Up @@ -395,11 +399,11 @@
override CSharpMath.Rendering.Text.TextAtom.Text.Equals(CSharpMath.Rendering.Text.TextAtom! atom) -> bool
override CSharpMath.Rendering.Text.TextAtom.Text.GetHashCode() -> int
override CSharpMath.Rendering.Text.TextAtom.Text.SingleChar(CSharpMath.Atom.FontStyle style) -> int?
override Typography.OpenFont.MathGlyphs.MathGlyphVariantRecord.ToString() -> string!

Check warning on line 402 in CSharpMath.Rendering/PublicAPI.Unshipped.txt

View workflow job for this annotation

GitHub Actions / Windows

Symbol 'override Typography.OpenFont.MathGlyphs.MathGlyphVariantRecord.ToString() -> string!' is part of the declared API, but is either not public or could not be found (https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
override Typography.OpenFont.MathGlyphs.MathKern.ToString() -> string!

Check warning on line 403 in CSharpMath.Rendering/PublicAPI.Unshipped.txt

View workflow job for this annotation

GitHub Actions / Windows

Symbol 'override Typography.OpenFont.MathGlyphs.MathKern.ToString() -> string!' is part of the declared API, but is either not public or could not be found (https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
override Typography.OpenFont.MathGlyphs.MathValueRecord.ToString() -> string!

Check warning on line 404 in CSharpMath.Rendering/PublicAPI.Unshipped.txt

View workflow job for this annotation

GitHub Actions / Windows

Symbol 'override Typography.OpenFont.MathGlyphs.MathValueRecord.ToString() -> string!' is part of the declared API, but is either not public or could not be found (https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
override Typography.OpenFont.PreviewFontInfo.ToString() -> string!

Check warning on line 405 in CSharpMath.Rendering/PublicAPI.Unshipped.txt

View workflow job for this annotation

GitHub Actions / Windows

Symbol 'override Typography.OpenFont.PreviewFontInfo.ToString() -> string!' is part of the declared API, but is either not public or could not be found (https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
override Typography.OpenFont.ScriptLang.ToString() -> string!

Check warning on line 406 in CSharpMath.Rendering/PublicAPI.Unshipped.txt

View workflow job for this annotation

GitHub Actions / Windows

Symbol 'override Typography.OpenFont.ScriptLang.ToString() -> string!' is part of the declared API, but is either not public or could not be found (https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
override Typography.OpenFont.ScriptLangInfo.ToString() -> string!
override Typography.OpenFont.ScriptTagDef.ToString() -> string!
override Typography.OpenFont.Tables.BASE.AxisTable.ToString() -> string!
Expand Down
12 changes: 12 additions & 0 deletions CSharpMath.Uno.Example/CSharpMath.Uno.Example.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,19 @@
https://aka.platform.uno/singleproject-features
-->
<UnoFeatures>SkiaRenderer</UnoFeatures>

<!--
Override vulnerable transitive dependencies pulled in by the Uno.Sdk
(NU1903 high-severity audit findings fail `dotnet format`'s msbuild restore).
System.Security.Cryptography.Xml 10.0.10 is the patched version Uno itself
bumped to (unoplatform/uno#23830).
-->
<NuGetAuditLevel>high</NuGetAuditLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Security.Cryptography.Xml" Version="10.0.10" />
<PackageReference Include="Tmds.DBus.Protocol" Version="0.21.3" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\CSharpMath.Rendering.Tests\TestRenderingMathData.cs" Link="Examples\TestRenderingMathData.cs" />
<Compile Include="..\CSharpMath.Rendering.Tests\TestRenderingSharedData.cs" Link="Examples\TestRenderingSharedData.cs" />
Expand Down
43 changes: 43 additions & 0 deletions CSharpMath/Atom/Atoms/Box.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace CSharpMath.Atom.Atoms {
/// <summary>Horizontal alignment of a box's child relative to the box origin
/// (drives the lap draw offset).</summary>
public enum BoxHAlign { Left, Center, Right }
/// <summary>Overlay strike drawn across an otherwise-unchanged box:
/// the cancel/sout family.</summary>
public enum StrikeStyle { None, Forward, Backward, Cross, Horizontal }

/// <summary>A box atom: the phantom/smash/lap family plus the cancel family.
/// The content lives in <see cref="InnerList"/>; the flags below select the variant.</summary>
public sealed class Box : MathAtom, IMathListContainer {
public MathList InnerList { get; set; } = new MathList();
/// <summary>Report the child's width (true) or zero width (false).</summary>
public bool KeepWidth { get; set; } = true;
/// <summary>Report the child's ascent (true) or zero ascent (false).</summary>
public bool KeepHeight { get; set; } = true;
/// <summary>Report the child's descent (true) or zero descent (false).</summary>
public bool KeepDepth { get; set; } = true;
/// <summary>Draw the measured child (true) or suppress drawing entirely (false, phantom).</summary>
public bool DrawChild { get; set; } = true;
/// <summary>Horizontal draw offset applied when KeepWidth == false (laps).</summary>
public BoxHAlign HAlign { get; set; }
/// <summary>Overlay strike drawn across the box; None (default) = no strike.</summary>
public StrikeStyle StrikeStyle { get; set; }
public override bool ScriptsAllowed => true;
System.Collections.Generic.IEnumerable<MathList> IMathListContainer.InnerLists =>
new[] { InnerList };
public new Box Clone(bool finalize) => (Box)base.Clone(finalize);
protected override MathAtom CloneInside(bool finalize) => new Box {
InnerList = InnerList.Clone(finalize),
KeepWidth = KeepWidth,
KeepHeight = KeepHeight,
KeepDepth = KeepDepth,
DrawChild = DrawChild,
HAlign = HAlign,
StrikeStyle = StrikeStyle
};
public override string DebugString =>
new System.Text.StringBuilder(@"\box")
.AppendInBracesOrEmptyBraces(InnerList.DebugString)
.AppendDebugStringOfScripts(this).ToString();
}
}
16 changes: 15 additions & 1 deletion CSharpMath/Atom/Atoms/Fraction.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Text;

namespace CSharpMath.Atom.Atoms {
public enum FractionStyle { Auto, Display, Text }
public enum FractionAlignment { Center, Left, Right }

public sealed class Fraction : MathAtom, IMathListContainer {
public MathList Numerator { get; }
public MathList Denominator { get; }
Expand All @@ -10,14 +13,25 @@ public sealed class Fraction : MathAtom, IMathListContainer {
public Boundary RightDelimiter { get; set; }
/// <summary>In this context, a "rule" is a fraction line.</summary>
public bool HasRule { get; }
/// <summary>Explicit style override: \dfrac/\dbinom/\cfrac force display,
/// \tfrac/\tbinom force text. Auto honors the surrounding style (Rule 15a).</summary>
public FractionStyle StyleOverride { get; set; } = FractionStyle.Auto;
/// <summary>True for \cfrac: operands are typeset in display style with struts
/// and the whole fraction is wrapped in surrounding 3mu thin space.</summary>
public bool IsContinuedFraction { get; set; }
/// <summary>Numerator alignment within max(numWidth, denWidth). Only \cfrac[l]/[r] sets a non-default value.</summary>
public FractionAlignment NumeratorAlignment { get; set; } = FractionAlignment.Center;
public Fraction(MathList numerator, MathList denominator, bool hasRule = true) =>
(Numerator, Denominator, HasRule) = (numerator, denominator, hasRule);
public override bool ScriptsAllowed => true;
public new Fraction Clone(bool finalize) => (Fraction)base.Clone(finalize);
protected override MathAtom CloneInside(bool finalize) =>
new Fraction(Numerator.Clone(finalize), Denominator.Clone(finalize), HasRule) {
LeftDelimiter = LeftDelimiter,
RightDelimiter = RightDelimiter
RightDelimiter = RightDelimiter,
StyleOverride = StyleOverride,
IsContinuedFraction = IsContinuedFraction,
NumeratorAlignment = NumeratorAlignment
};
public override string DebugString =>
new StringBuilder(HasRule ? @"\frac" : @"\atop")
Expand Down
30 changes: 30 additions & 0 deletions CSharpMath/Atom/Atoms/Group.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace CSharpMath.Atom.Atoms {
/// <summary>A brace group `{…}` in math mode — an Ord subformula. Script-capable;
/// spaced as Ordinary. The grouped content lives in <see cref="InnerList"/>, and the
/// sub/superscript fields drive scripting of the whole group.</summary>
public sealed class Group : MathAtom, IMathListContainer {
public MathList InnerList { get; set; } = new MathList();
public override bool ScriptsAllowed => true;
System.Collections.Generic.IEnumerable<MathList> IMathListContainer.InnerLists =>
new[] { InnerList };
public new Group Clone(bool finalize) => (Group)base.Clone(finalize);
protected override MathAtom CloneInside(bool finalize) => new Group {
InnerList = InnerList.Clone(finalize)
};
/// <summary>Offsets every atom range of the inner list so group contents carry
/// global indices starting at <paramref name="startIndex"/> (finalization only).</summary>
internal void OffsetInnerRanges(int startIndex) {
foreach (var atom in InnerList)
if (atom.IndexRange != Range.Zero && atom.IndexRange.Location < startIndex)
atom.IndexRange = new Range(atom.IndexRange.Location + startIndex, atom.IndexRange.Length);
// Recurse into nested containers.
foreach (var atom in InnerList)
if (atom is Group nested) nested.OffsetInnerRanges(startIndex);
}
public override string DebugString =>
new System.Text.StringBuilder()
.AppendInBracesOrEmptyBraces(InnerList.DebugString)
.AppendDebugStringOfScripts(this).ToString();
}
}

Loading
Loading