private void RunScriptTest(string input, Func <IMathAtom, IMathList> scriptGetter, MathAtomType[][] atomTypes, string output) { var builder = new MathListBuilder(input); var list = builder.Build(); Assert.Null(builder.Error); ExpandGroups(list); CheckAtomTypes(list, atomTypes[0]); IMathAtom firstAtom = list[0]; var types = atomTypes[1]; if (types.Count() > 0) { Assert.NotNull(scriptGetter(firstAtom)); } var scriptList = scriptGetter(firstAtom); CheckAtomTypes(scriptList, atomTypes[1]); if (atomTypes.Count() == 3) { // one more level var firstScript = scriptList[0]; var scriptScriptList = scriptGetter(firstScript); CheckAtomTypes(scriptScriptList, atomTypes[2]); } string latex = MathListBuilder.MathListToString(list); Assert.Equal(output, latex); }
static void InsertAtAtomIndexAndAdvance(this IMathList self, int atomIndex, IMathAtom atom, ref MathListIndex advance, MathListSubIndexType advanceType) { if (atomIndex < 0 || atomIndex > self.Count) { throw new IndexOutOfRangeException($"Index {atomIndex} is out of bounds for list of size {self.Atoms.Count}"); } // Test for placeholder to the right of index, e.g. \sqrt{‸■} -> \sqrt{2‸} if (atomIndex < self.Count && self[atomIndex] is MathAtom placeholder && placeholder?.AtomType is Enumerations.MathAtomType.Placeholder) { if (placeholder.Superscript is IMathList super) { if (atom.Superscript != null) { super.Append(atom.Superscript); } atom.Superscript = super; } if (placeholder.Subscript is IMathList sub) { if (atom.Subscript != null) { sub.Append(atom.Subscript); } atom.Subscript = sub; } self[atomIndex] = atom; }
internal bool ApplyModifier(string modifier, IMathAtom atom) { if (modifier == "limits") { if (atom is LargeOperator op) { op.Limits = true; } else { SetError(@"\limits can only be applied to an operator"); } return(true); } else if (modifier == "nolimits") { if (atom is LargeOperator op) { op.Limits = false; } else { SetError(@"\nolimits can only be applied to an operator"); } return(true); } return(false); }
public MathList(IMathList cloneMe, bool finalize) : this() { if (!finalize) { foreach (var atom in cloneMe.Atoms) { var cloneAtom = AtomCloner.Clone(atom, finalize); Add(cloneAtom); } } else { IMathAtom prevNode = null; foreach (var atom in cloneMe.Atoms) { var newNode = AtomCloner.Clone(atom, finalize); if (atom.IndexRange == Range.Zero) { int prevIndex = prevNode is null ? 0 : prevNode.IndexRange.Location + prevNode.IndexRange.Length; newNode.IndexRange = new Range(prevIndex, 1); } switch (newNode.AtomType) { case MathAtomType.BinaryOperator: switch (prevNode?.AtomType) { case null: case MathAtomType.BinaryOperator: case MathAtomType.Relation: case MathAtomType.Open: case MathAtomType.Punctuation: case MathAtomType.LargeOperator: newNode.AtomType = MathAtomType.UnaryOperator; break; } break; case MathAtomType.Relation: case MathAtomType.Punctuation: case MathAtomType.Close: if (prevNode != null && prevNode.AtomType == MathAtomType.BinaryOperator) { prevNode.AtomType = MathAtomType.UnaryOperator; } break; case MathAtomType.Number: if (prevNode != null && prevNode.AtomType == MathAtomType.Number && prevNode.Subscript == null && prevNode.Superscript == null) { prevNode.Fuse(newNode); continue; // do not add the new node; we fused it instead. } break; } Add(newNode); prevNode = newNode; } } }
internal IMathAtom BuildTable(string environment, IMathList firstList, bool isRow, char stopChar) { var oldEnv = _currentEnvironment; _currentEnvironment = new TableEnvironmentProperties(environment); int currentRow = 0; int currentColumn = 0; List <List <IMathList> > 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); } IMathAtom table = MathAtoms.Table(_currentEnvironment.Name, rows, out string errorMessage); if (table == null && errorMessage != null) { SetError(errorMessage); return(null); } _currentEnvironment = oldEnv; return(table); }
private static bool IsNotBinaryOperator(IMathAtom prevNode) { if (prevNode == null) { return(true); } switch (prevNode.AtomType) { case MathAtomType.BinaryOperator: case MathAtomType.Relation: case MathAtomType.Open: case MathAtomType.Punctuation: case MathAtomType.LargeOperator: return(true); default: return(false); } }
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); } }
private void CheckAtomTypeAndNucleus(IMathAtom atom, MathAtomType type, string nucleus) { Assert.Equal(type, atom.AtomType); Assert.Equal(nucleus, atom.Nucleus); }
internal static void CheckClone(IMathAtom original, IMathAtom clone) { Assert.Equal(original, clone); Assert.False(ReferenceEquals(original, clone)); }
public TextLineDisplay(List <TextRunDisplay <TFont, TGlyph> > runs, List <IMathAtom> atoms) { Runs = runs; Atoms = new IMathAtom[atoms.Count]; atoms.CopyTo(Atoms); }
public bool IsAtomAllowed(IMathAtom atom) { return(atom != null && atom.AtomType != MathAtomType.Boundary); }
internal static void CheckClone(IMathAtom original, IMathAtom clone) => MathListTest.CheckClone(original, clone);
public bool IsAtomAllowed(IMathAtom atom) => atom != null && atom.AtomType != MathAtomType.Boundary;
public static IMathAtom Table( string environment, List <List <IMathList> > rows, out string errorMessage) { errorMessage = null; var table = new Table(environment) { Cells = rows }; IMathAtom r = null; if (environment != null && _matrixEnvironments.TryGetValue(environment, out var delimiters)) { table.Environment = "matrix"; // TableEnvironment is set to matrix as delimiters are converted to latex outside the table. table.InterColumnSpacing = 18; var style = new Style(LineStyle.Text); foreach (var row in table.Cells) { foreach (var cell in row) { cell.Insert(0, style); } } if (delimiters != null) { var inner = new Inner { LeftBoundary = BoundaryAtom(delimiters.Value.First), RightBoundary = BoundaryAtom(delimiters.Value.Second), InnerList = MathLists.WithAtoms(table) }; r = inner; } else { r = table; } } else if (environment == null) { table.InterRowAdditionalSpacing = 1; for (int i = 0; i < table.NColumns; i++) { table.SetAlignment(ColumnAlignment.Left, i); } r = table; } else if (environment == "eqalign" || environment == "split" || environment == "aligned") { if (table.NColumns != 2) { errorMessage = environment + " environment can have only 2 columns"; } else { // add a spacer before each of the second column elements, in order to create the correct spacing for "=" and other relations. var spacer = Create(MathAtomType.Ordinary, string.Empty); foreach (var row in table.Cells) { if (row.Count > 1) { row[1].Insert(0, spacer); } } table.InterRowAdditionalSpacing = 1; table.SetAlignment(ColumnAlignment.Right, 0); table.SetAlignment(ColumnAlignment.Left, 1); r = table; } } else if (environment == "displaylines" || environment == "gather") { if (table.NColumns != 1) { errorMessage = environment + " environment can only have 1 column."; return(null); } table.InterRowAdditionalSpacing = 1; table.InterColumnSpacing = 0; table.SetAlignment(ColumnAlignment.Center, 0); r = table; } else if (environment == "eqnarray") { if (table.NColumns != 3) { errorMessage = environment + " must have exactly 3 columns."; } else { table.InterRowAdditionalSpacing = 1; table.InterColumnSpacing = 18; table.SetAlignment(ColumnAlignment.Right, 0); table.SetAlignment(ColumnAlignment.Center, 1); table.SetAlignment(ColumnAlignment.Left, 2); r = table; } } else if (environment == "cases") { if (table.NColumns < 1 || table.NColumns > 2) { errorMessage = "cases environment must have 1 to 2 columns"; } else { table.InterColumnSpacing = 18; table.SetAlignment(ColumnAlignment.Left, 0); if (table.NColumns == 2) { table.SetAlignment(ColumnAlignment.Left, 1); } var style = new Style(LineStyle.Text); foreach (var row in table.Cells) { foreach (var cell in row) { cell.Insert(0, style); } } // add delimiters var inner = new Inner { LeftBoundary = BoundaryAtom("{"), RightBoundary = BoundaryAtom(".") }; var space = ForLatexSymbolName(","); inner.InnerList = MathLists.WithAtoms(space, table); r = inner; } } return(r); }
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); }
public static string DelimiterName(IMathAtom boundaryAtom) => boundaryAtom.AtomType == MathAtomType.Boundary && BoundaryDelimiters.TryGetBySecond(boundaryAtom.Nucleus, out var name) ? name : null;
public static void Insert(this IMathList self, MathListIndex index, IMathAtom atom) { index = index ?? MathListIndex.Level0Index(0); if (index.AtomIndex > self.Atoms.Count) { throw new IndexOutOfRangeException($"Index {index.AtomIndex} is out of bounds for list of size {self.Atoms.Count}"); } switch (index.SubIndexType) { case MathListSubIndexType.None: self.Insert(index.AtomIndex, atom); break; case MathListSubIndexType.Nucleus: var currentAtom = self.Atoms[index.AtomIndex]; if (currentAtom.Subscript == null && currentAtom.Superscript == null) { throw new SubIndexTypeMismatchException("Nuclear fusion is not supported if there are neither subscripts nor superscripts in the current atom."); } if (atom.Subscript != null || atom.Superscript != null) { throw new ArgumentException("Cannot fuse with an atom that already has a subscript or a superscript"); } atom.Subscript = currentAtom.Subscript; atom.Superscript = currentAtom.Superscript; currentAtom.Subscript = null; currentAtom.Superscript = null; self.Insert(index.AtomIndex + index.SubIndex?.AtomIndex ?? 0, atom); break; case MathListSubIndexType.Degree: case MathListSubIndexType.Radicand: if (!(self.Atoms[index.AtomIndex] is Radical radical && radical.AtomType == Enumerations.MathAtomType.Radical)) { throw new SubIndexTypeMismatchException($"No radical found at index {index.AtomIndex}"); } if (index.SubIndexType == MathListSubIndexType.Degree) { radical.Degree.Insert(index.SubIndex, atom); } else { radical.Radicand.Insert(index.SubIndex, atom); } break; case MathListSubIndexType.Numerator: case MathListSubIndexType.Denominator: if (!(self.Atoms[index.AtomIndex] is Fraction frac && frac.AtomType == Enumerations.MathAtomType.Fraction)) { throw new SubIndexTypeMismatchException($"No fraction found at index {index.AtomIndex}"); } if (index.SubIndexType == MathListSubIndexType.Numerator) { frac.Numerator.Insert(index.SubIndex, atom); } else { frac.Denominator.Insert(index.SubIndex, atom); } break; case MathListSubIndexType.Subscript: var current = self.Atoms[index.AtomIndex]; if (current.Subscript == null) { throw new SubIndexTypeMismatchException($"No subscript for atom at index {index.AtomIndex}"); } current.Subscript.Insert(index.SubIndex, atom); break; case MathListSubIndexType.Superscript: current = self.Atoms[index.AtomIndex]; if (current.Superscript == null) { throw new SubIndexTypeMismatchException($"No superscript for atom at index {index.AtomIndex}"); } current.Superscript.Insert(index.SubIndex, atom); break; } }