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 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 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 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 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 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 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 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 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 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)); }
/// <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 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 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; } }
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 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; } }