App/lib/widgets/content/markdown_latex.dart
2025-06-23 23:40:29 +08:00

81 lines
2.6 KiB
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 {
final bool isDark;
LatexSyntax(this.isDark) : 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';
el.attributes['isDark'] = isDark.toString();
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 isDark = attributes['isDark'] == '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: isDark ? Colors.white : 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,
);
}
}