Zero, preface,

In the previous part, highlighting the specified text was realized through text parsing, as shown in the figure below. Today we continue to improve the rich text display features, such as text link parsing, text captions, specifying text bold, italics, and more. This article will use some knowledge of regular expressions. This series will not focus on regular expressions and will not explain them too much. If you don’t understand, you can fix it yourself.

Here are other articles in the Flutter Text Interpretation series:

  • The Flutter Text reading 1 | know from source Text component”
  • 2 | the Flutter Text reading Text is how to draw out”
  • The 3 | Flutter Text reading Text component using introduction
  • 4 the Flutter text reading | TextStyle text style interpretation”
  • 5 the Flutter text reading | RichText the use of the rich text (on)”

First, the processing of text links

1. Regex for link matching

With \[.*?\) you can match the links in markdown, so you can use StringScanner to get the starting index of each match, and the rest is the same.


2. Data abstraction and implementation

As you can see, the types that need to be parsed need to be extended. Different cases are handled differently, so we could create an enumerated class and decide what to do based on the type, but that would be a lot of logic jammed together and difficult to maintain. We can define a layer of abstraction, separate out properties and behaviors, and implement them differently depending on the situation, using abstract classes to accomplish tasks.

In the following abstraction, the data required is the start and end of a character, so the subclass needs to implement the text method to return the displayed character and the style method to get the text style. Recognizer properties are provided for event handling.

abstract class SpanBean {
  SpanBean(this.start, this.end,{this.recognizer});

  final int start;
  final int end;

  String text(String src);

  TextStyle get style;
  
  final GestureRecognizer recognizer;
}
Copy the code

This can be done using WrapSpanBean to implement the previous package highlighting as follows:

// Package rule: 'data'
class WrapSpanBean extends SpanBean {
  WrapSpanBean(int start, int end) : super(start, end);

  @override
  String text(String src) {
    return src.substring(start + 1, end - 1);
  }

  @override
  TextStyle get style => TextStyleSupport.dotWrapStyle;
}
Copy the code

When used, the abstract SpanBean is used and the corresponding implementation is used when objects are added to the list. That’s what polymorphism is all about.

List<SpanBean> _spans = []; // Use abstractions

void parseContent() {
  while(! _scanner.isDone) {if (_scanner.scan(RegExp('`. *? ` '))) {
      int startIndex = _scanner.lastMatch.start;
      int endIndex = _scanner.lastMatch.end;
      _spans.add(WrapSpanBean(startIndex, endIndex)); // Add the implementation
    }
    if(! _scanner.isDone) { _scanner.position++; }}}Copy the code

3. Link parsing

The LinkSpanBean that processes linked data implements SpanBean.

// Link rule: [data](link)
class LinkSpanBean extends SpanBean {
  LinkSpanBean(int start, int end, {GestureRecognizer recognizer})
      : super(start, end, recognizer: recognizer);

  @override
  TextStyle get style => TextStyleSupport.linkStyle;

  @override
  String text(String src) {
    final String target = src.substring(start, end);
    return target.split('] ') [0].replaceFirst("[".' '); }}Copy the code

You include the LinkSpanBean in parseContent, whose click event jumps to the browser through the url_launcher: ^5.7.10 plug-in. One thing to note: GestureRecognizer needs to be disposed, which can be defined in StringParser to iterate over the SpanBean list for release.

void parseContent() {
  while(! _scanner.isDone) {if (_scanner.scan(RegExp('`. *? ` '))) {
      int startIndex = _scanner.lastMatch.start;
      int endIndex = _scanner.lastMatch.end;
      _spans.add(WrapSpanBean(startIndex, endIndex));
    }
    
    if (_scanner.scan(RegExp(r'\[.*?\)'))) {
      int startIndex = _scanner.lastMatch.start;
      int endIndex = _scanner.lastMatch.end;
      final String target = content.substring(startIndex, endIndex);
      String link = target.split('(') [1].replaceFirst(') '.' '); GestureRecognizer recognizer = TapGestureRecognizer() .. onTap = () { launch(link); }; _spans.add(LinkSpanBean(startIndex, endIndex, recognizer: recognizer)); }if(! _scanner.isDone) { _scanner.position++; }}}voiddispose() { _spans.forEach((element) { element.recognizer? .dispose(); }); }Copy the code

4. TextSpan processing

As before, we add GestureRecognizer to the SpanBean, which we can use when generating TextSpan.

InlineSpan parser() {
  _scanner = StringScanner(content);
  parseContent();
  final List<TextSpan> spans = <TextSpan>[];
  int currentPosition = 0;
  for (SpanBean span in _spans) {
    if(currentPosition ! = span.start) { spans.add( TextSpan(text: content.substring(currentPosition, span.start))); } spans.add(TextSpan( style: span.style, text: span.text(content), recognizer: span.recognizer)); currentPosition = span.end; }if(currentPosition ! = content.length) spans.add( TextSpan(text: content.substring(currentPosition, content.length)));return TextSpan(style: TextStyleSupport.defaultStyle, children: spans);
}
Copy the code

5. Use effect

This allows you to highlight links in the text as shown below.

And when you click the link, you can jump.


Second, the processing of title text

