diff --git a/lib/model/katex.dart b/lib/model/katex.dart index 922546c676..6fb51cc1b1 100644 --- a/lib/model/katex.dart +++ b/lib/model/katex.dart @@ -198,6 +198,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; @@ -387,7 +388,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 { ... } @@ -430,6 +435,7 @@ class _KatexParser { } } final styles = KatexSpanStyles( + widthEm: widthEm, fontFamily: fontFamily, fontSizeEm: fontSizeEm, fontWeight: fontWeight, @@ -523,6 +529,7 @@ enum KatexSpanTextAlign { @immutable class KatexSpanStyles { + final double? widthEm; final double? heightEm; final String? fontFamily; @@ -532,6 +539,7 @@ class KatexSpanStyles { final KatexSpanTextAlign? textAlign; const KatexSpanStyles({ + this.widthEm, this.heightEm, this.fontFamily, this.fontSizeEm, @@ -543,6 +551,7 @@ class KatexSpanStyles { @override int get hashCode => Object.hash( 'KatexSpanStyles', + widthEm, heightEm, fontFamily, fontSizeEm, @@ -554,6 +563,7 @@ class KatexSpanStyles { @override bool operator ==(Object other) { return other is KatexSpanStyles && + other.widthEm == widthEm && other.heightEm == heightEm && other.fontFamily == fontFamily && other.fontSizeEm == fontSizeEm && @@ -565,6 +575,7 @@ class KatexSpanStyles { @override String toString() { final args = []; + if (widthEm != null) args.add('widthEm: $widthEm'); if (heightEm != null) args.add('heightEm: $heightEm'); if (fontFamily != null) args.add('fontFamily: $fontFamily'); if (fontSizeEm != null) args.add('fontSizeEm: $fontSizeEm'); @@ -583,6 +594,7 @@ class KatexSpanStyles { /// had `inherit` set to true. KatexSpanStyles merge(KatexSpanStyles other) { return KatexSpanStyles( + widthEm: other.widthEm ?? widthEm, heightEm: other.heightEm ?? heightEm, fontFamily: other.fontFamily ?? fontFamily, fontSizeEm: other.fontSizeEm ?? fontSizeEm, diff --git a/lib/widgets/content.dart b/lib/widgets/content.dart index b49fdb4d9c..5e9e908d7d 100644 --- a/lib/widgets/content.dart +++ b/lib/widgets/content.dart @@ -947,6 +947,9 @@ class _KatexSpan extends StatelessWidget { } return SizedBox( + width: styles.widthEm != null + ? styles.widthEm! * (fontSize ?? em) + : null, height: styles.heightEm != null ? styles.heightEm! * (fontSize ?? em) : null, diff --git a/test/model/content_test.dart b/test/model/content_test.dart index c90ac54b33..1921245d3e 100644 --- a/test/model/content_test.dart +++ b/test/model/content_test.dart @@ -893,6 +893,67 @@ 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: [ + KatexSpanNode( + styles: KatexSpanStyles(heightEm: 0.6944), + text: null, + nodes: []), + 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 @@ -1967,6 +2028,7 @@ void main() async { // `vertical-align` in inline styles. Currently it fails // because `strut` span has `vertical-align`. testParseExample(ContentExample.mathBlockKatexDelimSizing, skip: true); + testParseExample(ContentExample.mathBlockKatexNulldelimiter); testParseExample(ContentExample.imageSingle); testParseExample(ContentExample.imageSingleNoDimensions);