private MathList?BuildInternal(bool oneCharOnly, char stopChar = '\0') { if (oneCharOnly && stopChar > '\0') { throw new InvalidCodePathException("Cannot set both oneCharOnly and stopChar"); } var r = new MathList(); MathAtom?prevAtom = null; while (HasCharacters) { if (Error != null) { return(null); } MathAtom atom; switch (GetNextCharacter()) { case '^' when oneCharOnly: case '}' when oneCharOnly: case '_' when oneCharOnly: case '&' when oneCharOnly: // this is not the character we are looking for. They are for the caller to look at. UnlookCharacter(); return(r); case var ch when stopChar > '\0' && ch == stopChar: return(r); case '^': if (prevAtom == null || prevAtom.Superscript != null || !prevAtom.ScriptsAllowed) { prevAtom = new Ordinary(string.Empty); r.Add(prevAtom); } // this is a superscript for the previous atom. // note, if the next char is StopChar, it will be consumed and doesn't count as stop. prevAtom.Superscript = this.BuildInternal(true); continue; case '_': if (prevAtom == null || prevAtom.Subscript != null || !prevAtom.ScriptsAllowed) { prevAtom = new Ordinary(string.Empty); r.Add(prevAtom); } // this is a subscript for the previous atom. // note, if the next char is StopChar, it will be consumed and doesn't count as stop. prevAtom.Subscript = this.BuildInternal(true); continue; case '{': MathList?sublist; if (_currentEnvironment != null && _currentEnvironment.Name == null) { // \\ or \cr which do not have a corrosponding \end var oldEnv = _currentEnvironment; _currentEnvironment = null; sublist = BuildInternal(false, '}'); _currentEnvironment = oldEnv; } else { sublist = BuildInternal(false, '}'); } if (sublist == null) { return(null); } prevAtom = sublist.Atoms.LastOrDefault(); r.Append(sublist); if (oneCharOnly) { return(r); } continue; #warning TODO Example //https://phabricator.wikimedia.org/T99369 //https://phab.wmfusercontent.org/file/data/xsimlcnvo42siudvwuzk/PHID-FILE-bdcqexocj5b57tj2oezn/math_rendering.png //dt, \text{d}t, \partial t, \nabla\psi \\ \underline\overline{dy/dx, \text{d}y/\text{d}x, \frac{dy}{dx}, \frac{\text{d}y}{\text{d}x}, \frac{\partial^2}{\partial x_1\partial x_2}y} \\ \prime, case '}' when oneCharOnly || stopChar != 0: throw new InvalidCodePathException("This should have been handled before."); case '}': SetError("Missing opening brace"); return(null); case '\\': var command = ReadCommand(); var done = StopCommand(command, r, stopChar); if (done != null) { return(done); } if (Error != null) { return(null); } if (ApplyModifier(command, prevAtom)) { continue; } if (LaTeXDefaults.FontStyles.TryGetValue(command, out var fontStyle)) { var oldSpacesAllowed = _textMode; var oldFontStyle = _currentFontStyle; _textMode = (command == "text"); _currentFontStyle = fontStyle; var childList = BuildInternal(true); if (childList == null) { return(null); } _currentFontStyle = oldFontStyle; _textMode = oldSpacesAllowed; prevAtom = childList.Atoms.LastOrDefault(); r.Append(childList); if (oneCharOnly) { return(r); } continue; } switch (AtomForCommand(command, stopChar)) { case null: SetError(Error ?? "Internal error"); return(null); case var a: atom = a; break; } break; case '&': // column separation in tables if (_currentEnvironment != null) { return(r); } var table = BuildTable(null, r, false, stopChar); if (table == null) { return(null); } return(new MathList(table)); case '\'': // this case is NOT in iosMath int i = 1; while (ExpectCharacter('\'')) { i++; } atom = new Prime(i); break; case ' ' when _textMode: atom = new Ordinary(" "); break; case var ch when ch <= sbyte.MaxValue: if (LaTeXDefaults.ForAscii((sbyte)ch) is MathAtom asciiAtom) { atom = asciiAtom; } else { continue; // Ignore ASCII spaces and control characters } break; case var ch: // not a recognized character, display it directly atom = new Ordinary(ch.ToStringInvariant()); break; } atom.FontStyle = _currentFontStyle; r.Add(atom); prevAtom = atom; if (oneCharOnly) { return(r); // we consumed our character. } } if (stopChar > 0) { if (stopChar == '}') { SetError("Missing closing brace"); } else { // we never found our stop character. SetError("Expected character not found: " + stopChar.ToStringInvariant()); } } return(r); }
private MathAtom?AtomForCommand(string command, char stopChar) { switch (LaTeXDefaults.AtomForCommand(command)) { case Accent accent: var innerList = BuildInternal(true); if (innerList is null) { return(null); } return(new Accent(accent.Nucleus, innerList)); case MathAtom atom: return(atom); } switch (command) { case "frac": var numerator = BuildInternal(true); if (numerator is null) { return(null); } var denominator = BuildInternal(true); if (denominator is null) { return(null); } return(new Fraction(numerator, denominator)); case "binom": numerator = BuildInternal(true); if (numerator is null) { return(null); } denominator = BuildInternal(true); if (denominator is null) { return(null); } return(new Fraction(numerator, denominator, false) { LeftDelimiter = "(", RightDelimiter = ")" }); case "sqrt": var degree = ExpectCharacter('[') ? BuildInternal(false, ']') : new MathList(); if (degree is null) { return(null); } var radicand = BuildInternal(true); return(radicand != null ? new Radical(degree, radicand) : null); case "left": var oldInner = _currentInner; var leftBoundary = BoundaryAtomForDelimiterType("left"); if (!(leftBoundary is Boundary left)) { return(null); } _currentInner = new InnerEnvironment(); var innerList = BuildInternal(false, stopChar); if (innerList is null) { return(null); } if (!(_currentInner.RightBoundary is Boundary right)) { SetError("Missing \\right"); return(null); } _currentInner = oldInner; return(new Inner(left, innerList, right)); case "overline": innerList = BuildInternal(true); if (innerList is null) { return(null); } return(new Overline(innerList)); case "underline": innerList = BuildInternal(true); if (innerList is null) { return(null); } return(new Underline(innerList)); case "begin": var env = ReadEnvironment(); if (env == null) { return(null); } return(BuildTable(env, null, false, stopChar)); case "color": switch (ReadColor()) { case string s when BuildInternal(true) is { } ml: if (Structures.Color.Create(s.AsSpan()) is { } color) { return(new Color(color, ml)); } SetError(@"Invalid color: " + s); return(null); default: return(null); } ; case "prime": SetError(@"\prime won't be supported as Unicode has no matching character. Use ' instead."); return(null); case "kern": case "hskip": if (_textMode) { var(space, error) = ReadSpace(); if (error != null) { SetError(error); return(null); } else { return(new Space(space)); } } SetError($@"\{command} is not allowed in math mode"); return(null); case "mkern": case "mskip": if (!_textMode) { var(space, error) = ReadSpace(); if (error != null) { SetError(error); return(null); } else { return(new Space(space)); } } SetError($@"\{command} is not allowed in text mode"); return(null); case "raisebox": if (!ExpectCharacter('{')) { SetError("Expected {"); return(null); } var(raise, err) = ReadSpace(); if (err != null) { SetError(err); return(null); } if (!ExpectCharacter('}')) { SetError("Expected }"); return(null); } innerList = BuildInternal(true); if (innerList is null) { return(null); } return(new RaiseBox(raise, innerList)); case "TeX": return(TeX); default: SetError("Invalid command \\" + command); return(null); } }