internal IMathAtom BuildTable(string environment, IMathList firstList, bool isRow, char stopChar) { var oldEnv = _currentEnvironment; _currentEnvironment = new TableEnvironmentProperties(environment); int currentRow = 0; int currentColumn = 0; var rows = new List <List <IMathList> > { new List <IMathList>() }; if (firstList != null) { rows[currentRow].Add(firstList); if (isRow) { _currentEnvironment.NRows++; currentRow++; rows.Add(new List <IMathList>()); } else { currentColumn++; } } while (HasCharacters && !(_currentEnvironment.Ended)) { var list = BuildInternal(false, stopChar); if (list == null) { return(null); } rows[currentRow].Add(list); currentColumn++; if (_currentEnvironment.NRows > currentRow) { currentRow = _currentEnvironment.NRows; rows.Add(new List <IMathList>()); currentColumn = 0; } } if (_currentEnvironment.Name != null && !_currentEnvironment.Ended) { SetError(@"Missing \end"); return(null); } var tableResult = MathAtoms.Table(_currentEnvironment.Name, rows); if (tableResult.Error is string error) { SetError(error); return(null); } _currentEnvironment = oldEnv; return(tableResult.Value); }
internal IMathAtom _BoundaryAtomForDelimiterType(string delimiterType) { string delim = ReadDelimiter(); if (delim == null) { SetError("Missing delimiter for " + delimiterType); return(null); } var boundary = MathAtoms.BoundaryAtom(delim); if (boundary == null) { SetError(@"Invalid delimiter for \" + delimiterType + ": " + delim); } return(boundary); }
public static string DelimiterToString(IMathAtom delimiter) { var command = MathAtoms.DelimiterName(delimiter); if (command == null) { return(string.Empty); } var singleChars = @"()[]<>|./"; if (singleChars.IndexOf(command, StringComparison.OrdinalIgnoreCase) >= 0 && command.Length == 1) { return(command); } if (command == "||") { return(@"\|"); } else { return(@"\" + command); } }
public static string MathListToString(IMathList mathList) { var builder = new StringBuilder(); var currentFontStyle = FontStyle.Default; foreach (var atom in mathList) { if (currentFontStyle != atom.FontStyle) { if (currentFontStyle != FontStyle.Default) { // close the previous font style builder.Append("}"); } if (atom.FontStyle != FontStyle.Default) { // open a new font style var fontStyleName = atom.FontStyle.FontName(); builder.Append(@"\" + fontStyleName + "{"); } } currentFontStyle = atom.FontStyle; switch (atom.AtomType) { case MathAtomType.Fraction: { var fraction = (IFraction)atom; var numerator = MathListToString(fraction.Numerator); var denominator = MathListToString(fraction.Denominator); if (fraction.HasRule) { builder.Append(@"\frac{" + numerator + "}{" + denominator + "}"); } else { string command = null; if (fraction.LeftDelimiter == null && fraction.RightDelimiter == null) { command = "atop"; } else if (fraction.LeftDelimiter == "(" && fraction.RightDelimiter == ")") { command = "choose"; } else if (fraction.LeftDelimiter == "{" && fraction.RightDelimiter == "}") { command = "brace"; } else if (fraction.LeftDelimiter == "[" && fraction.RightDelimiter == "]") { command = "brack"; } else { command = $"atopwithdelims{fraction.LeftDelimiter}{fraction.RightDelimiter}"; } builder.Append("{" + numerator + @" \" + command + " " + denominator + "}"); } } break; case MathAtomType.Radical: { builder.Append(@"\sqrt"); var radical = (IRadical)atom; if (radical.Degree != null) { builder.Append($"[{MathListToString(radical.Degree)}]"); } builder.Append("{" + MathListToString(radical.Radicand) + "}"); break; } case MathAtomType.Inner: { var inner = (IMathInner)atom; if (inner.LeftBoundary == null && inner.RightBoundary == null) { builder.Append("{" + MathListToString(inner.InnerList) + "}"); } else { if (inner.LeftBoundary == null) { builder.Append(@"\left. "); } else { builder.Append(@"\left" + DelimiterToString(inner.LeftBoundary) + " "); } builder.Append(MathListToString(inner.InnerList)); if (inner.RightBoundary == null) { builder.Append(@"\right. "); } else { builder.Append(@"\right" + DelimiterToString(inner.RightBoundary) + " "); } } break; } case MathAtomType.Table: { var table = (IMathTable)atom; if (table.Environment != null) { builder.Append(@"\begin{" + table.Environment + "}"); } for (int i = 0; i < table.NRows; i++) { var row = table.Cells[i]; for (int j = 0; j < row.Count; j++) { var cell = row[j]; if (table.Environment == "matrix") { if (cell.Count >= 1 && cell[0].AtomType == MathAtomType.Style) { // remove the first atom. var atoms = cell.Atoms.GetRange(1, cell.Count - 1); cell = MathLists.WithAtoms(atoms.ToArray()); } } if (table.Environment == "eqalign" || table.Environment == "aligned" || table.Environment == "split") { if (j == 1 && cell.Count >= 1 && cell[0].AtomType == MathAtomType.Ordinary && string.IsNullOrEmpty(cell[0].Nucleus)) { // empty nucleus added for spacing. Remove it. var atoms = cell.Atoms.GetRange(1, cell.Count - 1); cell = MathLists.WithAtoms(atoms.ToArray()); } } builder.Append(MathListToString(cell)); if (j < row.Count - 1) { builder.Append("&"); } } if (i < table.NRows - 1) { builder.Append(@"\\ "); } } if (table.Environment != null) { builder.Append(@"\end{" + table.Environment + "}"); } break; } case MathAtomType.Overline: { var over = (IOverline)atom; builder.Append(@"\overline{" + MathListToString(over.InnerList) + "}"); break; } case MathAtomType.Underline: { var under = (IUnderline)atom; builder.Append(@"\underline{" + MathListToString(under.InnerList) + "}"); break; } case MathAtomType.Accent: { var accent = (IAccent)atom; var list = accent.InnerList; accent.InnerList = null; //for lookup builder.Append(@"\" + MathAtoms.Commands[(MathAtom)atom] + "{" + MathListToString(list) + "}"); break; } case MathAtomType.LargeOperator: { var op = (LargeOperator)atom; var command = MathAtoms.LatexSymbolNameForAtom(op); var originalOperator = (LargeOperator)MathAtoms.ForLatexSymbolName(command); builder.Append($@"\{command} "); if (originalOperator.Limits != op.Limits) { switch (op.Limits) { case true: builder.Append(@"\limits "); break; case false: if (!op.NoLimits) { builder.Append(@"\nolimits "); } break; case null: break; } } break; } case MathAtomType.Space: { var space = (ISpace)atom; var intSpace = (int)space.Length; if (SpaceToCommands.ContainsKey(intSpace) && intSpace == space.Length) { var command = SpaceToCommands[intSpace]; builder.Append(@"\" + command + " "); } else { if (space.IsMu) { builder.Append(@"\mkern" + space.Length.ToString("0.0") + "mu"); } else { builder.Append(@"\kern" + space.Length.ToString("0.0") + "pt"); } } break; } case MathAtomType.Style: { var style = (IStyle)atom; var command = StyleToCommands[style.LineStyle]; builder.Append(@"\" + command + " "); break; } case MathAtomType.Color: { var color = (IColor)atom; builder.Append($@"\color{{{color.ColorString}}}{{{MathListToString(color.InnerList)}}}"); break; } case MathAtomType.Group: builder.AppendInBraces(MathListToString(((Group)atom).InnerList), NullHandling.EmptyContent); break; case MathAtomType.Prime: builder.Append('\'', ((Prime)atom).Length); break; default: { var aNucleus = atom.Nucleus; if (String.IsNullOrEmpty(aNucleus)) { builder.Append(@"{}"); } else if (aNucleus == "\u2236") { builder.Append(":"); } else if (aNucleus == "\u2212") { builder.Append("-"); } else { var command = MathAtoms.LatexSymbolNameForAtom((MathAtom)atom); if (command == null) { if (atom is Extension.I_ExtensionAtom ext) { builder.Append(Extension._MathListDestructor.MathAtomToString(ext)); } else { builder.Append(aNucleus); } } else { builder.Append(@"\" + command + " "); } } break; } } if (atom.Subscript != null) { var scriptString = MathListToString(atom.Subscript); builder.Append(scriptString.Length == 1 ? $"_{scriptString}" : $"_{{{scriptString}}}"); } if (atom.Superscript != null) { var scriptString = MathListToString(atom.Superscript); builder.Append(scriptString.Length == 1 ? $"^{scriptString}" : $"^{{{scriptString}}}"); } } if (currentFontStyle != FontStyle.Default) { builder.Append("}"); } return(builder.ToString()); }
internal IMathList BuildInternal(bool oneCharOnly, char stopChar) { if (oneCharOnly && stopChar > 0) { throw new InvalidOperationException("Cannot set both oneCharOnly and stopChar"); } var r = new MathList(); IMathAtom prevAtom = null; while (HasCharacters) { if (_error != null) { return(null); } IMathAtom atom; var ch = GetNextCharacter(); if (oneCharOnly) { if (ch == '^' || ch == '}' || ch == '_' || ch == '&') { // this is not the character we are looking for. They are for the caller to look at. UnlookCharacter(); return(r); } } if (stopChar > 0 && ch == stopChar) { return(r); } switch (ch) { case '^': if (prevAtom == null || prevAtom.Superscript != null || !prevAtom.ScriptsAllowed) { prevAtom = MathAtoms.Create(MathAtomType.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 = MathAtoms.Create(MathAtomType.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 '{': IMathList 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(); if (oneCharOnly) { r.Append(sublist); return(r); } #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, atom = new Group { InnerList = sublist }; break; case '}': if (oneCharOnly || stopChar != 0) { throw new InvalidCodePathException("This should have been handled before."); } 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; } var fontStyleQ = MathAtoms.FontStyle(command); if (fontStyleQ.HasValue) { var fontStyle = fontStyleQ.Value; 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; } atom = AtomForCommand(command, stopChar); if (atom == null) { SetError(_error ?? "Internal error"); return(null); } break; case '&': { // column separation in tables if (_currentEnvironment != null) { return(r); } var table = BuildTable(null, r, false, stopChar); if (table == null) { return(null); } return(MathLists.WithAtoms(table)); } case '\'': // this case is NOT in iosMath int i = 1; while (ExpectCharacter('\'')) { i++; } atom = new Prime(i); break; default: if (_textMode && ch == ' ') { atom = MathAtoms.ForLatexSymbolName(" "); } else { atom = MathAtoms.ForCharacter(ch); if (atom == null) { // not a recognized character continue; } } break; } if (atom == null) { throw new InvalidCodePathException("Atom shouldn't be null"); } 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.ToString()); } } return(r); }
internal IMathAtom AtomForCommand(string command, char stopChar) { var atom = MathAtoms.ForLatexSymbolName(command); if (atom is Accent accent) { accent.InnerList = BuildInternal(true); return(accent); } if (atom != null) { return(atom); } switch (command) { case "frac": return(new Fraction { Numerator = BuildInternal(true), Denominator = BuildInternal(true) }); case "binom": return(new Fraction(false) { Numerator = BuildInternal(true), Denominator = BuildInternal(true), LeftDelimiter = "(", RightDelimiter = ")" }); case "sqrt": var rad = new Radical(); if (ExpectCharacter('[')) { rad.Degree = BuildInternal(false, ']'); rad.Radicand = BuildInternal(true); } else { rad.Radicand = BuildInternal(true); } return(rad); case "left": var oldInner = _currentInnerAtom; _currentInnerAtom = new Inner { LeftBoundary = _BoundaryAtomForDelimiterType("left") }; if (_currentInnerAtom.LeftBoundary == null) { return(null); } _currentInnerAtom.InnerList = BuildInternal(false, stopChar); if (_currentInnerAtom.RightBoundary == null) { SetError("Missing \\right"); return(null); } var newInner = _currentInnerAtom; _currentInnerAtom = oldInner; return(newInner); case "overline": return(new Overline { InnerList = BuildInternal(true) }); case "underline": return(new Underline() { InnerList = BuildInternal(true) }); case "begin": var env = ReadEnvironment(); if (env == null) { return(null); } var table = BuildTable(env, null, false, stopChar); return(table); case "color": return(new Color { ColorString = ReadColor(), InnerList = BuildInternal(true) }); case "prime": SetError(@"\prime won't be supported as Unicode has no matching character. Use ' instead."); return(null); default: var extResult = Extension._MathListBuilder.AtomForCommand(this, command); if (extResult != null) { return(extResult); } SetError("Invalid command \\" + command); return(null); } }