/// <summary> /// Creates a tree of <see cref="InlineSpan"/> from its array. /// </summary> /// <param name="spans">The array of <see cref="InlineSpan"/>.</param> /// <param name="rootLength">The length of the whole text.</param> /// <returns>The tree of <see cref="InlineSpan"/> which is equivalent to <paramref name="spans"/>.</returns> private static InlineSpan GetInlineTree(SortedList <int, InlineSpan> spans, int rootLength) { var root = new InlineSpan { Begin = 0, End = rootLength, SpanType = InlineSpanType.Root, }; InlineSpan currentSpan = root; foreach (var item in spans) { var delimSpan = item.Value; while (currentSpan.End < delimSpan.End) { currentSpan = currentSpan.Parent ?? throw new Exception(); } currentSpan.Children.Add(delimSpan.Begin, delimSpan); delimSpan.Parent = currentSpan; currentSpan = delimSpan; } return(root); }
void _extractPlaceholderSpans(InlineSpan span) { _placeholderSpans = new List <PlaceholderSpan>(); span.visitChildren((InlineSpan inlinespan) => { if (inlinespan is PlaceholderSpan) { PlaceholderSpan placeholderSpan = (PlaceholderSpan)inlinespan; _placeholderSpans.Add(placeholderSpan); } return(true); }); }
/// <summary> /// Converts the specified string and <see cref="InlineSpan"/> tree to inline elements. /// </summary> /// <param name="text">The string object.</param> /// <param name="delim">The <see cref="InlineSpan"/> tree.</param> /// <returns>The inline elements which is equivalent to <paramref name="delim"/>.</returns> private InlineElement[] ToInlines(string text, InlineSpan delim) { int lastEnd = delim.ParseBegin; List <InlineElement> newChildren = new List <InlineElement>(); foreach (var item in delim.Children.Where(d => d.Value.End <= delim.ParseEnd)) { InlineSpan delimSpan = item.Value; if (lastEnd < delimSpan.Begin) { newChildren.AddRange( ParseLineBreak(text.Substring(lastEnd, delimSpan.Begin - lastEnd))); } newChildren.AddRange(ToInlines(text, delimSpan)); lastEnd = delimSpan.End; } if (lastEnd < delim.ParseEnd) { newChildren.AddRange( ParseLineBreak(text.Substring(lastEnd, delim.ParseEnd - lastEnd))); } switch (delim.SpanType) { case InlineSpanType.Link: return(new InlineElement[] { new Link(newChildren.ToArray(), delim.Destination, delim.Title, parserConfig) }); case InlineSpanType.Image: return(new InlineElement[] { new Image(newChildren.ToArray(), delim.Destination, delim.Title, parserConfig) }); case InlineSpanType.Emphasis: return(new InlineElement[] { new Emphasis(newChildren.ToArray(), false, parserConfig) }); case InlineSpanType.StrongEmphasis: return(new InlineElement[] { new Emphasis(newChildren.ToArray(), true, parserConfig) }); case InlineSpanType.Root: return(newChildren.ToArray()); default: return(new[] { delim.DelimElem }); } }
public override RenderComparison compareTo(InlineSpan other) { if (this == other) { return(RenderComparison.identical); } if (other.GetType() != GetType()) { return(RenderComparison.layout); } if ((style == null) != (other.style == null)) { return(RenderComparison.layout); } WidgetSpan typedOther = other as WidgetSpan; if (!child.Equals(typedOther.child) || alignment != typedOther.alignment) { return(RenderComparison.layout); } RenderComparison result = RenderComparison.identical; if (style != null) { RenderComparison candidate = style.compareTo(other.style); if ((int)candidate > (int)result) { result = candidate; } if (result == RenderComparison.layout) { return(result); } } return(result); }
public override void handleEvent(PointerEvent evt, HitTestEntry entry) { D.assert(debugHandleEvent(evt, entry)); if (!(evt is PointerDownEvent)) { return; } _layoutTextWithConstraints(constraints); Offset offset = ((BoxHitTestEntry)entry).localPosition; TextPosition position = _textPainter.getPositionForOffset(offset); InlineSpan span = _textPainter.text.getSpanForPosition(position); if (span == null) { return; } if (span is TextSpan) { TextSpan textSpan = (TextSpan)span; textSpan.recognizer?.addPointer(evt as PointerDownEvent); } }
public RenderParagraph( InlineSpan text = null, TextAlign textAlign = TextAlign.start, TextDirection textDirection = TextDirection.ltr, bool softWrap = true, TextOverflow overflow = TextOverflow.clip, float textScaleFactor = 1.0f, int?maxLines = null, Locale locale = null, StrutStyle strutStyle = null, TextWidthBasis textWidthBasis = TextWidthBasis.parent, ui.TextHeightBehavior textHeightBehavior = null, List <RenderBox> children = null ) { D.assert(maxLines == null || maxLines > 0); D.assert(text != null); D.assert(text.debugAssertIsValid()); D.assert(maxLines == null || maxLines > 0); _softWrap = softWrap; _overflow = overflow; _textPainter = new TextPainter( text: text, textAlign: textAlign, textDirection: textDirection, textScaleFactor: textScaleFactor, maxLines: maxLines, ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null, locale: locale, strutStyle: strutStyle, textWidthBasis: textWidthBasis, textHeightBehavior: textHeightBehavior ); addAll(children); _extractPlaceholderSpans(text); }
/// <summary> /// Parses inline elements and returns them. /// </summary> /// <param name="text">The text to parse.</param> /// <returns>Inline elements in <paramref name="text"/>.</returns> public IEnumerable <InlineElement> ParseInlineElements(string text) { var highPriorityDelims = new List <InlineSpan>(); int currentIndex = 0; int nextBacktick = GetNextUnescaped(text, '`', 0); int nextLessThan = GetNextUnescaped(text, '<', 0); // Extract code spans, raw html and auto links. while (currentIndex < text.Length) { int newIndex; int nextElemIndex; InlineElement newInline; // Find ` // Search code span if (nextBacktick >= 0 && (nextLessThan < 0 || nextBacktick < nextLessThan)) { nextElemIndex = nextBacktick; newIndex = nextElemIndex; newInline = GetCodeSpan(text, nextBacktick, ref newIndex); } // Find < // Search raw html and auto links else if (nextLessThan >= 0 && (nextBacktick < 0 || nextLessThan < nextBacktick)) { nextElemIndex = nextLessThan; newIndex = nextElemIndex; newInline = GetInlineHtmlOrLink(text, nextLessThan, ref newIndex); } else // Find neither ` nor < { // End Searching break; } if (newInline != null) { var span = new InlineSpan { Begin = nextElemIndex, End = newIndex, SpanType = ToDelimType(newInline.Type), DelimElem = newInline, }; highPriorityDelims.Add(span); currentIndex = newIndex; nextBacktick = GetNextUnescaped(text, '`', currentIndex); nextLessThan = GetNextUnescaped(text, '<', currentIndex); } else { nextElemIndex += CountSameChars(text, nextElemIndex); nextBacktick = GetNextUnescaped(text, '`', nextElemIndex); nextLessThan = GetNextUnescaped(text, '<', nextElemIndex); } } return(ParseLinkEmphasis(text, highPriorityDelims)); }
/// <summary> /// Parses the structure and add the <see cref="InlineSpan"/> to <paramref name="delimSpans"/>. /// </summary> /// <param name="deliminators">The list of <see cref="DeliminatorInfo"/>.</param> /// <param name="delimSpans">The list of <see cref="InlineSpan"/>.</param> /// <param name="stackBottom">The stack bottom.</param> private static void ParseEmphasis(LinkedList <DeliminatorInfo> deliminators, SortedList <int, InlineSpan> delimSpans, DeliminatorInfo stackBottom) { if (deliminators.Count == 0) { return; } LinkedListNode <DeliminatorInfo> firstClose = null; LinkedListNode <DeliminatorInfo> infoNode = stackBottom == null ? deliminators.First : deliminators.Find(stackBottom); while (infoNode != null) { if (infoNode.Value.CanClose) { firstClose = infoNode; break; } infoNode = infoNode.Next; } if (firstClose == null) { return; } LinkedListNode <DeliminatorInfo> startDelimNode = null; infoNode = firstClose; while ((infoNode = infoNode.Previous) != null && infoNode.Value != stackBottom) { if (infoNode.Value.CanOpen && infoNode.Value.Type == firstClose.Value.Type && ((infoNode.Value.DeliminatorLength + firstClose.Value.DeliminatorLength) % 3 != 0 || infoNode.Value.DeliminatorLength % 3 == 0 || (!firstClose.Value.CanOpen && !infoNode.Value.CanClose))) { startDelimNode = infoNode; break; } } if (startDelimNode == null) { if (!firstClose.Value.CanOpen) { deliminators.Remove(firstClose); } firstClose = firstClose.Next; if (firstClose == null) { return; } ParseEmphasis(deliminators, delimSpans, firstClose.Value); return; } DeliminatorInfo openInfo = startDelimNode.Value; DeliminatorInfo closeInfo = firstClose.Value; int delimLength = openInfo.DeliminatorLength > 1 && closeInfo.DeliminatorLength > 1 ? 2 : 1; var delimSpan = new InlineSpan { Begin = openInfo.Index + openInfo.DeliminatorLength - delimLength, End = closeInfo.Index + delimLength, SpanType = delimLength > 1 ? InlineSpanType.StrongEmphasis : InlineSpanType.Emphasis, }; delimSpan.ParseBegin = delimSpan.Begin + delimLength; delimSpan.ParseEnd = delimSpan.End - delimLength; delimSpans.Add(delimSpan.Begin, delimSpan); while ((infoNode = infoNode.Next) != null && infoNode != firstClose) { deliminators.Remove(infoNode); } openInfo.DeliminatorLength -= delimLength; if (openInfo.DeliminatorLength <= 0) { deliminators.Remove(openInfo); } closeInfo.DeliminatorLength -= delimLength; closeInfo.Index += delimLength; if (closeInfo.DeliminatorLength <= 0) { deliminators.Remove(closeInfo); } ParseEmphasis(deliminators, delimSpans, deliminators.Find(firstClose.Value) == null ? firstClose.Next?.Value : firstClose.Value); }
/// <summary> /// Parses inline elements to Links, Images Emphasis and Line breaks. /// </summary> /// <param name="text">The string object to @arse.</param> /// <param name="higherSpans">Delim Spans which represents higher priority.</param> /// <returns>The parse result.</returns> private IEnumerable <InlineElement> ParseLinkEmphasis(string text, List <InlineSpan> higherSpans) { var deliminators = new LinkedList <DeliminatorInfo>(); var inlineSpans = new SortedList <int, InlineSpan>(); for (int i = 0; i < text.Length; i++) { // If the character is escaped or contained in higherSpans if (IsEscaped(text, i) || higherSpans.Any(d => d.Begin <= i && d.End > i)) { continue; } if (text[i] == '*') { int length = CountSameChars(text, i); var delimInfo = DeliminatorInfo.Create(DeliminatorType.Star, i, length); delimInfo.CanOpen = IsLeftFlanking(text, delimInfo); delimInfo.CanClose = IsRightFlanking(text, delimInfo); deliminators.AddLast(delimInfo); i += length - 1; } else if (text[i] == '_') { int length = CountSameChars(text, i); var delimInfo = DeliminatorInfo.Create(DeliminatorType.UnderBar, i, length); bool leftFranking = IsLeftFlanking(text, delimInfo); bool rightFlanking = IsRightFlanking(text, delimInfo); delimInfo.CanOpen = leftFranking && (!rightFlanking || IsPrecededByPunctuation(text, delimInfo)); delimInfo.CanClose = rightFlanking && (!leftFranking || IsFollowedByPunctuation(text, delimInfo)); deliminators.AddLast(delimInfo); i += length - 1; } else if (text[i] == '[') { deliminators.AddLast(DeliminatorInfo.Create(DeliminatorType.OpenLink, i, 1)); } else if (BeginWith(text, i, "![")) { deliminators.AddLast(DeliminatorInfo.Create(DeliminatorType.OpenImage, i, 2)); i++; } else if (i > 0 && text[i] == ']') { DeliminatorInfo openInfo = deliminators .LastOrDefault(info => info.Type == DeliminatorType.OpenImage || info.Type == DeliminatorType.OpenLink); if (openInfo == null) { continue; } if (!openInfo.Active) { deliminators.Remove(openInfo); continue; } int linkLabel = GetStartIndexOfLinkLabel(text, 0, i + 1, higherSpans); int linkBody = GetLinkBodyEndIndex(text, i + 1, out var dest, out var title); int linkLabel2 = GetEndIndexOfLinkLabel(text, i + 1, higherSpans); // Inline Link/Image if (text[Math.Min(i + 1, text.Length - 1)] == '(' && linkLabel >= 0 && linkBody >= 0) { inlineSpans.Add(openInfo.Index, InlineSpan.FromDeliminatorInfo(openInfo, linkBody, i, dest ?? string.Empty, title)); i = linkBody - 1; } // Collapsed Reference Link else if (BeginWith(text, i + 1, "[]") && TryGetReference(text.Substring(openInfo.Index + openInfo.DeliminatorLength, i - openInfo.Index - openInfo.DeliminatorLength), out var definition)) { inlineSpans.Add(openInfo.Index, InlineSpan.FromDeliminatorInfo(openInfo, i + 3, i, definition)); i += 2; } // Full Reference Link else if (linkLabel2 >= 0 && TryGetReference(text.Substring(i + 2, linkLabel2 - i - 3), out definition)) { inlineSpans.Add(openInfo.Index, InlineSpan.FromDeliminatorInfo(openInfo, linkLabel2, i, definition)); i = linkLabel2 - 1; } // shortcut link else if (TryGetReference(text.Substring(openInfo.Index + openInfo.DeliminatorLength, i - openInfo.Index - openInfo.DeliminatorLength), out definition) && GetEndIndexOfLinkLabel(text, i + 1, higherSpans) < 0) { inlineSpans.Add(openInfo.Index, InlineSpan.FromDeliminatorInfo(openInfo, i + 1, i, definition)); } else { deliminators.Remove(openInfo); continue; } if (openInfo.Type == DeliminatorType.OpenLink) { foreach (var item in deliminators.TakeWhile(c => c != openInfo)) { if (item.Type == DeliminatorType.OpenLink) { item.Active = false; } } } ParseEmphasis(deliminators, inlineSpans, openInfo); deliminators.Remove(openInfo); } } ParseEmphasis(deliminators, inlineSpans, null); foreach (var item in higherSpans) { inlineSpans.Add(item.Begin, item); } var tree = GetInlineTree(inlineSpans, text.Length); return(ToInlines(text, tree)); }