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
5 changes: 2 additions & 3 deletions CSharpMath.Core.Tests/Atom/MathListTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,8 @@ static void CheckListContents(MathList? list) {
CheckAtomNucleusAndRange<Fraction>("", 13, 1),
CheckAtomNucleusAndRange<LargeOperator>("∫", 14, 1),
CheckAtomNucleusAndRange<Variable>("θ", 15, 1),
// Comments are not given ranges as they won't affect typesetting
CheckAtomNucleusAndRange<Comment>(":)", Range.UndefinedInt, Range.UndefinedInt),
CheckAtomNucleusAndRange<Punctuation>(",", 16, 1)
CheckAtomNucleusAndRange<Comment>(":)", 16, 1),
CheckAtomNucleusAndRange<Punctuation>(",", 17, 1)
);
Assert.Collection(list.Atoms[2].Superscript,
CheckAtomNucleusAndRange<Number>("13", 0, 2),
Expand Down
101 changes: 92 additions & 9 deletions CSharpMath.Core.Tests/Display/TypesetterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ internal static ListDisplay<TFont, TGlyph> ParseLaTeXToDisplay(string latex) =>
private static readonly TFont _font = new TFont(20);
private static readonly TypesettingContext<TFont, TGlyph> _context = BackEnd.TestTypesettingContext.Instance;

System.Action<IDisplay<TFont, TGlyph>?> TestList(int rangeMax, double ascent, double descent, double width, double x, double y,
static System.Action<IDisplay<TFont, TGlyph>?> TestList((int, int) range, double ascent, double descent, double width, double x, double y,
LinePosition linePos, int indexInParent, params System.Action<IDisplay<TFont, TGlyph>>[] inspectors) => d => {
var list = Assert.IsType<ListDisplay<TFont, TGlyph>>(d);
Assert.False(list.HasScript);
Assert.Equal(new Range(0, rangeMax), list.Range);
Assert.Equal(new Range(range.Item1, range.Item2), list.Range);
Approximately.Equal(ascent, list.Ascent);
Approximately.Equal(descent, list.Descent);
Approximately.Equal(width, list.Width);
Expand All @@ -29,10 +29,17 @@ internal static ListDisplay<TFont, TGlyph> ParseLaTeXToDisplay(string latex) =>
Assert.Equal(indexInParent, list.IndexInParent);
Assert.Collection(list.Displays, inspectors);
};
void TestOuter(string latex, int rangeMax, double ascent, double descent, double width,
static System.Action<IDisplay<TFont, TGlyph>?> TestList(int rangeMax, double ascent, double descent, double width, double x, double y,
LinePosition linePos, int indexInParent, params System.Action<IDisplay<TFont, TGlyph>>[] inspectors) =>
TestList((0, rangeMax), ascent, descent, width, x, y, linePos, indexInParent, inspectors);
static void TestOuter(string latex, int rangeMax, double ascent, double descent, double width,
params System.Action<IDisplay<TFont, TGlyph>>[] inspectors) =>
TestList(rangeMax, ascent, descent, width, 0, 0, LinePosition.Regular, Range.UndefinedInt, inspectors)
(ParseLaTeXToDisplay(latex));
static void TestOuter(string latex, (int, int) range, double ascent, double descent, double width,
params System.Action<IDisplay<TFont, TGlyph>>[] inspectors) =>
TestList(range, ascent, descent, width, 0, 0, LinePosition.Regular, Range.UndefinedInt, inspectors)
(ParseLaTeXToDisplay(latex));

/// <summary>Makes sure that a single codepoint of various atom types have the same measured size.</summary>
[Theory, InlineData("x"), InlineData("2"), InlineData(","), InlineData("+"), InlineData("Σ"), InlineData("𝑥")]
Expand Down Expand Up @@ -67,18 +74,18 @@ public void TestVariablesNumbersAndOrdinaries(string latex) =>
Assert.Equal(40, line.Width);
});
[Theory]
[InlineData("%\n1234", "1234")]
[InlineData("12.b% comment ", "12.𝑏")]
[InlineData("|`% \\notacommand \u2028@/", "|`@/")]
public void TestIgnoreComments(string latex, string text) =>
TestOuter(latex, 4, 14, 4, 40,
[InlineData("%\n1234", "1234", 1, 4)]
[InlineData("12.b% comment ", "12.𝑏", 0, 4)]
[InlineData("|`% \\notacommand \u2028@/", "|`@/", 0, 5)]
public void TestIgnoreComments(string latex, string text, int rangeStart, int rangeEnd) =>
TestOuter(latex, (rangeStart, rangeEnd), 14, 4, 40,
d => {
var line = Assert.IsType<TextLineDisplay<TFont, TGlyph>>(d);
Assert.Equal(4, line.Atoms.Count);
Assert.All(line.Atoms, Assert.IsNotType<Atom.Atoms.Comment>);
Assert.Equal(text, string.Concat(line.Text));
Assert.Equal(new PointF(), line.Position);
Assert.Equal(new Range(0, 4), line.Range);
Assert.Equal(new Range(rangeStart, rangeEnd), line.Range);
Assert.False(line.HasScript);

Assert.Equal(14, line.Ascent);
Expand Down Expand Up @@ -533,5 +540,81 @@ public void TestColor() =>
Assert.Null(line.TextColor);
}
);
[Fact]
public void Issue213() {
float charWidth = 10;
float mathUnit = 20f / 18f;
foreach (var space in new[] { "", "\\," })
// 5 mu spacing between = (Relation) and - (Unary Operator, aka Ordinary)
TestOuter($"{space}=-1", (space.Length / 2, 3), 14, 4, 3 * charWidth + 5 * mathUnit, // expected: - becomes UnaryOperator then typesetted as Ordinary
d => {
var textBefore = Assert.IsType<TextLineDisplay<TFont, TGlyph>>(d);
Assert.Equal(3 * charWidth + 5 * mathUnit, textBefore.Width);
Assert.Equal("=\u22121", string.Concat(textBefore.Text));
Assert.Equal(new Range(space.Length / 2, 3), textBefore.Range);
Assert.Collection(textBefore.Atoms,
a => Assert.Equal("=", Assert.IsType<Atom.Atoms.Relation>(a).Nucleus),
a => Assert.Equal("\u2212", Assert.IsType<Atom.Atoms.Ordinary>(a).Nucleus),
a => Assert.Equal("1", Assert.IsType<Atom.Atoms.Ordinary>(a).Nucleus));
});
foreach (var (nonDisplayedAtomThatCreatesNewDisplayLine, additionalWidth) in new[] { (@"\, ", 3 * mathUnit), (@"\displaystyle ", 0) })
TestOuter($@"={nonDisplayedAtomThatCreatesNewDisplayLine}-1", 4, 14, 4, 3 * charWidth + 5 * mathUnit + additionalWidth, // issue 213: inserting a space between them should still work
eq => {
var textBefore = Assert.IsType<TextLineDisplay<TFont, TGlyph>>(eq);
Assert.Equal(new PointF(), textBefore.Position);
Assert.Equal(charWidth, textBefore.Width);
Assert.Equal("=", string.Concat(textBefore.Text));
Assert.Equal(new Range(0, 1), textBefore.Range);
Assert.Equal("=", Assert.IsType<Atom.Atoms.Relation>(Assert.Single(textBefore.Atoms)).Nucleus);
}, m1 => {
var textAfter = Assert.IsType<TextLineDisplay<TFont, TGlyph>>(m1);
Assert.Equal(new PointF(10 + 5 * mathUnit + additionalWidth, 0), textAfter.Position);
Assert.Equal(2 * charWidth, textAfter.Width);
Assert.Equal("\u22121", string.Concat(textAfter.Text));
Assert.Equal(new Range(2, 2), textAfter.Range);
Assert.Collection(textAfter.Atoms,
a => Assert.Equal("\u2212", Assert.IsType<Atom.Atoms.Ordinary>(a).Nucleus),
a => Assert.Equal("1", Assert.IsType<Atom.Atoms.Ordinary>(a).Nucleus));
});
TestOuter("=%comment\n-1", 4, 14, 4, 3 * charWidth + 5 * mathUnit, // same should apply to Comment atoms
d => {
var textBefore = Assert.IsType<TextLineDisplay<TFont, TGlyph>>(d);
Assert.Equal(3 * charWidth + 5 * mathUnit, textBefore.Width);
Assert.Equal("=\u22121", string.Concat(textBefore.Text));
Assert.Equal(new Range(0, 4), textBefore.Range);
Assert.Collection(textBefore.Atoms,
a => Assert.Equal("=", Assert.IsType<Atom.Atoms.Relation>(a).Nucleus),
a => Assert.Equal("\u2212", Assert.IsType<Atom.Atoms.Ordinary>(a).Nucleus),
a => Assert.Equal("1", Assert.IsType<Atom.Atoms.Ordinary>(a).Nucleus));
});
}
[Fact]
public void SpacingBetweenNumbers() {
// Numbers that have non-display atoms between them should not be fused together.
foreach (var (nonDisplayedAtomThatCreatesNewDisplayLine, additionalWidth) in new[] { (@"\quad ", 20), (@"\displaystyle ", 0) })
TestOuter($"2{nonDisplayedAtomThatCreatesNewDisplayLine}3", 3, 14, 4, 10 + additionalWidth + 10,
d => {
var line = Assert.IsType<TextLineDisplay<TFont, TGlyph>>(d);
Assert.Equal(new PointF(), line.Position);
Assert.Equal("2", Assert.IsType<Atom.Atoms.Ordinary>(Assert.Single(line.Atoms)).Nucleus);
Assert.Equal("2", string.Concat(line.Text));
Assert.Equal(new Range(0, 1), line.Range);
Assert.False(line.HasScript);
Assert.Equal(14, line.Ascent);
Assert.Equal(4, line.Descent);
Assert.Equal(10, line.Width);
},
d => {
var line = Assert.IsType<TextLineDisplay<TFont, TGlyph>>(d);
Assert.Equal(new PointF(10 + additionalWidth, 0), line.Position);
Assert.Equal("3", Assert.IsType<Atom.Atoms.Ordinary>(Assert.Single(line.Atoms)).Nucleus);
Assert.Equal("3", string.Concat(line.Text));
Assert.Equal(new Range(2, 1), line.Range);
Assert.False(line.HasScript);
Assert.Equal(14, line.Ascent);
Assert.Equal(4, line.Descent);
Assert.Equal(10, line.Width);
});
}
}
}
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/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.
3 changes: 1 addition & 2 deletions CSharpMath/Atom/MathAtom.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ public void Fuse(MathAtom otherAtom) {
FusedAtoms.Add(otherAtom);
}
Nucleus += otherAtom.Nucleus;
IndexRange = new Range(IndexRange.Location,
IndexRange.Length + otherAtom.IndexRange.Length);
IndexRange += otherAtom.IndexRange;
Subscript = otherAtom.Subscript;
Superscript = otherAtom.Superscript;
}
Expand Down
30 changes: 13 additions & 17 deletions CSharpMath/Atom/MathList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,32 +53,28 @@ public MathList Clone(bool finalize) {
foreach (var atom in Atoms)
newList.Add(atom.Clone(finalize));
} else {
MathAtom? prevNode = null;
int prevDisplayedIndex = -1;
foreach (var atom in Atoms) {
if (atom is Comment) {
var newComment = atom.Clone(finalize);
newComment.IndexRange = Range.NotFound;
newList.Add(newComment);
continue;
}
var prevNode = newList.Last;
var newNode = atom.Clone(finalize);
if (atom.IndexRange == Range.Zero) {
int prevIndex =
prevNode?.IndexRange.Location + prevNode?.IndexRange.Length ?? 0;
newNode.IndexRange = new Range(prevIndex, 1);
}
switch (prevNode, newNode) {
if (newNode.IndexRange == Range.Zero)
newNode.IndexRange = new Range(prevNode is { } prev ? prev.IndexRange.Location + prev.IndexRange.Length : 0, 1);
switch (prevDisplayedIndex == -1 ? null : newList[prevDisplayedIndex], newNode) {
// NOTE: The left pattern does not include UnaryOperator. Just try "1+++2" and "1++++2" in any LaTeX rendering engine.
case (null or BinaryOperator or Relation or Open or Punctuation or LargeOperator, BinaryOperator b):
newNode = b.ToUnaryOperator();
break;
case (BinaryOperator b, Relation or Punctuation or Close):
newList.Last = b.ToUnaryOperator();
newList[prevDisplayedIndex] = b.ToUnaryOperator();
break;
case (Number n, Number _) when n.Superscript.IsEmpty() && n.Subscript.IsEmpty():
n.Fuse(newNode);
continue; // do not add the new node; we fused it instead.
}
if ((prevNode, newNode) is (Number { Superscript.Count: 0, Subscript.Count: 0 } n, Number)) {
n.Fuse(newNode);
continue; // do not add the new node; we fused it instead.
}
if (newNode is not (Comment or Space or Style)) prevDisplayedIndex = newList.Count; // Corresponds to atom types that use continue; in Typesetter.CreateLine
newList.Add(newNode);
prevNode = newNode;
}
}
return newList;
Expand Down
2 changes: 1 addition & 1 deletion CSharpMath/Atom/Range.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace CSharpMath.Atom {
/// <summary>
/// Corresponds to a range of <see cref="MathAtom"/>s before finalization.
/// This value is tracked in finalized <see cref="MathAtom"/>s and <see cref="Display.IDisplay{TFont, TGlyph}"/>s,
/// for utilization in CSharpMath.Editor to construct MathListIndexes from <see cref="Display.IDisplay{TFont, TGlyph}"/>s.
/// for use in <see cref="Editor.Extensions.IndexForPoint{TFont, TGlyph}(Display.IDisplay{TFont, TGlyph}, Display.FrontEnd.TypesettingContext{TFont, TGlyph}, System.Drawing.PointF)"/>.
/// </summary>
public readonly struct Range : IEquatable<Range> {
public const int UndefinedInt = int.MinValue;
Expand Down
2 changes: 1 addition & 1 deletion CSharpMath/Display/InterElementSpaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ static int GetInterElementSpaceArrayIndexForType(MathAtom atomType, bool row) =>
var multiplier =
Spaces[leftIndex, rightIndex] switch {
Invalid => throw new InvalidCodePathException
($"Invalid space between {left.TypeName} and {right.TypeName}"),
($"Invalid space between {left.TypeName} and {right.TypeName}. The {nameof(Atoms.BinaryOperator)} should have been converted to a {nameof(Atoms.UnaryOperator)} during {nameof(MathList)} {nameof(MathList.Clone)}, then converted to an {nameof(Atoms.Ordinary)} during {nameof(Typesetter)} preparation."),
None => 0,
Thin => 3,
NsThin => style < LineStyle.Script ? 3 : 0,
Expand Down
6 changes: 4 additions & 2 deletions CSharpMath/Display/Typesetter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ internal Typesetter(TFont font, TypesettingContext<TFont, TGlyph> context,
internal static ListDisplay<TFont, TGlyph> CreateLine(
MathList list, TFont font, TypesettingContext<TFont, TGlyph> context,
LineStyle style, bool cramped, bool spaced = false) {
// NOTE: The 3 atom types that use continue; below, aka [Comment, Space, Style], correspond to non-displayed atom types
// in MathList.Clone(true). Update that if-condition and add a test in Issue213() if more such atom types are added.
// Otherwise, using these atoms between = (Relation) and - (BinaryOperator) will cause an exception from invalid spacing.

List<MathAtom> _PreprocessMathList() {
MathAtom? prevAtom = null;
Expand Down Expand Up @@ -338,8 +341,7 @@ private void CreateDisplayAtoms(List<MathAtom> preprocessedAtoms) {
if (_currentLineIndexRange.Location == Range.UndefinedInt)
_currentLineIndexRange = atom.IndexRange;
else
_currentLineIndexRange = new Range(_currentLineIndexRange.Location,
_currentLineIndexRange.Length + atom.IndexRange.Length);
_currentLineIndexRange += atom.IndexRange;
// add the fused atoms
if (atom.FusedAtoms != null)
_currentAtoms.AddRange(atom.FusedAtoms);
Expand Down
Loading