Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion lib/model/katex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,13 @@ class _KatexParser {
case 'nobreak':
case 'allowbreak':
case 'mathdefault':
case 'overline':
case 'underline':
case 'overline-line':
case 'underline-line':
// .overline-line,
// .underline-line { width: 100%; border-bottom-style: solid; }
// Border applied via inline style: border-bottom-width: 0.04em;
// Ignore these classes because they don't have a CSS definition
// in katex.scss, but we encounter them in the generated HTML.
// (Why are they there if they're not used? The story seems to be:
Expand Down Expand Up @@ -657,6 +664,7 @@ class _KatexParser {
marginRightEm: _takeStyleEm(inlineStyles, 'margin-right'),
color: _takeStyleColor(inlineStyles, 'color'),
position: _takeStylePosition(inlineStyles, 'position'),
borderBottomWidthEm: _takeStyleEm(inlineStyles, 'border-bottom-width')
// TODO handle more CSS properties
);
if (inlineStyles != null && inlineStyles.isNotEmpty) {
Expand Down Expand Up @@ -893,6 +901,7 @@ class KatexSpanStyles {

final KatexSpanColor? color;
final KatexSpanPosition? position;
final double? borderBottomWidthEm;

const KatexSpanStyles({
this.widthEm,
Expand All @@ -907,6 +916,7 @@ class KatexSpanStyles {
this.textAlign,
this.color,
this.position,
this.borderBottomWidthEm,
});

@override
Expand All @@ -924,6 +934,7 @@ class KatexSpanStyles {
textAlign,
color,
position,
borderBottomWidthEm
);

@override
Expand All @@ -940,7 +951,8 @@ class KatexSpanStyles {
other.fontStyle == fontStyle &&
other.textAlign == textAlign &&
other.color == color &&
other.position == position;
other.position == position &&
other.borderBottomWidthEm == borderBottomWidthEm;
}

@override
Expand All @@ -958,6 +970,7 @@ class KatexSpanStyles {
if (textAlign != null) args.add('textAlign: $textAlign');
if (color != null) args.add('color: $color');
if (position != null) args.add('position: $position');
if (borderBottomWidthEm != null) args.add('borderBottomWidthEm: $borderBottomWidthEm');
return '${objectRuntimeType(this, 'KatexSpanStyles')}(${args.join(', ')})';
}

Expand All @@ -975,6 +988,7 @@ class KatexSpanStyles {
bool textAlign = true,
bool color = true,
bool position = true,
bool borderBottomWidthEm = true,
}) {
return KatexSpanStyles(
widthEm: widthEm ? this.widthEm : null,
Expand All @@ -989,6 +1003,7 @@ class KatexSpanStyles {
textAlign: textAlign ? this.textAlign : null,
color: color ? this.color : null,
position: position ? this.position : null,
borderBottomWidthEm: borderBottomWidthEm ? this.borderBottomWidthEm : null,
);
}
}
Expand Down
75 changes: 69 additions & 6 deletions lib/widgets/katex.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:math' as math;

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

import '../model/content.dart';
Expand Down Expand Up @@ -231,12 +231,75 @@ class _KatexVlist extends StatelessWidget {
@override
Widget build(BuildContext context) {
final em = DefaultTextStyle.of(context).style.fontSize!;
final defaultColor = DefaultTextStyle.of(context).style.color ;

final lines = <_VlistLine>[];
for (final row in node.rows) {
final lineInfo = _findLineInfo(row.node);
if (lineInfo != null) {
final y = (row.verticalOffsetEm - (lineInfo.heightEm ?? 0) + 0.9) * em;
final thickness = lineInfo.borderWidthEm * em;
lines.add(_VlistLine(y, thickness, lineInfo.color ?? defaultColor));
}}

return CustomPaint(
foregroundPainter: _KatexBorderPainter(lines),
child: Stack(
children: List.unmodifiable(node.rows.map((row) {
return Transform.translate(
offset: Offset(0, row.verticalOffsetEm * em),
child: _KatexSpan(row.node));
}))));
}
({double? heightEm, double borderWidthEm, Color? color})? _findLineInfo(KatexSpanNode span) {
final borderWidth = span.styles.borderBottomWidthEm;
final color = span.styles.color != null
? Color.fromARGB(span.styles.color!.a, span.styles.color!.r, span.styles.color!.g, span.styles.color!.b)
: null;
if (borderWidth != null) {return (heightEm: span.styles.heightEm, borderWidthEm: borderWidth, color: color);}

if (span.nodes != null) {
for (final child in span.nodes!) {
if (child is KatexSpanNode) {
final info = _findLineInfo(child);
if (info != null) return info;
}}}
return null;
}
}

class _VlistLine {
const _VlistLine(this.y, this.thickness, this.color);
final double y;
final double thickness;
final Color? color;
}

class _KatexBorderPainter extends CustomPainter {
const _KatexBorderPainter(this.lines);

return Stack(children: List.unmodifiable(node.rows.map((row) {
return Transform.translate(
offset: Offset(0, row.verticalOffsetEm * em),
child: _KatexSpan(row.node));
})));
final List<_VlistLine> lines;

@override
void paint(Canvas canvas, Size size) {
if (lines.isEmpty) return;
final paint = Paint()..style = PaintingStyle.fill;
for (final line in lines) {
paint.color = line.color ?? Colors.black;
canvas.drawRect(Rect.fromLTWH(0, line.y, size.width, line.thickness), paint);
}
}

@override
bool shouldRepaint(covariant _KatexBorderPainter oldDelegate) {
if (oldDelegate.lines.length != lines.length) return true;
for (var i = 0; i < lines.length; i++) {
if (oldDelegate.lines[i].y != lines[i].y ||
oldDelegate.lines[i].thickness != lines[i].thickness ||
oldDelegate.lines[i].color != lines[i].color) {
return true;
}}
return false;
}
}

Expand Down
105 changes: 105 additions & 0 deletions test/model/katex_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,109 @@ class KatexExample extends ContentExample {
]),
]),
]);

