public static void RemoveAt(this MathList self, ref MathListIndex 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.RemoveAt(index.AtomIndex); break; case var _ when index.SubIndex is null: throw new InvalidCodePathException("index.SubIndex is null despite non-None subindex type"); case MathListSubIndexType.BetweenBaseAndScripts: var currentAtom = self.Atoms[index.AtomIndex]; if (currentAtom.Subscript.IsEmpty() && currentAtom.Superscript.IsEmpty()) { throw new SubIndexTypeMismatchException(index); } var downIndex = index.LevelDown(); if (downIndex is null) { throw new InvalidCodePathException("downIndex is null"); } if (index.AtomIndex > 0 && self.Atoms[index.AtomIndex - 1] is MathAtom previous && previous.Subscript.IsEmpty() && previous.Superscript.IsEmpty() && previous switch { Atoms.BinaryOperator _ => false, Atoms.UnaryOperator _ => false, Atoms.Relation _ => false, Atoms.Punctuation _ => false, Atoms.Space _ => false, _ => true }) { previous.Superscript.Append(currentAtom.Superscript); previous.Subscript.Append(currentAtom.Subscript); self.RemoveAt(index.AtomIndex); // it was in the nucleus and we removed it, get out of the nucleus and get in the nucleus of the previous one. index = downIndex.Previous is MathListIndex downPrev ? downPrev.LevelUpWithSubIndex(MathListSubIndexType.BetweenBaseAndScripts, MathListIndex.Level0Index(1)) : downIndex; break; } // insert placeholder since we couldn't place the scripts in previous atom var insertionAtom = LaTeXSettings.Placeholder; insertionAtom.Subscript.Append(currentAtom.Subscript); insertionAtom.Superscript.Append(currentAtom.Superscript); self.RemoveAt(index.AtomIndex); index = downIndex; self.InsertAndAdvance(ref index, insertionAtom, MathListSubIndexType.None); index = index.Previous ?? throw new InvalidCodePathException("Cannot go back after insertion?"); return;
public static MathListIndex IndexForPoint <TFont, TGlyph>(this FractionDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> { // We can be before or after the fraction if (point.X < self.Position.X - PixelDelta) { //We are before the fraction, so return(MathListIndex.Level0Index(self.Range.Location)); } else if (point.X > self.Position.X + self.Width + PixelDelta) { //We are after the fraction return(MathListIndex.Level0Index(self.Range.End)); } if (point.Y > self.LinePosition + PixelDelta) { return(MathListIndex.IndexAtLocation(self.Range.Location, MathListSubIndexType.Numerator, self.Numerator.IndexForPoint(context, point))); } else if (point.Y < self.LinePosition - PixelDelta) { return(MathListIndex.IndexAtLocation(self.Range.Location, MathListSubIndexType.Denominator, self.Denominator.IndexForPoint(context, point))); } if (point.X > self.Position.X + self.Width / 2) { return(MathListIndex.Level0Index(self.Range.End)); } return(MathListIndex.Level0Index(self.Range.Location)); }
public static MathListIndex IndexForPoint <TFont, TGlyph>(this FractionDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> { // We can be before or after the fraction if (point.X < self.Position.X - PixelDelta) { //We are before the fraction, so return(MathListIndex.Level0Index(self.Range.Location)); } else if (point.X > self.Position.X + self.Width + PixelDelta) { //We are after the fraction return(MathListIndex.Level0Index(self.Range.End)); } //We can be either near the numerator or denominator var numeratorDistance = DistanceFromPointToRect(point, self.Numerator.DisplayBounds); var denominatorDistance = DistanceFromPointToRect(point, self.Denominator.DisplayBounds); if (numeratorDistance < denominatorDistance) { return(MathListIndex.IndexAtLocation(self.Range.Location, self.Numerator.IndexForPoint(context, point), MathListSubIndexType.Numerator)); } else { return(MathListIndex.IndexAtLocation(self.Range.Location, self.Denominator.IndexForPoint(context, point), MathListSubIndexType.Denominator)); } }
public static MathListIndex IndexForPoint <TFont, TGlyph>(this RadicalDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> { // We can be before or after the radical if (point.X < self.Position.X - PixelDelta) { //We are before the radical, so return(MathListIndex.Level0Index(self.Range.Location)); } else if (point.X > self.Position.X + self.Width + PixelDelta) { //We are after the radical return(MathListIndex.Level0Index(self.Range.End)); } //We can be either near the degree or the radicand var degreeRect = self.Degree != null ? new RectangleF(self.Degree.Position, self.Degree.DisplayBounds.Size) : default; var radicandRect = new RectangleF(self.Radicand.Position, self.Radicand.DisplayBounds.Size); var degreeDistance = DistanceFromPointToRect(point, degreeRect); var radicandDistance = DistanceFromPointToRect(point, radicandRect); if (degreeDistance < radicandDistance) { if (self.Degree != null) { return(MathListIndex.IndexAtLocation(self.Range.Location, MathListSubIndexType.Degree, self.Degree.IndexForPoint(context, point))); } return(MathListIndex.Level0Index(self.Range.Location)); } else { return(MathListIndex.IndexAtLocation(self.Range.Location, MathListSubIndexType.Radicand, self.Radicand.IndexForPoint(context, point))); } }
public static MathListIndex IndexForPoint <TFont, TGlyph>(this TextLineDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> { // Convert the point to the reference of the CTLine var relativePoint = new PointF(point.X - self.Position.X, point.Y - self.Position.Y); var runsAndIndicies = self.Runs .Select(run => ValueTuple.Create(run, run.Run.GlyphIndexForXOffset(context, relativePoint.Plus(run.Position).X))) .Where(x => x.Item2.HasValue) .ToArray(); if (runsAndIndicies.Length == 0) { return(null); } var(r, nindex) = runsAndIndicies.Single(); var index = nindex.GetValueOrDefault(); var diffLng = r.Run.Length != r.Range.Length; if (index < 0 || (!diffLng && index > self.Range.Length) || (diffLng && index > r.Run.Length)) { throw new InvalidCodePathException($"Returned index out of range: {index}, range ({self.Range.Location}, {self.Range.Length})"); } if (!diffLng) { return(MathListIndex.Level0Index(self.Range.Location + index)); } if (index > r.Run.Length / 2) { return(MathListIndex.Level0Index(self.Range.End)); } return(MathListIndex.Level0Index(self.Range.Location)); }
public static MathListIndex IndexForPoint <TFont, TGlyph>(this RadicalDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> { // We can be before or after the radical if (point.X < self.Position.X - PixelDelta) { //We are before the radical, so return(MathListIndex.Level0Index(self.Range.Location)); } else if (point.X > self.Position.X + self.Width + PixelDelta) { //We are after the radical return(MathListIndex.Level0Index(self.Range.End)); } //We can be either near the degree or the radicand var degreeDistance = DistanceFromPointToRect(point, self.Degree?.DisplayBounds ?? default); var radicandDistance = DistanceFromPointToRect(point, self.Radicand.DisplayBounds); if (degreeDistance < radicandDistance) { return(MathListIndex.IndexAtLocation(self.Range.Location, self.Degree.IndexForPoint(context, point), MathListSubIndexType.Numerator)); } else { return(MathListIndex.IndexAtLocation(self.Range.Location, self.Radicand.IndexForPoint(context, point), MathListSubIndexType.Denominator)); } }
public static MathListIndex IndexForPoint <TFont, TGlyph>( this IGlyphDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> _, PointF point) where TFont : IFont <TGlyph> => point.X > self.Position.X + self.Width / 2 ? MathListIndex.Level0Index(self.Range.End) : MathListIndex.Level0Index(self.Range.Location);
public static void HighlightCharacterAt <TFont, TGlyph>( this IGlyphDisplay <TFont, TGlyph> self, MathListIndex index, Color color) where TFont : IFont <TGlyph> { if (index.SubIndexType != MathListSubIndexType.None) { throw new ArgumentException ("The subindex must be none to get the highlight a character in it.", nameof(index)); } self.Highlight(color); }
public static PointF?PointForIndex <TFont, TGlyph>( this IGlyphDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> _, MathListIndex index) where TFont : IFont <TGlyph> => index.SubIndexType != MathListSubIndexType.None ? throw new ArgumentException ("The subindex must be none to get the closest point for it.", nameof(index)) : index.AtomIndex == self.Range.End // draw a caret after the glyph ? self.Position.Plus(new PointF(self.DisplayBounds().Right, 0)) // draw a caret before the glyph : self.Position;
public static PointF?PointForIndex <TFont, TGlyph>(this ListDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, MathListIndex index) where TFont : IFont <TGlyph> { if (index is null) { return(null); } PointF?position; if (index.AtomIndex == self.Range.End) { // Special case the edge of the range position = new PointF(self.Width, 0); } else if (self.Range.Contains(index.AtomIndex) && self.SubDisplayForIndex(index) is IDisplay <TFont, TGlyph> display) { switch (index.SubIndexType) { case MathListSubIndexType.Nucleus: var nucleusPosition = index.AtomIndex + index.SubIndex.AtomIndex; position = display.PointForIndex(context, MathListIndex.Level0Index(nucleusPosition)); break; case MathListSubIndexType.None: position = display.PointForIndex(context, index); break; default: // Recurse position = display.PointForIndex(context, index.SubIndex); break; } } else { // Outside the range return(null); } if (position is PointF found) { // Convert bounds from our coordinate system before returning found.X += self.Position.X; found.Y += self.Position.Y; return(found); } else { // We didn't find the position return(null); } }
public static MathListIndex IndexForPoint <TFont, TGlyph>( this InnerDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> => // We can be before or after the inner point.X <self.Position.X + (self.Left?.Width / 2 ?? 0) //We are before the inner, so ? MathListIndex.Level0Index(self.Range.Location) : point.X> self.Position.X + self.Width - (self.Right?.Width / 2 ?? 0) //We are after the inner ? MathListIndex.Level0Index(self.Range.End) : MathListIndex.IndexAtLocation(self.Range.Location, MathListSubIndexType.Inner, self.Inner.IndexForPoint(context, point));
public static MathListIndex IndexForPoint <TFont, TGlyph>( this LargeOpLimitsDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> => // We can be before or after the large operator point.X <self.Position.X - PixelDelta // We are before the large operator, so ?MathListIndex.Level0Index(self.Range.Location) : point.X> self.Position.X + self.Width + PixelDelta // We are after the large operator ? MathListIndex.Level0Index(self.Range.End) : self.UpperLimit is {
public static MathListIndex IndexForPoint <TFont, TGlyph>( this RadicalDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> => // We can be before or after the radical point.X <self.Position.X - PixelDelta //We are before the radical, so ?MathListIndex.Level0Index(self.Range.Location) : point.X> self.Position.X + self.Width + PixelDelta //We are after the radical ? MathListIndex.Level0Index(self.Range.End) //We can be either near the degree or the radicand : DistanceFromPointToRect(point, self.Degree != null ? new RectangleF(self.Degree.Position, self.Degree.DisplayBounds().Size) : default) < DistanceFromPointToRect(point, new RectangleF(self.Radicand.Position, self.Radicand.DisplayBounds().Size)) ? self.Degree != null ? MathListIndex.IndexAtLocation(self.Range.Location, MathListSubIndexType.Degree, self.Degree.IndexForPoint(context, point)) : MathListIndex.Level0Index(self.Range.Location) : MathListIndex.IndexAtLocation(self.Range.Location, MathListSubIndexType.Radicand, self.Radicand.IndexForPoint(context, point));
static void InsertAtAtomIndexAndAdvance(this MathList self, int atomIndex, MathAtom 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 Atoms.Placeholder placeholder) { atom.Superscript.Append(placeholder.Superscript); atom.Subscript.Append(placeholder.Subscript); self[atomIndex] = atom; } else { self.Insert(atomIndex, atom); } advance = advanceType switch { MathListSubIndexType.None => advance.Next, _ => advance.LevelUpWithSubIndex(advanceType, MathListIndex.Level0Index(0)), }; }
public static IMathAtom AtomAt(this IMathList self, MathListIndex index) { if (index is null || index.AtomIndex >= self.Atoms.Count) { return(null); } var atom = self.Atoms[index.AtomIndex]; switch (index.SubIndexType) { case MathListSubIndexType.None: case MathListSubIndexType.Nucleus: return(atom); case MathListSubIndexType.Subscript: return(atom.Subscript.AtomAt(index.SubIndex)); case MathListSubIndexType.Superscript: return(atom.Superscript.AtomAt(index.SubIndex)); case MathListSubIndexType.Radicand: case MathListSubIndexType.Degree: if (atom is Radical radical && radical.AtomType == Enumerations.MathAtomType.Radical) { if (index.SubIndexType == MathListSubIndexType.Degree) { return(radical.Degree.AtomAt(index.SubIndex)); } else { return(radical.Radicand.AtomAt(index.SubIndex)); } } else { return(null); }
public static MathListIndex IndexForPoint <TFont, TGlyph>(this TextLineDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> { // Convert the point to the reference of the CTLine var relativePoint = new PointF(point.X - self.Position.X, point.Y - self.Position.Y); var indices = self.Runs.Select(run => run.Run.GlyphIndexForXOffset(context, relativePoint.Plus(run.Position).X)).Where(x => x.HasValue); if (indices.IsEmpty()) { return(null); } var index = indices.Single().GetValueOrDefault(); // The index returned is in UTF-16, translate to codepoint index. // NSUInteger codePointIndex = stringIndexToCodePointIndex(self.attributedString.string, index); // Convert the code point index to an index into the mathlist var mlIndex = self.StringIndexToMathListIndex(index); // index will be between 0 and _range.length inclusive if (mlIndex < 0 || mlIndex > self.Range.Length) { throw new InvalidCodePathException($"Returned index out of range: {index}, range ({self.Range.Location}, {self.Range.Length})"); } // translate to the current index return(MathListIndex.Level0Index(self.Range.Location + mlIndex)); }
public SubIndexTypeMismatchException(Type atomType, MathListIndex index) : base( $"{atomType} not found at index {index.AtomIndex}.") { }
public static PointF?PointForIndex <TFont, TGlyph>(this FractionDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, MathListIndex index) where TFont : IFont <TGlyph> { if (index.SubIndexType != MathListSubIndexType.None) { throw Arg("The subindex must be none to get the closest point for it.", nameof(index)); } // draw a caret after the fraction return(new PointF(self.DisplayBounds.Right, self.Position.Y)); }
public static MathListIndex?IndexForPoint <TFont, TGlyph> (this ListDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> { // The origin of for the subelements of a MathList is the current position, // so translate the current point to our origin. var translatedPoint = new PointF(point.X - self.Position.X, point.Y - self.Position.Y); IDisplay <TFont, TGlyph>?closest = null; var xbounds = new List <IDisplay <TFont, TGlyph> >(); float minDistance = float.MaxValue; foreach (var display in self.Displays) { var bounds = display.DisplayBounds(); var rect = new RectangleF(display.Position, bounds.Size); var maxBoundsX = rect.Right; if (rect.X - PixelDelta <= translatedPoint.X && translatedPoint.X <= maxBoundsX + PixelDelta) { xbounds.Add(display); } var distance = DistanceFromPointToRect(translatedPoint, rect); if (distance < minDistance) { closest = display; minDistance = distance; } } IDisplay <TFont, TGlyph>?displayWithPoint; switch (xbounds.Count) { case 0: if (translatedPoint.X <= -PixelDelta) { // All the way to the left return(self.Range.Location < 0 ? null : MathListIndex.Level0Index(self.Range.Location)); } else if (translatedPoint.X >= self.Width + PixelDelta) { // if closest is a script if (closest is ListDisplay <TFont, TGlyph> ld && ld.LinePosition != LinePosition.Regular) { // then we try to find its parent var parent = self.Displays.FirstOrDefault(d => d.HasScript && d.Range.Contains(ld.IndexInParent)); if (parent != null) { return(MathListIndex.Level0Index(parent.Range.End)); } } // All the way to the right return (self.Range.End < 0 ? null : self.Displays.Count == 1 && self.Displays[0] is TextLineDisplay <TFont, TGlyph> { Atoms : var atoms } && atoms.Count == 1 && atoms[0] is Atom.Atoms.Placeholder ? MathListIndex.Level0Index(self.Range.Location) : MathListIndex.Level0Index(self.Range.End)); }
public static MathListIndex IndexForPoint <TFont, TGlyph>(this ListDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, PointF point) where TFont : IFont <TGlyph> { // The origin of for the subelements of a MathList is the current position, so translate the current point to our origin. var translatedPoint = new PointF(point.X - self.Position.X, point.Y - self.Position.Y); IDisplay <TFont, TGlyph> closest = null; var xbounds = new List <IDisplay <TFont, TGlyph> >(); float minDistance = float.MaxValue; foreach (var display in self.Displays) { var bounds = display.DisplayBounds; var rect = new RectangleF(display.Position, bounds.Size); var maxBoundsX = rect.Right; if (rect.X - PixelDelta <= translatedPoint.X && translatedPoint.X <= maxBoundsX + PixelDelta) { xbounds.Add(display); } var distance = DistanceFromPointToRect(translatedPoint, rect); if (distance < minDistance) { closest = display; minDistance = distance; } } IDisplay <TFont, TGlyph> displayWithPoint; switch (xbounds.Count) { case 0: if (translatedPoint.X <= -PixelDelta) { // All the way to the left return(self.Range.Location < 0 ? null : MathListIndex.Level0Index(self.Range.Location)); } else if (translatedPoint.X >= self.Width + PixelDelta) { // if closest is a script if (closest != null && closest is ListDisplay <TFont, TGlyph> ld && ld.LinePosition != Enumerations.LinePosition.Regular) { // then we try to find its parent var parent = self.Displays.FirstOrDefault(d => d.HasScript && d.Range.Contains(ld.IndexInParent)); if (parent != null) { return(MathListIndex.Level0Index(parent.Range.End)); } } // All the way to the right return(self.Range.End < 0 ? null : MathListIndex.Level0Index(self.Range.End)); } else { // It is within the ListDisplay but not within the X bounds of any sublist. Use the closest in that case. displayWithPoint = closest; } break; case 1: displayWithPoint = xbounds[0]; var rect = new RectangleF(displayWithPoint.Position, displayWithPoint.DisplayBounds.Size); if (translatedPoint.X >= self.Width - PixelDelta) { //The point is close to the end. Only use the selected X bounds if the Y is within range. if (translatedPoint.Y <= rect.YMin() - PixelDelta) { //The point is less than the Y including the delta. Move the cursor to the end rather than in this atom. return(MathListIndex.Level0Index(self.Range.End)); } } break; default: //Use the closest since there are more than 2 sublists which have this X position. displayWithPoint = closest; break; } if (displayWithPoint is null) { return(null); } var index = displayWithPoint.IndexForPoint(context, translatedPoint); if (displayWithPoint is ListDisplay <TFont, TGlyph> closestLine) { if (closestLine.LinePosition is Enumerations.LinePosition.Regular) { throw Arg($"{nameof(ListDisplay<TFont, TGlyph>)} {nameof(ListDisplay<TFont, TGlyph>.LinePosition)} {nameof(Enumerations.LinePosition.Regular)} " + $"inside an {nameof(ListDisplay<TFont, TGlyph>)} - shouldn't happen", nameof(self)); } // This is a subscript or a superscript, return the right type of subindex var indexType = closestLine.LinePosition is Enumerations.LinePosition.Subscript ? MathListSubIndexType.Subscript : MathListSubIndexType.Superscript; // The index of the atom this denotes. if (closestLine.IndexInParent is int.MinValue) { throw Arg($"Index was not set for a {indexType} in the {nameof(ListDisplay<TFont, TGlyph>)}.", nameof(self)); } return(MathListIndex.IndexAtLocation(closestLine.IndexInParent, indexType, index)); } else if (displayWithPoint.HasScript) { //The display list has a subscript or a superscript. If the index is at the end of the atom, then we need to put it before the sub/super script rather than after. if (index?.AtomIndex == displayWithPoint.Range.End) { return(MathListIndex.IndexAtLocation(index.AtomIndex - 1, MathListSubIndexType.BetweenBaseAndScripts, MathListIndex.Level0Index(1))); } } return(index); }
public static PointF?PointForIndex <TFont, TGlyph>(this RadicalDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, MathListIndex index) where TFont : IFont <TGlyph> { if (index.SubIndexType != MathListSubIndexType.None) { throw Arg("The subindex must be none to get the closest point for it.", nameof(index)); } if (index.AtomIndex == self.Range.End) { // draw a caret after the radical return(self.Position.Plus(new PointF(self.DisplayBounds.Right, 0))); } // draw a caret before the radical return(self.Position); }
///<summary>The bounds of the display indicated by the given index</summary> public static PointF?PointForIndex <TFont, TGlyph>(this IDisplay <TFont, TGlyph> display, FrontEnd.TypesettingContext <TFont, TGlyph> context, MathListIndex index) where TFont : IFont <TGlyph> { switch (display) { case TextLineDisplay <TFont, TGlyph> text: return(text.PointForIndex(context, index)); case FractionDisplay <TFont, TGlyph> frac: return(frac.PointForIndex(context, index)); case RadicalDisplay <TFont, TGlyph> radical: return(radical.PointForIndex(context, index)); case ListDisplay <TFont, TGlyph> list: return(list.PointForIndex(context, index)); default: return(null); } }
public static void HighlightCharacterAt <TFont, TGlyph>(this IDisplay <TFont, TGlyph> display, MathListIndex index, Structures.Color color) where TFont : IFont <TGlyph> { switch (display) { case TextLineDisplay <TFont, TGlyph> text: text.HighlightCharacterAt(index, color); break; case FractionDisplay <TFont, TGlyph> frac: frac.HighlightCharacterAt(index, color); break; case RadicalDisplay <TFont, TGlyph> radical: radical.HighlightCharacterAt(index, color); break; case ListDisplay <TFont, TGlyph> list: list.HighlightCharacterAt(index, color); break; default: break; } }
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; }
public static PointF?PointForIndex <TFont, TGlyph>(this ListDisplay <TFont, TGlyph> self, TypesettingContext <TFont, TGlyph> context, MathListIndex index) where TFont : IFont <TGlyph> { if (index is null) { return(null); } PointF?position = null; var nonScripted = self.Displays .Where(d => !(d is ListDisplay <TFont, TGlyph> ld && ld.LinePosition != Enumerations.LinePosition.Regular)) .ToArray(); if (index.SubIndexType == MathListSubIndexType.None && nonScripted.Length > 0 && nonScripted.All(d => d.Range.End <= index.AtomIndex)) { position = new PointF(self.Width, 0); } else { if (index.AtomIndex == self.Range.End) { // Special case the edge of the range position = new PointF(self.Width, 0); } else if (self.Range.Contains(index.AtomIndex) && self.SubDisplayForIndex(index) is IDisplay <TFont, TGlyph> display) { switch (index.SubIndexType) { case MathListSubIndexType.BetweenBaseAndScripts: var nucleusPosition = index.AtomIndex + index.SubIndex.AtomIndex; position = display.PointForIndex(context, MathListIndex.Level0Index(nucleusPosition)); break; case MathListSubIndexType.None: if (!display.HasScript) { position = display.PointForIndex(context, index); } else { var mainPosition = display.PointForIndex(context, index); position = self.Displays.SingleOrDefault(d => d is ListDisplay <TFont, TGlyph> ld && ld.IndexInParent == index.AtomIndex - 1) is IDisplay <TFont, TGlyph> scripted && mainPosition != null ? new PointF(mainPosition.Value.X + scripted.Width, 0) : mainPosition; } break; default: // Recurse position = display.PointForIndex(context, index.SubIndex); break; } } else { // Outside the range return(null); } } if (position is PointF found) { // Convert bounds from our coordinate system before returning found.X += self.Position.X; found.Y += self.Position.Y; return(found); } else { // We didn't find the position return(null); } }
public static void HighlightCharacterAt <TFont, TGlyph>(this ListDisplay <TFont, TGlyph> self, MathListIndex index, Color color) where TFont : IFont <TGlyph> { if (index is null) { return; } if (self.Range.Contains(index.AtomIndex) && self.SubDisplayForIndex(index) is IDisplay <TFont, TGlyph> display) { if (index.SubIndexType is MathListSubIndexType.BetweenBaseAndScripts || index.SubIndexType is MathListSubIndexType.None) { display.HighlightCharacterAt(index, color); } else { // Recurse display.HighlightCharacterAt(index.SubIndex, color); } } }
/// <summary>Inserts <paramref name="atom"/> and modifies <paramref name="index"/> to advance to the next position.</summary> public static void InsertAndAdvance(this MathList self, ref MathListIndex index, MathAtom atom, MathListSubIndexType advanceType) { 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.InsertAtAtomIndexAndAdvance(index.AtomIndex, atom, ref index, advanceType); break; case var _ when index.SubIndex is null: throw new InvalidCodePathException("index.SubIndex is null despite non-None subindex type"); case MathListSubIndexType.BetweenBaseAndScripts: var currentAtom = self.Atoms[index.AtomIndex]; if (currentAtom.Subscript.IsEmpty() && currentAtom.Superscript.IsEmpty()) { throw new SubIndexTypeMismatchException(index); } if (atom.Subscript.IsNonEmpty() || atom.Superscript.IsNonEmpty()) { throw new ArgumentException("Cannot fuse with an atom that already has a subscript or a superscript"); } atom.Subscript.Append(currentAtom.Subscript); atom.Superscript.Append(currentAtom.Superscript); currentAtom.Subscript.Clear(); currentAtom.Superscript.Clear(); var atomIndex = index.AtomIndex; // Prevent further subindexing inside BetweenBaseAndScripts if (advanceType != MathListSubIndexType.None && index.LevelDown() is MathListIndex levelDown) { index = levelDown.Next; } self.InsertAtAtomIndexAndAdvance(atomIndex + 1, atom, ref index, advanceType); break; case MathListSubIndexType.Degree: case MathListSubIndexType.Radicand: if (!(self.Atoms[index.AtomIndex] is Atoms.Radical radical)) { throw new SubIndexTypeMismatchException(typeof(Atoms.Radical), index); } if (index.SubIndexType == MathListSubIndexType.Degree) { radical.Degree.InsertAndAdvance(ref index.SubIndex, atom, advanceType); } else { radical.Radicand.InsertAndAdvance(ref index.SubIndex, atom, advanceType); } break; case MathListSubIndexType.Numerator: case MathListSubIndexType.Denominator: if (!(self.Atoms[index.AtomIndex] is Atoms.Fraction frac)) { throw new SubIndexTypeMismatchException(typeof(Atoms.Fraction), index); } if (index.SubIndexType == MathListSubIndexType.Numerator) { frac.Numerator.InsertAndAdvance(ref index.SubIndex, atom, advanceType); } else { frac.Denominator.InsertAndAdvance(ref index.SubIndex, atom, advanceType); } break; case MathListSubIndexType.Subscript: self.Atoms[index.AtomIndex].Subscript.InsertAndAdvance(ref index.SubIndex, atom, advanceType); break; case MathListSubIndexType.Superscript: self.Atoms[index.AtomIndex].Superscript.InsertAndAdvance(ref index.SubIndex, atom, advanceType); break; case MathListSubIndexType.Inner: if (!(self.Atoms[index.AtomIndex] is Atoms.Inner inner)) { throw new SubIndexTypeMismatchException(typeof(Atoms.Inner), index); } inner.InnerList.InsertAndAdvance(ref index.SubIndex, atom, advanceType); break; default: throw new SubIndexTypeMismatchException(index); } }
public static IDisplay <TFont, TGlyph> SubDisplayForIndex <TFont, TGlyph>(this ListDisplay <TFont, TGlyph> self, MathListIndex index) where TFont : IFont <TGlyph> { // Inside the range if (index.SubIndexType is MathListSubIndexType.Superscript || index.SubIndexType is MathListSubIndexType.Subscript) { foreach (var display in self.Displays) { if (display is ListDisplay <TFont, TGlyph> list && index.AtomIndex == list.IndexInParent && // This is the right character for the sub/superscript, check that it's type matches the index ((list.LinePosition is Enumerations.LinePosition.Subscript && index.SubIndexType is MathListSubIndexType.Subscript) || (list.LinePosition is Enumerations.LinePosition.Superscript && index.SubIndexType is MathListSubIndexType.Superscript))) { return(list); } else { } }
public SubIndexTypeMismatchException(MathListIndex index) : base( Array.IndexOf(typeof(MathListSubIndexType).GetEnumValues(), index.SubIndexType) == -1 ? $"{index.SubIndexType} is an invalid subindex type." : $"{index.SubIndexType} not found at index {index.AtomIndex}.") { }
public static void RemoveAt(this IMathList self, MathListIndex index) { 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.RemoveAt(index.AtomIndex); break; case MathListSubIndexType.Nucleus: var currentAtom = self.Atoms[index.AtomIndex]; if (currentAtom.Subscript == null && currentAtom.Superscript == null) { throw new SubIndexTypeMismatchException("Nuclear fission is not supported if there are no subscripts or superscripts."); } if (index.AtomIndex > 0) { var previous = self.Atoms[index.AtomIndex - 1]; if (previous.Subscript != null && previous.Superscript != null) { previous.Superscript = currentAtom.Superscript; previous.Subscript = currentAtom.Subscript; self.RemoveAt(index.AtomIndex); break; } } // no previous atom or the previous atom sucks (has sub/super scripts) currentAtom.Nucleus = ""; break; case MathListSubIndexType.Radicand: case MathListSubIndexType.Degree: 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.RemoveAt(index.SubIndex); } else { radical.Radicand.RemoveAt(index.SubIndex); } 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.RemoveAt(index.SubIndex); } else { frac.Denominator.RemoveAt(index.SubIndex); } 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.RemoveAt(index.SubIndex); 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.RemoveAt(index.SubIndex); break; } }