Skip to content

Commit 5f80b95

Browse files
authored
Punctuation should stick to inline maths (#119)
* Initial change * Tests are passing * New test in CSharpMath.Rendering.Tests * Surrogates are tested
1 parent dd4e554 commit 5f80b95

File tree

9 files changed

+81
-41
lines changed

9 files changed

+81
-41
lines changed

CSharpMath.Forms.Example/CSharpMath.Forms.Example.Android/Resources/Resource.designer.cs

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CSharpMath.Rendering.Tests/TestRenderingTextData.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public sealed class TestRenderingTextData : TestRenderingSharedData<TestRenderin
1515
public const string QuadraticPolynomial = @"$$p(x)=ax^2+bx+c, \text{ where } a \neq 0$$ Above is the definition of a quadratic ($2^{nd}$ degree) polynomial.";
1616
public const string InlineMath = @"$\int_{a_1^2}^{a_2^2}\sqrt\frac x2dx$";
1717
public const string DisplayMath = @"$$\int_{a_1^2}^{a_2^2}\sqrt\frac x2dx$$";
18+
public const string PunctuationAffectsLineBreaking = @"11111222223333344444555556666677777888889999900000 12345678.\\11111222223333344444555556666677777888889999900000 123456789.\\11111222223333344444555556666677777888889999900000 \(\overline{12345678}$.\\11111222223333344444555556666677777888889999900000 $\overline{123456789}\).";
1819
#warning Fix plz
1920
//public const string MatrixLookalike = @"$$x$$ $$y$$ $$z$$";
2021
//public const string MultilineDisplayMath = @"$$x$$\;$$y$$\;$$z$$";
Loading
Loading
Loading
Loading

CSharpMath.Rendering.Text.Tests/TextLaTeXParserTests.cs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ private Action<TextAtom> CheckAtom<T>
1919
[InlineData("123.456")]
2020
[InlineData("abc")]
2121
[InlineData("abc", "123")]
22+
[InlineData("123", "abc")]
2223
[InlineData("12", "a.m.")]
2324
[InlineData("1,", "2,", "3")]
2425
[InlineData("1,,", "2,,", "3")]
2526
[InlineData("1,,,")]
2627
[InlineData("1,,,", "2")]
2728
[InlineData("1()", "a")]
2829
[InlineData("a,", "b,", "c")]
30+
[InlineData("a,.", "b,.", "c")]
2931
[InlineData("a,,", "b,,", "c")]
3032
[InlineData("1/", "2/", "3")]
3133
[InlineData("!@*()")]
@@ -57,6 +59,51 @@ public void Command(string input, string output, params string[] text) {
5759
Assert.Equal(output, TextLaTeXParser.TextAtomToLaTeX(atom).ToString());
5860
}
5961
[Theory]
62+
[InlineData(@"\color{red}␣a", "a", null, @"\color{red}{a}")]
63+
[InlineData(@"\color{red}␣{a}", "a", null, @"\color{red}{a}")]
64+
[InlineData(@"\color{red}␣😀", "😀", null, @"\color{red}{😀}")]
65+
[InlineData(@"\color{red}␣{😀}", "😀", null, @"\color{red}{😀}")]
66+
[InlineData(@"\color{red}␣\textbar", "|", null, @"\color{red}{\textbar }")]
67+
[InlineData(@"\color{red}␣{\textbar}", "|", null, @"\color{red}{\textbar }")]
68+
[InlineData(@"\color{red}␣aa", "a", "a", @"\color{red}{a}a")]
69+
[InlineData(@"\color{red}␣{a}a", "a", "a", @"\color{red}{a}a")]
70+
[InlineData(@"\color{red}␣a😄", "a", "😄", @"\color{red}{a}😄")]
71+
[InlineData(@"\color{red}␣{a}😄", "a", "😄", @"\color{red}{a}😄")]
72+
[InlineData(@"\color{red}␣😀a", "😀", "a", @"\color{red}{😀}a")]
73+
[InlineData(@"\color{red}␣{😀}a", "😀", "a", @"\color{red}{😀}a")]
74+
[InlineData(@"\color{red}␣😀😄", "😀", "😄", @"\color{red}{😀}😄")]
75+
[InlineData(@"\color{red}␣{😀}😄", "😀", "😄", @"\color{red}{😀}😄")]
76+
[InlineData(@"\color{red}␣\textbar a", "|", "a", @"\color{red}{\textbar }a")]
77+
[InlineData(@"\color{red}␣{\textbar}a", "|", "a", @"\color{red}{\textbar }a")]
78+
[InlineData(@"\color{red}␣a\textbar", "a", "|", @"\color{red}{a}\textbar ")]
79+
[InlineData(@"\color{red}␣{a}\textbar", "a", "|", @"\color{red}{a}\textbar ")]
80+
[InlineData(@"\color{red}␣\textbar\textbar", "|", "|", @"\color{red}{\textbar }\textbar ")]
81+
[InlineData(@"\color{red}␣{\textbar}\textbar", "|", "|", @"\color{red}{\textbar }\textbar ")]
82+
[InlineData(@"\color{red}␣\textbar😄", "|", "😄", @"\color{red}{\textbar }😄")]
83+
[InlineData(@"\color{red}␣{\textbar}😄", "|", "😄", @"\color{red}{\textbar }😄")]
84+
[InlineData(@"\color{red}␣😀\textbar", "😀", "|", @"\color{red}{😀}\textbar ")]
85+
[InlineData(@"\color{red}␣{😀}\textbar", "😀", "|", @"\color{red}{😀}\textbar ")]
86+
public void CommandArguments(string input, string colored, string? after, string output) {
87+
void Test(string input) {
88+
var atom = Parse(input);
89+
var list = new List<TextAtom> {
90+
new TextAtom.Color(new TextAtom.Text(colored), Structures.Color.PredefinedColors["red"])
91+
};
92+
if (after != null) list.Add(new TextAtom.Text(after));
93+
Assert.Equal(list.Count == 1 ? list[0] : new TextAtom.List(list), atom);
94+
Assert.Equal(output, TextLaTeXParser.TextAtomToLaTeX(atom).ToString());
95+
}
96+
Test(input.Replace("␣", ""));
97+
Test(input.Replace("␣", " "));
98+
Test(input.Replace("␣", " "));
99+
Test(input.Replace("␣", "\r"));
100+
Test(input.Replace("␣", "\n"));
101+
Test(input.Replace("␣", "\r\n"));
102+
Test(input.Replace("␣", " \r "));
103+
Test(input.Replace("␣", " \n "));
104+
Test(input.Replace("␣", " \r\n "));
105+
}
106+
[Theory]
60107
[InlineData(@"\textbb", Atom.FontStyle.Blackboard)]
61108
[InlineData(@"\textbf", Atom.FontStyle.Bold)]
62109
[InlineData(@"\textrm", Atom.FontStyle.Default)] // Default is converted to Roman
@@ -234,14 +281,14 @@ public void Accent(string command, string accent) {
234281
[InlineData(@"\$\[\$\]\$", @"\$", @"\$", true, @"\$", @"\$\[\$ \]\$")]
235282

236283
// https://github.com/verybadcat/CSharpMath/issues/113
237-
//[InlineData(@",$,$,", @",", @",,", false, @"", @",\(,, \)")]
238-
//[InlineData(@",\(,$,", @",", @",,", false, @"", @",\(,, \)")]
239-
//[InlineData(@",$,\),", @",", @",,", false, @"", @",\(,, \)")]
240-
//[InlineData(@",\(,\),", @",", @",,", false, @"", @",\(,, \)")]
241-
//[InlineData(@",$$,$$,", @",", @",,", true, @"", @",\[,, \]")]
242-
//[InlineData(@",\[,$$,", @",", @",,", true, @"", @",\[,, \]")]
243-
//[InlineData(@",$$,\],", @",", @",,", true, @"", @",\[,, \]")]
244-
//[InlineData(@",\[,\],", @",", @",,", true, @"", @",\[,, \]")]
284+
[InlineData(@",$,$,", @",", @",,", false, null, @",\(,,\)")]
285+
[InlineData(@",\(,$,", @",", @",,", false, null, @",\(,,\)")]
286+
[InlineData(@",$,\),", @",", @",,", false, null, @",\(,,\)")]
287+
[InlineData(@",\(,\),", @",", @",,", false, null, @",\(,,\)")]
288+
[InlineData(@",$$,$$,", @",", @",", true, @",", @",\[,\],")]
289+
[InlineData(@",\[,$$,", @",", @",", true, @",", @",\[,\],")]
290+
[InlineData(@",$$,\],", @",", @",", true, @",", @",\[,\],")]
291+
[InlineData(@",\[,\],", @",", @",", true, @",", @",\[,\],")]
245292
public void Math(string input, string? textBefore, string math, bool display, string? textAfter, string output) {
246293
var atom = Parse(input);
247294
var list = new List<TextAtom>();

CSharpMath.Rendering/Text/TextAtomListBuilder.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,19 @@ public class TextAtomListBuilder : IReadOnlyList<TextAtom> {
88
readonly List<TextAtom> _list = new List<TextAtom>();
99
private void Add(TextAtom atom) => _list.Add(atom);
1010
public void ControlSpace() => Add(new TextAtom.ControlSpace());
11-
public void Accent(TextAtom atom, string accent) =>
12-
Add(new TextAtom.Accent(atom, accent));
13-
public void Text(string text, ReadOnlySpan<char> lookAheadForPunc) =>
14-
Add(new TextAtom.Text(text + lookAheadForPunc.ToString()));
11+
public void Accent(TextAtom atom, string accent) => Add(new TextAtom.Accent(atom, accent));
12+
public void Text(string text) {
13+
if (char.IsPunctuation(text, 0))
14+
switch (Last) {
15+
case TextAtom.Text { Content: var prevText }:
16+
Last = new TextAtom.Text(prevText + text);
17+
return;
18+
case TextAtom.Math { DisplayStyle: false, Content: var mathList }:
19+
mathList.Add(new Atom.Atoms.Punctuation(text));
20+
return;
21+
}
22+
Add(new TextAtom.Text(text));
23+
}
1524
public void Space(Space space) => Add(new TextAtom.Space(space));
1625
public void Style(TextAtom atom, Atom.FontStyle style) => Add(new TextAtom.Style(atom, style));
1726
public void Size(TextAtom atom, float fontSize) => Add(new TextAtom.Size(atom, fontSize));

CSharpMath.Rendering/Text/TextLaTeXParser.cs

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,6 @@ void ObtainSection(ReadOnlySpan<char> latexInput, int index,
111111
kind = breakList[index].wordKind;
112112
}
113113
ObtainSection(latex, i, out var startAt, out endAt, out var textSection, out var wordKind);
114-
bool PreviousSection(ReadOnlySpan<char> latexInput, ref ReadOnlySpan<char> section) {
115-
bool success = i-- > 0;
116-
if (success) ObtainSection(latexInput, i, out startAt, out endAt, out section, out wordKind);
117-
return success;
118-
}
119114
bool NextSection(ReadOnlySpan<char> latexInput, ref ReadOnlySpan<char> section) {
120115
bool success = ++i < breakList.Count;
121116
if (success) ObtainSection(latexInput, i, out startAt, out endAt, out section, out wordKind);
@@ -155,24 +150,12 @@ Result<Color> ReadColor(ReadOnlySpan<char> latexInput, ref ReadOnlySpan<char> se
155150
Ok(value) :
156151
Err("Invalid color: " + color.ToString())
157152
);
158-
///<summary>Get punctutation after current section</summary>
159-
ReadOnlySpan<char> NextSectionWhilePunc(ReadOnlySpan<char> latexInput, ref ReadOnlySpan<char> section) {
160-
int start = endAt;
161-
ReadOnlySpan<char> specialChars = stackalloc[] { '#', '$', '%', '&', '\\', '^', '_', '{', '}', '~' };
162-
while (NextSection(latexInput, ref section))
163-
if (wordKind != WordKind.Punc || specialChars.IndexOf(section[0]) != -1) {
164-
// We have overlooked by one when non-punctuation or special character is encountered
165-
PreviousSection(latexInput, ref section);
166-
break;
167-
}
168-
return latexInput.Slice(start, endAt - start);
169-
}
170153
//Nothing should be before dollar sign checking -- dollar sign checking uses continue;
171154
atoms.TextLength = startAt;
172155
if (textSection.Is('$')) {
173156
if (backslashEscape)
174157
if (displayMath != null) mathLaTeX.Append(@"\$");
175-
else atoms.Text("$", NextSectionWhilePunc(latex, ref textSection));
158+
else atoms.Text("$");
176159
else {
177160
dollarCount++;
178161
continue;
@@ -227,7 +210,9 @@ ReadOnlySpan<char> NextSectionWhilePunc(ReadOnlySpan<char> latexInput, ref ReadO
227210
break;
228211
case var _ when textSection.Is('}'):
229212
return "Missing opening brace";
230-
case var _ when wordKind == WordKind.NewLine:
213+
// !char.IsSurrogate(textSection[0]) is result of https://github.com/LayoutFarm/Typography/issues/206
214+
case var _ when wordKind == WordKind.NewLine && !char.IsSurrogate(textSection[0]):
215+
if (oneCharOnly) continue;
231216
// Consume newlines after commands
232217
// Double newline == paragraph break
233218
if (afterNewline) {
@@ -239,21 +224,20 @@ ReadOnlySpan<char> NextSectionWhilePunc(ReadOnlySpan<char> latexInput, ref ReadO
239224
afterNewline = true;
240225
continue;
241226
}
242-
case var _ when wordKind == WordKind.Whitespace:
227+
case var _ when wordKind == WordKind.Whitespace && !char.IsSurrogate(textSection[0]):
243228
//Collpase spaces
244-
if (afterCommand) continue;
229+
if (afterCommand || oneCharOnly) continue;
245230
else atoms.ControlSpace();
246231
break;
247232
default: //Just ordinary text
248233
if (oneCharOnly) {
249-
if (startAt + 1 < endAt) { //Only re-read if current break span is more than 1 long
234+
var firstCodepointLength = char.IsHighSurrogate(textSection[0]) ? 2 : 1;
235+
if (startAt + firstCodepointLength < endAt) { //Only re-read if current break span is more than 1 long
250236
i--;
251-
breakList[i] = new BreakAtInfo(breakList[i].breakAt + 1, breakList[i].wordKind);
237+
breakList[i] = new BreakAtInfo(breakList[i].breakAt + firstCodepointLength, breakList[i].wordKind);
252238
}
253-
//Need to allocate in the end :(
254-
//Don't look ahead for punc; we are looking for one char only
255-
atoms.Text(textSection[0].ToString(), default);
256-
} else atoms.Text(textSection.ToString(), NextSectionWhilePunc(latex, ref textSection));
239+
atoms.Text(textSection.Slice(0, firstCodepointLength).ToString());
240+
} else atoms.Text(textSection.ToString());
257241
break;
258242
}
259243
afterCommand = false;
@@ -428,7 +412,7 @@ ReadOnlySpan<char> NextSectionWhilePunc(ReadOnlySpan<char> latexInput, ref ReadO
428412
}
429413
//case "textasciicircum", "textless", ...
430414
case var textSymbol when TextLaTeXSettings.PredefinedTextSymbols.TryGetValue(textSymbol, out var replaceResult):
431-
atoms.Text(replaceResult, NextSectionWhilePunc(latex, ref textSection));
415+
atoms.Text(replaceResult);
432416
break;
433417
case var command:
434418
if (displayMath != null) mathLaTeX.Append(command); //don't eat the command when parsing math

0 commit comments

Comments
 (0)