Flutter Markdown LaTeX Rendering


#Flutter#Dart#markdown_widget#LaTeX#Markdown

在 Flutter 中使用 markdown_widget 與 flutter_math_fork,讓 Markdown 支援 LaTeX 數學公式渲染。

Preview



Install


dependencies:
    markdown_widget: ^2.3.2+8
    flutter_math_fork: ^0.7.0

latex.dart


import 'package:flutter/material.dart';
import 'package:markdown_widget/markdown_widget.dart';
import 'package:flutter_math_fork/flutter_math.dart';
import 'package:markdown/markdown.dart' as m;

SpanNodeGeneratorWithTag latexGenerator =
    SpanNodeGeneratorWithTag(tag: _latexTag, generator: (e, config, visitor) => LatexNode(e.attributes, e.textContent, config));

const _latexTag = 'latex';

class LatexSyntax extends m.InlineSyntax {
  LatexSyntax() : super(r'(\$\$[\s\S]+\$\$)|(\$.+?\$)');

  @override
  bool onMatch(m.InlineParser parser, Match match) {
    final input = match.input;
    final matchValue = input.substring(match.start, match.end);
    String content = '';
    bool isInline = true;
    const blockSyntax = '\$\$';
    const inlineSyntax = '\$';
    if (matchValue.startsWith(blockSyntax) && matchValue.endsWith(blockSyntax) && (matchValue != blockSyntax)) {
      content = matchValue.substring(2, matchValue.length - 2);
      isInline = false;
    } else if (matchValue.startsWith(inlineSyntax) && matchValue.endsWith(inlineSyntax) && matchValue != inlineSyntax) {
      content = matchValue.substring(1, matchValue.length - 1);
    }
    m.Element el = m.Element.text(_latexTag, matchValue);
    el.attributes['content'] = content;
    el.attributes['isInline'] = '$isInline';
    parser.addNode(el);
    return true;
  }
}

class LatexNode extends SpanNode {
  final Map<String, String> attributes;
  final String textContent;
  final MarkdownConfig config;

  LatexNode(this.attributes, this.textContent, this.config);

  @override
  InlineSpan build() {
    final content = attributes['content'] ?? '';
    final isInline = attributes['isInline'] == 'true';
    final style = parentStyle ?? config.p.textStyle;
    if (content.isEmpty) return TextSpan(style: style, text: textContent);
    final latex = Math.tex(
      content,
      mathStyle: MathStyle.text,
      textStyle: style.copyWith(color: Colors.black),
      textScaleFactor: 1,
      onErrorFallback: (error) {
        return Text(textContent, style: style.copyWith(color: Colors.red));
      },
    );

    return WidgetSpan(
      alignment: PlaceholderAlignment.middle,
      child: !isInline
          ? Container(
              width: double.infinity,
              margin: EdgeInsets.symmetric(vertical: 16),
              child: Center(child: latex),
            )
          : latex,
    );
  }
}

Usage


import 'package:project_name/latex.dart';
import 'package:markdown_widget/markdown_widget.dart';

......

MarkdownWidget(
  data: '''\$\$f(X,n) = X_n + X_{n-1}\$\$
    
\$\$
M = 
\\begin{bmatrix}
\\frac{5}{6} & \\frac{1}{6} & 0 \\\\[0.3em]
\\frac{5}{6} & 0 & \\frac{1}{6} \\\\[0.3em]
0 & \\frac{5}{6} & \\frac{1}{6}
\\end{bmatrix}
\$\$''',
  markdownGenerator: MarkdownGenerator(
    inlineSyntaxList: [LatexSyntax()],
    generators: [latexGenerator],
  ),
),


......

Dart 單例模式


#Flutter#Dart#單例模式 (Singleton)

在寫 Futtler 時常常一些變數是希望在 APP 關閉前可以隨手取得,這時使用單例模式 (Singleton) 可以很方便的存取。
使用 Factory constructor 方式即可完成 Dart 中的 Singleton

class SingletonClass {
  static final SingletonClass _singleton = SingletonClass._init();
  factory SingletonClass() => _singleton;
  SingletonClass._init();
}

Dart List Filter 的方法


#Flutter#Dart#List

其他語言通常都是有叫做 filter() 之類的 function,但在 Dart 想過濾 List 中的某些值時是使用 where(),滿特別的命名。
where() 回傳的是 Iterable 型別,所以必須再呼叫 toList() 轉回 List 型別。

void main() {
  List<int> list = [87, 8787];

  print(list.where((e) => e > 100).toList()); // [8787]
  print(list.where((e) => e < 100).toList()); // [87]
}

試一試吧:https://dartpad.dev/?id=8281264511bc4db31bbdc5c4379c042d



#Flutter#Dart#Widget

使用一些套件時發現必須呼叫套件底層的 Function,這時候就要利用 GlobalKey。

宣告一個 GlobalKey

final GlobalKey<MyState> _myKey = GlobalKey();

加入到需要呼叫的 Widget

MyWidget(
    key: _myKey,
),

Widget State 內的 Function 必須是 Public

class MyState extends State<MyWidget> {
    foo() {
        // some code
    }
}

呼叫

_myKey.currentState!.foo();

Dart 的 List Call By Value 方法


#Flutter#Dart#List

最近把 List 存到 BLoC 發現在 A 頁面取出後對這 List 操作後,B 頁面也會跟著被改變,是因為 Dart 物件預設是 call by reference。

void main() {
  List<int> aList = [8787];
  List<int> bList = aList;
  
  print('aList: $aList');// aList: [8787]
  print('bList: $bList');// bList: [8787]
  
  bList.add(87);
  
  print('aList: $aList');// aList: [8787, 87]
  print('bList: $bList');// bList: [8787, 87]
}

解決方法


只要在給值的時候使用加上 toList() 即可

void main() {
  List<int> aList = [8787];
- List<int> bList = aList;
+ List<int> bList = aList.toList();
  
  print('aList: $aList');// aList: [8787]
  print('bList: $bList');// bList: [8787]
  
  bList.add(87);
  
  print('aList: $aList');// aList: [8787]
  print('bList: $bList');// bList: [8787, 87]
} 

試一試吧:https://dartpad.dev/?id=26d30bcd783277c83d00041279de673e