static final overline = KatexExample.block(
r'overline: \overline{AB}',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Saif.20KaTeX/near/2285099
r'\overline{AB}',
'<p>'
'<span class="katex-display"><span class="katex">'
'<span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mrow><mi>A</mi><mi>B</mi></mrow><mo stretchy="true">‾</mo></mover></mrow><annotation encoding="application/x-tex">\\overline{AB}</annotation></semantics></math></span>'
'<span class="katex-html" aria-hidden="true">'
'<span class="base">'
'<span class="strut" style="height:0.8833em;"></span>'
'<span class="mord overline">'
'<span class="vlist-t">'
'<span class="vlist-r">'
'<span class="vlist" style="height:0.8833em;">'
'<span style="top:-3em;">'
'<span class="pstrut" style="height:3em;"></span>'
'<span class="mord">'
'<span class="mord mathnormal">A</span>'
'<span class="mord mathnormal" style="margin-right:0.05017em;">B</span></span></span>'
'<span style="top:-3.8033em;">'
'<span class="pstrut" style="height:3em;"></span>'
'<span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span></span></span></span></span></span></span></p>',[
KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.8833, verticalAlignEm: null),
KatexSpanNode(nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -3 + 3,
node: KatexSpanNode(nodes: [
KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
text: 'A'),
KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.05017, fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
text: 'B'),
]),
])),
KatexVlistRowNode(
verticalOffsetEm: -3.8033 + 3,
node: KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(borderBottomWidthEm: 0.04),
nodes: []),
])),
]),
]),
]),
]);

static final underline = KatexExample.block(
r'underline: \underline{AB}',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Saif.20KaTeX/near/2285099
r'\underline{AB}',
'<p>'
'<span class="katex-display"><span class="katex">'
'<span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><munder accentunder="true"><mrow><mi>A</mi><mi>B</mi></mrow><mo stretchy="true">‾</mo></munder></mrow><annotation encoding="application/x-tex">\\underline{AB}</annotation></semantics></math></span>'
'<span class="katex-html" aria-hidden="true">'
'<span class="base">'
'<span class="strut" style="height:0.8833em;vertical-align:-0.2em;"></span>'
'<span class="mord underline">'
'<span class="vlist-t vlist-t2">'
'<span class="vlist-r">'
'<span class="vlist" style="height:0.6833em;">'
'<span style="top:-2.84em;">'
'<span class="pstrut" style="height:3em;"></span>'
'<span class="underline-line" style="border-bottom-width:0.04em;"></span></span>'
'<span style="top:-3em;">'
'<span class="pstrut" style="height:3em;"></span>'
'<span class="mord">'
'<span class="mord mathnormal">A</span>'
'<span class="mord mathnormal" style="margin-right:0.05017em;">B</span></span></span></span>'
'<span class="vlist-s">​</span></span>'
'<span class="vlist-r">'
'<span class="vlist" style="height:0.2em;"><span></span></span></span></span></span></span></span></span></span></p>',[
KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.8833, verticalAlignEm: -0.2),
KatexSpanNode(nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -2.84 + 3,
node: KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(borderBottomWidthEm: 0.04),
nodes: []),
])),
KatexVlistRowNode(
verticalOffsetEm: -3 + 3,
node: KatexSpanNode(nodes: [
KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
text: 'A'),
KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.05017, fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
text: 'B'),
]),
])),
]),
]),
]),
]);
}

void main() async {
Expand All @@ -754,6 +857,8 @@ void main() async {
testParseExample(KatexExample.bigOperators);
testParseExample(KatexExample.colonEquals);
testParseExample(KatexExample.nulldelimiter);
testParseExample(KatexExample.overline);
testParseExample(KatexExample.underline);

group('parseCssHexColor', () {
const testCases = [
Expand Down
8 changes: 8 additions & 0 deletions test/widgets/katex_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ void main() {
('a', Offset(2.47, 3.36), Size(10.88, 25.00)),
('b', Offset(15.81, 3.36), Size(8.82, 25.00)),
]),
(KatexExample.overline, skip: false, [
('A', Offset(0.0, 5.61), Size(15.43, 25.0)),
('B', Offset(15.43, 5.61), Size(15.61, 25.0)),
]),
(KatexExample.underline, skip: false, [
('A', Offset(0.0, 5.61), Size(15.43, 25.0)),
('B', Offset(15.43, 5.61), Size(15.61, 25.0)),
]),
];

for (final testCase in testCases) {
Expand Down