Skip to content

Commit 4a99319

Browse files
KaTeX overline underline
1 parent 3d3b1c1 commit 4a99319

File tree

4 files changed

+159
-1
lines changed

4 files changed

+159
-1
lines changed

lib/model/katex.dart

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ class _KatexParser {
411411
KatexSpanFontWeight? fontWeight;
412412
KatexSpanFontStyle? fontStyle;
413413
KatexSpanTextAlign? textAlign;
414+
KatexBorderStyle? borderStyle;
414415
var index = 0;
415416
while (index < spanClasses.length) {
416417
final spanClass = spanClasses[index++];
@@ -636,6 +637,20 @@ class _KatexParser {
636637
// )
637638
break;
638639

640+
case 'overline':
641+
// .overline { border-top: 0.049em solid; }
642+
borderStyle = KatexBorderStyle(
643+
position: KatexBorderPosition.top,
644+
widthEm: 0.049,
645+
);
646+
647+
case 'underline':
648+
// .underline { border-bottom: 0.049em solid; }
649+
borderStyle = KatexBorderStyle(
650+
position: KatexBorderPosition.bottom,
651+
widthEm: 0.049,
652+
);
653+
639654
default:
640655
assert(debugLog('KaTeX: Unsupported CSS class: $spanClass'));
641656
unsupportedCssClasses.add(spanClass);
@@ -657,6 +672,7 @@ class _KatexParser {
657672
marginRightEm: _takeStyleEm(inlineStyles, 'margin-right'),
658673
color: _takeStyleColor(inlineStyles, 'color'),
659674
position: _takeStylePosition(inlineStyles, 'position'),
675+
borderStyle: borderStyle,
660676
// TODO handle more CSS properties
661677
);
662678
if (inlineStyles != null && inlineStyles.isNotEmpty) {
@@ -840,6 +856,39 @@ enum KatexSpanPosition {
840856
relative,
841857
}
842858

859+
enum KatexBorderPosition {
860+
top,
861+
bottom,
862+
}
863+
864+
class KatexBorderStyle {
865+
const KatexBorderStyle({
866+
required this.position,
867+
required this.widthEm,
868+
this.color,
869+
});
870+
871+
final KatexBorderPosition position;
872+
final double widthEm;
873+
final KatexSpanColor? color;
874+
875+
@override
876+
bool operator ==(Object other) {
877+
return other is KatexBorderStyle &&
878+
other.position == position &&
879+
other.widthEm == widthEm &&
880+
other.color == color;
881+
}
882+
883+
@override
884+
int get hashCode => Object.hash('KatexBorderStyle', position, widthEm, color);
885+
886+
@override
887+
String toString() {
888+
return '${objectRuntimeType(this, 'KatexBorderStyle')}($position, $widthEm, $color)';
889+
}
890+
}
891+
843892
class KatexSpanColor {
844893
const KatexSpanColor(this.r, this.g, this.b, this.a);
845894

@@ -893,6 +942,7 @@ class KatexSpanStyles {
893942

894943
final KatexSpanColor? color;
895944
final KatexSpanPosition? position;
945+
final KatexBorderStyle? borderStyle;
896946

897947
const KatexSpanStyles({
898948
this.widthEm,
@@ -907,6 +957,7 @@ class KatexSpanStyles {
907957
this.textAlign,
908958
this.color,
909959
this.position,
960+
this.borderStyle,
910961
});
911962

912963
@override
@@ -924,6 +975,7 @@ class KatexSpanStyles {
924975
textAlign,
925976
color,
926977
position,
978+
borderStyle,
927979
);
928980

929981
@override
@@ -940,7 +992,8 @@ class KatexSpanStyles {
940992
other.fontStyle == fontStyle &&
941993
other.textAlign == textAlign &&
942994
other.color == color &&
943-
other.position == position;
995+
other.position == position &&
996+
other.borderStyle == borderStyle;
944997
}
945998

946999
@override
@@ -958,6 +1011,7 @@ class KatexSpanStyles {
9581011
if (textAlign != null) args.add('textAlign: $textAlign');
9591012
if (color != null) args.add('color: $color');
9601013
if (position != null) args.add('position: $position');
1014+
if (borderStyle != null) args.add('borderStyle: $borderStyle');
9611015
return '${objectRuntimeType(this, 'KatexSpanStyles')}(${args.join(', ')})';
9621016
}
9631017

@@ -975,6 +1029,7 @@ class KatexSpanStyles {
9751029
bool textAlign = true,
9761030
bool color = true,
9771031
bool position = true,
1032+
bool borderStyle = true,
9781033
}) {
9791034
return KatexSpanStyles(
9801035
widthEm: widthEm ? this.widthEm : null,
@@ -989,6 +1044,7 @@ class KatexSpanStyles {
9891044
textAlign: textAlign ? this.textAlign : null,
9901045
color: color ? this.color : null,
9911046
position: position ? this.position : null,
1047+
borderStyle: borderStyle ? this.borderStyle : null,
9921048
);
9931049
}
9941050
}

lib/widgets/katex.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,48 @@ class _KatexSpan extends StatelessWidget {
195195
break;
196196
}
197197

198+
if (styles.borderStyle case final borderStyle?) {
199+
final currentColor = color ?? DefaultTextStyle.of(context).style.color!;
200+
final Color borderColor = borderStyle.color != null
201+
? Color.fromARGB(borderStyle.color!.a,borderStyle.color!.r,borderStyle.color!.g,borderStyle.color!.b,
202+
): currentColor;
203+
204+
final double borderWidth = borderStyle.widthEm * em;
205+
final bool isTop = borderStyle.position == KatexBorderPosition.top;
206+
widget = CustomPaint(
207+
painter: _BorderPainter(color: borderColor,strokeWidth: borderWidth,isTop: isTop),
208+
child: widget);
209+
}
198210
return widget;
199211
}
200212
}
201213

214+
class _BorderPainter extends CustomPainter {
215+
_BorderPainter({
216+
required this.color,
217+
required this.strokeWidth,
218+
required this.isTop,
219+
});
220+
221+
final Color color;
222+
final double strokeWidth;
223+
final bool isTop;
224+
225+
@override
226+
void paint(Canvas canvas, Size size) {
227+
final paint = Paint()
228+
..color = color
229+
..strokeWidth = strokeWidth
230+
..style = PaintingStyle.stroke;
231+
if (isTop) {canvas.drawLine(Offset(0, strokeWidth / 2 + 3),Offset(size.width, strokeWidth / 2 + 3),paint);
232+
} else {canvas.drawLine(Offset(0, size.height - strokeWidth / 2 - 3),Offset(size.width, size.height - strokeWidth / 2 - 3),paint);
233+
}
234+
}
235+
@override
236+
bool shouldRepaint(_BorderPainter oldDelegate) {
237+
return oldDelegate.color != color || oldDelegate.strokeWidth != strokeWidth ||oldDelegate.isTop != isTop;
238+
}}
239+
202240
class _KatexStrut extends StatelessWidget {
203241
const _KatexStrut(this.node);
204242

test/model/katex_test.dart

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,62 @@ class KatexExample extends ContentExample {
731731
]),
732732
]),
733733
]);
734+
735+
static final overline = KatexExample.block(
736+
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Saif.20KaTeX/with/2278868
737+
r'overline: \overline{a}',
738+
r'\overline{a}',
739+
'<p>'
740+
'<span class="katex-display"><span class="katex">'
741+
'<span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mi>a</mi><mo>¯</mo></mover></mrow><annotation encoding="application/x-tex">\\overline{a}</annotation></semantics></math></span>'
742+
'<span class="katex-html" aria-hidden="true">'
743+
'<span class="base">'
744+
'<span class="strut" style="height:0.6944em;"></span>'
745+
'<span class="mord overline">'
746+
'<span class="mord mathnormal">a</span></span></span></span></span></span></p>', [
747+
KatexSpanNode(nodes: [
748+
KatexStrutNode(heightEm: 0.6944, verticalAlignEm: null),
749+
KatexSpanNode(
750+
styles: KatexSpanStyles(
751+
borderStyle: KatexBorderStyle(
752+
position: KatexBorderPosition.top,
753+
widthEm: 0.049,
754+
color: null)),
755+
nodes: [
756+
KatexSpanNode(
757+
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
758+
text: 'a'),
759+
]),
760+
]),
761+
]);
762+
763+
static final underline = KatexExample.block(
764+
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Saif.20KaTeX/with/2278868
765+
r'underline: \underline{b}',
766+
r'\underline{b}',
767+
'<p>'
768+
'<span class="katex-display"><span class="katex">'
769+
'<span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><munder accent="true"><mi>b</mi><mo>‾</mo></munder></mrow><annotation encoding="application/x-tex">\\underline{b}</annotation></semantics></math></span>'
770+
'<span class="katex-html" aria-hidden="true">'
771+
'<span class="base">'
772+
'<span class="strut" style="height:0.6944em;"></span>'
773+
'<span class="mord underline">'
774+
'<span class="mord mathnormal">b</span></span></span></span></span></span></p>', [
775+
KatexSpanNode(nodes: [
776+
KatexStrutNode(heightEm: 0.6944, verticalAlignEm: null),
777+
KatexSpanNode(
778+
styles: KatexSpanStyles(
779+
borderStyle: KatexBorderStyle(
780+
position: KatexBorderPosition.bottom,
781+
widthEm: 0.049,
782+
color: null)),
783+
nodes: [
784+
KatexSpanNode(
785+
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
786+
text: 'b'),
787+
]),
788+
]),
789+
]);
734790
}
735791

736792
void main() async {
@@ -754,6 +810,8 @@ void main() async {
754810
testParseExample(KatexExample.bigOperators);
755811
testParseExample(KatexExample.colonEquals);
756812
testParseExample(KatexExample.nulldelimiter);
813+
testParseExample(KatexExample.overline);
814+
testParseExample(KatexExample.underline);
757815

758816
group('parseCssHexColor', () {
759817
const testCases = [

test/widgets/katex_test.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ void main() {
8181
('a', Offset(2.47, 3.36), Size(10.88, 25.00)),
8282
('b', Offset(15.81, 3.36), Size(8.82, 25.00)),
8383
]),
84+
(KatexExample.overline, skip: false, [
85+
('a', Offset(0.00, 3.36), Size(10.88, 25.00)),
86+
]),
87+
(KatexExample.underline, skip: false, [
88+
('b', Offset(0.00, 3.36), Size(8.82, 25.00)),
89+
]),
8490
];
8591

8692
for (final testCase in testCases) {

0 commit comments

Comments
 (0)