diff --git a/lib/model/katex.dart b/lib/model/katex.dart index 057f7076bc..41052f4705 100644 --- a/lib/model/katex.dart +++ b/lib/model/katex.dart @@ -225,6 +225,7 @@ class _KatexParser { // A copy of class definition (where possible) is accompanied in a comment // with each case statement to keep track of updates. final spanClasses = List.unmodifiable(element.className.split(' ')); + double? widthEm; String? fontFamily; double? fontSizeEm; KatexSpanFontWeight? fontWeight; @@ -415,7 +416,11 @@ class _KatexParser { _ => throw _KatexHtmlParseError(), }; - // TODO handle .nulldelimiter and .delimcenter . + case 'nulldelimiter': + // .nulldelimiter { display: inline-block; width: 0.12em; } + widthEm = 0.12; + + // TODO .delimcenter . case 'op-symbol': // .op-symbol { ... } @@ -458,6 +463,7 @@ class _KatexParser { } } final styles = KatexSpanStyles( + widthEm: widthEm, fontFamily: fontFamily, fontSizeEm: fontSizeEm, fontWeight: fontWeight, @@ -575,6 +581,7 @@ enum KatexSpanTextAlign { @immutable class KatexSpanStyles { + final double? widthEm; final double? heightEm; final double? verticalAlignEm; @@ -588,6 +595,7 @@ class KatexSpanStyles { final KatexSpanTextAlign? textAlign; const KatexSpanStyles({ + this.widthEm, this.heightEm, this.verticalAlignEm, this.marginRightEm, @@ -602,6 +610,7 @@ class KatexSpanStyles { @override int get hashCode => Object.hash( 'KatexSpanStyles', + widthEm, heightEm, verticalAlignEm, marginRightEm, @@ -616,6 +625,7 @@ class KatexSpanStyles { @override bool operator ==(Object other) { return other is KatexSpanStyles && + other.widthEm == widthEm && other.heightEm == heightEm && other.verticalAlignEm == verticalAlignEm && other.marginRightEm == marginRightEm && @@ -630,6 +640,7 @@ class KatexSpanStyles { @override String toString() { final args = []; + if (widthEm != null) args.add('widthEm: $widthEm'); if (heightEm != null) args.add('heightEm: $heightEm'); if (verticalAlignEm != null) args.add('verticalAlignEm: $verticalAlignEm'); if (marginRightEm != null) args.add('marginRightEm: $marginRightEm'); @@ -651,6 +662,7 @@ class KatexSpanStyles { /// had `inherit` set to true. KatexSpanStyles merge(KatexSpanStyles other) { return KatexSpanStyles( + widthEm: other.widthEm ?? widthEm, heightEm: other.heightEm ?? heightEm, verticalAlignEm: other.verticalAlignEm ?? verticalAlignEm, marginRightEm: other.marginRightEm ?? marginRightEm, diff --git a/lib/widgets/content.dart b/lib/widgets/content.dart index 52ab7008b8..b7e3752a5e 100644 --- a/lib/widgets/content.dart +++ b/lib/widgets/content.dart @@ -976,6 +976,9 @@ class _KatexSpan extends StatelessWidget { } widget = SizedBox( + width: styles.widthEm != null + ? styles.widthEm! * em + : null, height: styles.heightEm != null ? styles.heightEm! * em : null, diff --git a/test/model/content_test.dart b/test/model/content_test.dart index c19e1c02a5..6ae62b6b86 100644 --- a/test/model/content_test.dart +++ b/test/model/content_test.dart @@ -943,6 +943,40 @@ class ContentExample { ]), ]); + static const mathBlockKatexNulldelimiter = ContentExample( + 'math block; KaTeX nulldelimiter', + // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2205534 + '```math\n\\left. a \\middle. b \\right.\n```', + '

' + '' + 'a.b\\left. a \\middle. b \\right.' + '

', [ + MathBlockNode(texSource: '\\left. a \\middle. b \\right.', nodes: [ + KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [ + KatexStrutNode(heightEm: 0.6944, verticalAlignEm: null), + KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [ + KatexSpanNode(styles: KatexSpanStyles(widthEm: 0.12), text: null, nodes: []), + KatexSpanNode( + styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic), + text: 'a', nodes: null), + KatexSpanNode(styles: KatexSpanStyles(widthEm: 0.12), text: null, nodes: []), + KatexSpanNode( + styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic), + text: 'b', nodes: null), + KatexSpanNode(styles: KatexSpanStyles(widthEm: 0.12), text: null, nodes: []), + ]), + ]), + ]), + ]); + static const imageSingle = ContentExample( 'single image', // https://chat.zulip.org/#narrow/stream/7-test-here/topic/Thumbnails/near/1900103 @@ -2033,6 +2067,7 @@ void main() async { testParseExample(ContentExample.mathBlockKatexNestedSizing); testParseExample(ContentExample.mathBlockKatexDelimSizing); testParseExample(ContentExample.mathBlockKatexSpace); + testParseExample(ContentExample.mathBlockKatexNulldelimiter); testParseExample(ContentExample.imageSingle); testParseExample(ContentExample.imageSingleNoDimensions); diff --git a/test/widgets/content_test.dart b/test/widgets/content_test.dart index f6366a9215..95031ec306 100644 --- a/test/widgets/content_test.dart +++ b/test/widgets/content_test.dart @@ -602,6 +602,10 @@ void main() { (':', Offset(16.00, 2.24), Size(5.72, 25.00)), ('2', Offset(27.43, 2.24), Size(10.28, 25.00)), ]), + (ContentExample.mathBlockKatexNulldelimiter, skip: false, [ + ('a', Offset(2.47, 3.36), Size(10.88, 25.00)), + ('b', Offset(15.81, 3.36), Size(8.82, 25.00)), + ]), ]; for (final testCase in testCases) {