1. Regex for title matching

Matches the beginning of several # lines by ^#+.*. Matches requirements at the beginning of multiple lines in the Dart re. MultiLine: true. Then the following # 777 will not be mismatched.

RegExp(r'^#+ .*',multiLine: true)
Copy the code


2. HeadSpanBean definition

As the implementation class of SpanBean, HeadSpanBean can complete six levels of titles, which are indicated by the lever attribute.

// Header rule: #+ data
class HeadSpanBean extends SpanBean{

  HeadSpanBean(int start, int end,this.lever) : super(start, end);

  final int lever;

  @override
  TextStyle get style => TextStyleSupport.headStyleMap[lever];

  @override
  String text(String src) {
    final String target = src.substring(start, end);
    return target.replaceRange(0, lever + 1.' '); }}Copy the code

Provide a headStyleMap in TextStyleSupport to fetch styles by number so that you don’t have to branch them back one by one, making the code more comfortable to look at.

class TextStyleSupport {
  static const TextStyle defaultStyle =
      TextStyle(color: Colors.black, fontSize: 14);

  static const TextStyle dotWrapStyle =
      TextStyle(color: Colors.purple, fontSize: 14);

  static const TextStyle linkStyle = TextStyle(
      color: Colors.blue,
      decoration: TextDecoration.underline,
      decorationColor: Colors.blue);

  static const Map<int,TextStyle>  headStyleMap = {
    1:h1,
    2:h2,
    3:h3,
    4:h4,
    5:h5,
    6:h6,
  };

  static const TextStyle h1 =
      TextStyle(color: Colors.black, fontSize: 24, fontWeight: FontWeight.bold);
  static const TextStyle h2 =
      TextStyle(color: Colors.black, fontSize: 22, fontWeight: FontWeight.bold);
  static const TextStyle h3 =
      TextStyle(color: Colors.black, fontSize: 20, fontWeight: FontWeight.bold);
  static const TextStyle h4 =
      TextStyle(color: Colors.black, fontSize: 18, fontWeight: FontWeight.bold);
  static const TextStyle h5 =
      TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold);
  static const TextStyle h6 =
      TextStyle(color: Colors.black, fontSize: 14, fontWeight: FontWeight.bold);
}
Copy the code

3. Title analysis

In this way, by using the regular expression ^#+.*, obtain the corresponding character interval before and after the bounds, and then analyze how many # can be.

if (_scanner.scan(RegExp(r'^#+ .*',multiLine: true))) {
  int startIndex = _scanner.lastMatch.start;
  int endIndex = _scanner.lastMatch.end;
  int lever = content.substring(startIndex, endIndex).split(' ') [0].length;
  _spans.add(HeadSpanBean(startIndex, endIndex,lever));
}
Copy the code

This completes the heading style that begins with #. In TextStyleSupport you can modify these default styles. Or provide multiple groups of different styles to provide switching. Knowing the principle, operability can be greatly improved.


3. Bold and slanted text

1. Bold text processing

The markdown bold rule is **data**. With the previous ones, you should now know the general flow. The corresponding re is \*\*.*? \ \ * *,

Define BoldSpanBean as follows:

// Bold rule: **data**
class BoldSpanBean extends SpanBean{
  BoldSpanBean(int start, int end) : super(start, end);
  @override
  TextStyle get style => TextStyleSupport.bold;
  @override
  String text(String src) {
    return src.substring(start+2, end2 -); }}Copy the code

When parsing the content, add BoldSpanBean.

if (_scanner.scan(RegExp(r'\*\*.*? \ \ * * '))) {
  int startIndex = _scanner.lastMatch.start;
  int endIndex = _scanner.lastMatch.end;
  _spans.add(BoldSpanBean(startIndex, endIndex));
}
Copy the code

This will make local text bold:


2. Text skew processing

The rule for markdown tilt is *data*. The corresponding re is \*\*.*? ** *, then we will find that such bold **data** will have interference, used in the parsing, you can first parse bold, and then parse tilt. Because StringScanner scans the text only once, after the bolder scan, the position index is increased and there is no effect on slanted regex.

// Match in bold
if (_scanner.scan(RegExp(r'\*\*.*? \ \ * * '))) {
  int startIndex = _scanner.lastMatch.start;
  int endIndex = _scanner.lastMatch.end;
  _spans.add(BoldSpanBean(startIndex, endIndex));
}

// Tilt match
if (_scanner.scan(RegExp(r'\*.*? \ * '))) {
  int startIndex = _scanner.lastMatch.start;
  int endIndex = _scanner.lastMatch.end;
  _spans.add(LeanSpanBean(startIndex, endIndex));
}
Copy the code

// Bold rule: *data*
class LeanSpanBean extends SpanBean{

  LeanSpanBean(int start, int end) : super(start, end);

  @override
  TextStyle get style => TextStyleSupport.lean;

  @override
  String text(String src) {
    return src.substring(start+1, end- 1); }}Copy the code

By the end of this article, you should be familiar with the use of rich text. To add a new rule, the most important thing is to find the corresponding regular expression. Once found, it’s a matter of simple processing. That’s all for this article, and the next article will look at how to implement a code highlighting rich text in Flutter.