From 459ca983ab14ebab133841b6518a078c4b0ffa39 Mon Sep 17 00:00:00 2001
From: Greg Price
Date: Sat, 19 Jul 2025 14:01:42 -0700
Subject: [PATCH 01/11] katex test [nfc]: Split parsing tests to their own
file, like implementation
This way we maintain our usual parallel between the files that tests
live in and the files with the code the tests are about.
There's more we can do to make use of having a specific class for
these. But we'll do that in separate commits, so that this massive
move commit is as pure of a move as possible.
---
test/model/content_test.dart | 655 +------------------------------
test/model/katex_test.dart | 698 +++++++++++++++++++++++++++++++++
test/widgets/content_test.dart | 23 +-
3 files changed, 713 insertions(+), 663 deletions(-)
create mode 100644 test/model/katex_test.dart
diff --git a/test/model/content_test.dart b/test/model/content_test.dart
index 88bd11c66c..bead811079 100644
--- a/test/model/content_test.dart
+++ b/test/model/content_test.dart
@@ -699,647 +699,6 @@ class ContentExample {
]),
]);
- // The font sizes can be compared using the katex.css generated
- // from katex.scss :
- // https://unpkg.com/katex@0.16.21/dist/katex.css
- static const mathBlockKatexSizing = ContentExample(
- 'math block; KaTeX different sizing',
- // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2155476
- '```math\n\\Huge 1\n\\huge 2\n\\LARGE 3\n\\Large 4\n\\large 5\n\\normalsize 6\n\\small 7\n\\footnotesize 8\n\\scriptsize 9\n\\tiny 0\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- '1'
- '2'
- '3'
- '4'
- '5'
- '6'
- '7'
- '8'
- '9'
- '0
',
- [
- MathBlockNode(
- texSource: "\\Huge 1\n\\huge 2\n\\LARGE 3\n\\Large 4\n\\large 5\n\\normalsize 6\n\\small 7\n\\footnotesize 8\n\\scriptsize 9\n\\tiny 0",
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 2.488), // .reset-size6.size11
- text: '1',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 2.074), // .reset-size6.size10
- text: '2',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.728), // .reset-size6.size9
- text: '3',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.44), // .reset-size6.size8
- text: '4',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.2), // .reset-size6.size7
- text: '5',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.0), // .reset-size6.size6
- text: '6',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.9), // .reset-size6.size5
- text: '7',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.8), // .reset-size6.size4
- text: '8',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: '9',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.5), // .reset-size6.size1
- text: '0',
- nodes: null),
- ]),
- ]),
- ]);
-
- static const mathBlockKatexNestedSizing = ContentExample(
- 'math block; KaTeX nested sizing',
- '```math\n\\tiny {1 \\Huge 2}\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- '1'
- '2
',
- [
- MathBlockNode(
- texSource: '\\tiny {1 \\Huge 2}',
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.5), // reset-size6 size1
- text: null,
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: '1',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 4.976), // reset-size1 size11
- text: '2',
- nodes: null),
- ]),
- ]),
- ]),
- ]);
-
- static const mathBlockKatexDelimSizing = ContentExample(
- 'math block; KaTeX delimiter sizing',
- // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2147135
- '```math\n⟨ \\big( \\Big[ \\bigg⌈ \\Bigg⌊\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- '⟨'
- '('
- '['
- '⌈'
- '⌊
',
- [
- MathBlockNode(
- texSource: '⟨ \\big( \\Big[ \\bigg⌈ \\Bigg⌊',
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexStrutNode(heightEm: 3, verticalAlignEm: -1.25),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: '⟨',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size1'),
- text: '(',
- nodes: null),
- ]),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size2'),
- text: '[',
- nodes: null),
- ]),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size3'),
- text: '⌈',
- nodes: null),
- ]),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size4'),
- text: '⌊',
- nodes: null),
- ]),
- ]),
- ]),
- ]);
-
- static const mathBlockKatexSpace = ContentExample(
- 'math block; KaTeX space',
- // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2214883
- '```math\n1:2\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- '1'
- ''
- ':'
- ''
- ''
- ''
- '2
', [
- MathBlockNode(
- texSource: '1:2',
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexStrutNode(
- heightEm: 0.6444,
- verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: '1',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.2778),
- text: null,
- nodes: []),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: ':',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.2778),
- text: null,
- nodes: []),
- ]),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexStrutNode(
- heightEm: 0.6444,
- verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: '2',
- nodes: null),
- ]),
- ]),
- ]);
-
- static const mathBlockKatexSuperscript = ContentExample(
- 'math block, KaTeX superscript; single vlist-r, single vertical offset row',
- // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176734
- '```math\na\'\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- 'a'
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- '′
', [
- MathBlockNode(texSource: 'a\'', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.8019, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(
- fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'a', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -3.113 + 2.7,
- node: KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(fontSizeEm: 0.7), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: '′', nodes: null),
- ]),
- ]),
- ])),
- ]),
- ]),
- ]),
- ]),
- ]),
- ]);
-
- static const mathBlockKatexSubscript = ContentExample(
- 'math block, KaTeX subscript; two vlist-r, single vertical offset row',
- // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176735
- '```math\nx_n\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- 'x'
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- 'n'
- ''
- ''
- '
', [
- MathBlockNode(texSource: 'x_n', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.5806, verticalAlignEm: -0.15),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(
- fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'x', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.55 + 2.7,
- node: KatexSpanNode(
- styles: KatexSpanStyles(marginLeftEm: 0, marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'n', nodes: null),
- ]),
- ])),
- ]),
- ]),
- ]),
- ]),
- ]),
- ]);
-
- static const mathBlockKatexSubSuperScript = ContentExample(
- 'math block, KaTeX subsup script; two vlist-r, multiple vertical offset rows',
- // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176738
- '```math\n_u^o\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- 'u'
- ''
- ''
- ''
- 'o'
- ''
- ''
- '
', [
- MathBlockNode(texSource: "_u^o", nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.9614, verticalAlignEm: -0.247),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexSpanNode(
- styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.453 + 2.7,
- node: KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'u', nodes: null),
- ]),
- ])),
- KatexVlistRowNode(
- verticalOffsetEm: -3.113 + 2.7,
- node: KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'o', nodes: null),
- ]),
- ])),
- ]),
- ]),
- ]),
- ]),
- ]),
- ]);
-
- static const mathBlockKatexRaisebox = ContentExample(
- 'math block, KaTeX raisebox; single vlist-r, single vertical offset row',
- // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176739
- '```math\na\\raisebox{0.25em}{\$b\$}c\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- 'a'
- ''
- ''
- ''
- ''
- ''
- ''
- 'b'
- 'c
', [
- MathBlockNode(texSource: 'a\\raisebox{0.25em}{\$b\$}c', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.9444, verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'a', nodes: null),
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -3.25 + 3,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'b', nodes: null),
- ]),
- ])),
- ]),
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'c', nodes: null),
- ]),
- ]),
- ]);
-
- static const mathBlockKatexNegativeMargin = ContentExample(
- 'math block, KaTeX negative margin',
- // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2223563
- '```math\n1 \\! 2\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- '1'
- ''
- '2
', [
- MathBlockNode(texSource: '1 \\! 2', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: '1', nodes: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: '2', nodes: null),
- ]),
- ]),
- ]),
- ]);
-
- static const mathBlockKatexLogo = ContentExample(
- 'math block, KaTeX logo',
- // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2141902
- '```math\n\\KaTeX\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- 'K'
- ''
- ''
- ''
- ''
- ''
- ''
- 'A'
- ''
- ''
- 'T'
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- 'E'
- ''
- ''
- ''
- ''
- 'X
', [
- MathBlockNode(texSource: '\\KaTeX', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.8988, verticalAlignEm: -0.2155),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'K', nodes: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.17, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.905 + 2.7,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main', fontSizeEm: 0.7), // .reset-size6.size3
- text: 'A', nodes: null),
- ]),
- ])),
- ]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.15, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'T', nodes: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.7845 + 3,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'E', nodes: null),
- ]),
- ])),
- ]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.125, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'X', nodes: null),
- ]),
- ]),
- ]),
- ]),
- ]),
- ]),
- ]),
- ]),
- ]);
-
- static const mathBlockKatexNegativeMarginsOnVlistRow = ContentExample(
- 'math block, KaTeX negative margins on a vlist row',
- // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2224918
- '```math\nX_n\n```',
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- 'X'
- ''
- ''
- ''
- ''
- ''
- ''
- ''
- 'n'
- ''
- ''
- '
', [
- MathBlockNode(texSource: 'X_n', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.8333, verticalAlignEm: -0.15),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(
- marginRightEm: 0.07847,
- fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'X', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.55 + 2.7,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexNegativeMarginNode(leftOffsetEm: -0.0785, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'n', nodes: null),
- ]),
- ]),
- ]),
- ])),
- ]),
- ]),
- ]),
- ]),
- ]),
- ]);
-
static const imageSingle = ContentExample(
'single image',
// https://chat.zulip.org/#narrow/stream/7-test-here/topic/Thumbnails/near/1900103
@@ -2421,22 +1780,14 @@ void main() async {
testParseExample(ContentExample.codeBlockWithUnknownSpanType);
testParseExample(ContentExample.codeBlockFollowedByMultipleLineBreaks);
+ // The math examples in this file are about how math blocks and spans fit
+ // into the context of a Zulip message.
+ // For tests going deeper inside KaTeX content, see katex_test.dart.
testParseExample(ContentExample.mathBlock);
testParseExample(ContentExample.mathBlocksMultipleInParagraph);
testParseExample(ContentExample.mathBlockInQuote);
testParseExample(ContentExample.mathBlocksMultipleInQuote);
testParseExample(ContentExample.mathBlockBetweenImages);
- testParseExample(ContentExample.mathBlockKatexSizing);
- testParseExample(ContentExample.mathBlockKatexNestedSizing);
- testParseExample(ContentExample.mathBlockKatexDelimSizing);
- testParseExample(ContentExample.mathBlockKatexSpace);
- testParseExample(ContentExample.mathBlockKatexSuperscript);
- testParseExample(ContentExample.mathBlockKatexSubscript);
- testParseExample(ContentExample.mathBlockKatexSubSuperScript);
- testParseExample(ContentExample.mathBlockKatexRaisebox);
- testParseExample(ContentExample.mathBlockKatexNegativeMargin);
- testParseExample(ContentExample.mathBlockKatexLogo);
- testParseExample(ContentExample.mathBlockKatexNegativeMarginsOnVlistRow);
testParseExample(ContentExample.imageSingle);
testParseExample(ContentExample.imageSingleNoDimensions);
diff --git a/test/model/katex_test.dart b/test/model/katex_test.dart
new file mode 100644
index 0000000000..be2ba56d11
--- /dev/null
+++ b/test/model/katex_test.dart
@@ -0,0 +1,698 @@
+import 'dart:io';
+
+import 'package:zulip/model/settings.dart';
+import 'package:checks/checks.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test_api/scaffolding.dart';
+import 'package:zulip/model/content.dart';
+import 'package:zulip/model/katex.dart';
+
+import 'binding.dart';
+import 'content_test.dart';
+
+/// Holds examples of KaTeX Zulip content for test cases.
+///
+/// For guidance on writing examples, see comments on [ContentExample].
+abstract class KatexExample {
+ // The font sizes can be compared using the katex.css generated
+ // from katex.scss :
+ // https://unpkg.com/katex@0.16.21/dist/katex.css
+ static const mathBlockKatexSizing = ContentExample(
+ 'math block; KaTeX different sizing',
+ // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2155476
+ '```math\n\\Huge 1\n\\huge 2\n\\LARGE 3\n\\Large 4\n\\large 5\n\\normalsize 6\n\\small 7\n\\footnotesize 8\n\\scriptsize 9\n\\tiny 0\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ '1'
+ '2'
+ '3'
+ '4'
+ '5'
+ '6'
+ '7'
+ '8'
+ '9'
+ '0
',
+ [
+ MathBlockNode(
+ texSource: "\\Huge 1\n\\huge 2\n\\LARGE 3\n\\Large 4\n\\large 5\n\\normalsize 6\n\\small 7\n\\footnotesize 8\n\\scriptsize 9\n\\tiny 0",
+ nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: null,
+ nodes: [
+ KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 2.488), // .reset-size6.size11
+ text: '1',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 2.074), // .reset-size6.size10
+ text: '2',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.728), // .reset-size6.size9
+ text: '3',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.44), // .reset-size6.size8
+ text: '4',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.2), // .reset-size6.size7
+ text: '5',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.0), // .reset-size6.size6
+ text: '6',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.9), // .reset-size6.size5
+ text: '7',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.8), // .reset-size6.size4
+ text: '8',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: '9',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.5), // .reset-size6.size1
+ text: '0',
+ nodes: null),
+ ]),
+ ]),
+ ]);
+
+ static const mathBlockKatexNestedSizing = ContentExample(
+ 'math block; KaTeX nested sizing',
+ '```math\n\\tiny {1 \\Huge 2}\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ '1'
+ '2
',
+ [
+ MathBlockNode(
+ texSource: '\\tiny {1 \\Huge 2}',
+ nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: null,
+ nodes: [
+ KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.5), // reset-size6 size1
+ text: null,
+ nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: '1',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 4.976), // reset-size1 size11
+ text: '2',
+ nodes: null),
+ ]),
+ ]),
+ ]),
+ ]);
+
+ static const mathBlockKatexDelimSizing = ContentExample(
+ 'math block; KaTeX delimiter sizing',
+ // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2147135
+ '```math\n⟨ \\big( \\Big[ \\bigg⌈ \\Bigg⌊\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ '⟨'
+ '('
+ '['
+ '⌈'
+ '⌊
',
+ [
+ MathBlockNode(
+ texSource: '⟨ \\big( \\Big[ \\bigg⌈ \\Bigg⌊',
+ nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: null,
+ nodes: [
+ KatexStrutNode(heightEm: 3, verticalAlignEm: -1.25),
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: '⟨',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: null,
+ nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size1'),
+ text: '(',
+ nodes: null),
+ ]),
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: null,
+ nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size2'),
+ text: '[',
+ nodes: null),
+ ]),
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: null,
+ nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size3'),
+ text: '⌈',
+ nodes: null),
+ ]),
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: null,
+ nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size4'),
+ text: '⌊',
+ nodes: null),
+ ]),
+ ]),
+ ]),
+ ]);
+
+ static const mathBlockKatexSpace = ContentExample(
+ 'math block; KaTeX space',
+ // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2214883
+ '```math\n1:2\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ '1'
+ ''
+ ':'
+ ''
+ ''
+ ''
+ '2
', [
+ MathBlockNode(
+ texSource: '1:2',
+ nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: null,
+ nodes: [
+ KatexStrutNode(
+ heightEm: 0.6444,
+ verticalAlignEm: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: '1',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.2778),
+ text: null,
+ nodes: []),
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: ':',
+ nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.2778),
+ text: null,
+ nodes: []),
+ ]),
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: null,
+ nodes: [
+ KatexStrutNode(
+ heightEm: 0.6444,
+ verticalAlignEm: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(),
+ text: '2',
+ nodes: null),
+ ]),
+ ]),
+ ]);
+
+ static const mathBlockKatexSuperscript = ContentExample(
+ 'math block, KaTeX superscript; single vlist-r, single vertical offset row',
+ // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176734
+ '```math\na\'\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'a'
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ '′
', [
+ MathBlockNode(texSource: 'a\'', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.8019, verticalAlignEm: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(
+ fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'a', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
+ text: null, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -3.113 + 2.7,
+ node: KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.05),
+ text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(fontSizeEm: 0.7), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: '′', nodes: null),
+ ]),
+ ]),
+ ])),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]);
+
+ static const mathBlockKatexSubscript = ContentExample(
+ 'math block, KaTeX subscript; two vlist-r, single vertical offset row',
+ // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176735
+ '```math\nx_n\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'x'
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'n'
+ ''
+ ''
+ '
', [
+ MathBlockNode(texSource: 'x_n', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.5806, verticalAlignEm: -0.15),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(
+ fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'x', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
+ text: null, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.55 + 2.7,
+ node: KatexSpanNode(
+ styles: KatexSpanStyles(marginLeftEm: 0, marginRightEm: 0.05),
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'n', nodes: null),
+ ]),
+ ])),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]);
+
+ static const mathBlockKatexSubSuperScript = ContentExample(
+ 'math block, KaTeX subsup script; two vlist-r, multiple vertical offset rows',
+ // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176738
+ '```math\n_u^o\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'u'
+ ''
+ ''
+ ''
+ 'o'
+ ''
+ ''
+ '
', [
+ MathBlockNode(texSource: "_u^o", nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.9614, verticalAlignEm: -0.247),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexSpanNode(
+ styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
+ text: null, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.453 + 2.7,
+ node: KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.05),
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'u', nodes: null),
+ ]),
+ ])),
+ KatexVlistRowNode(
+ verticalOffsetEm: -3.113 + 2.7,
+ node: KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.05),
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'o', nodes: null),
+ ]),
+ ])),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]);
+
+ static const mathBlockKatexRaisebox = ContentExample(
+ 'math block, KaTeX raisebox; single vlist-r, single vertical offset row',
+ // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176739
+ '```math\na\\raisebox{0.25em}{\$b\$}c\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'a'
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'b'
+ 'c
', [
+ MathBlockNode(texSource: 'a\\raisebox{0.25em}{\$b\$}c', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.9444, verticalAlignEm: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'a', nodes: null),
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -3.25 + 3,
+ node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'b', nodes: null),
+ ]),
+ ])),
+ ]),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'c', nodes: null),
+ ]),
+ ]),
+ ]);
+
+ static const mathBlockKatexNegativeMargin = ContentExample(
+ 'math block, KaTeX negative margin',
+ // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2223563
+ '```math\n1 \\! 2\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ '1'
+ ''
+ '2
', [
+ MathBlockNode(texSource: '1 \\! 2', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: '1', nodes: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: '2', nodes: null),
+ ]),
+ ]),
+ ]),
+ ]);
+
+ static const mathBlockKatexLogo = ContentExample(
+ 'math block, KaTeX logo',
+ // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2141902
+ '```math\n\\KaTeX\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'K'
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'A'
+ ''
+ ''
+ 'T'
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'E'
+ ''
+ ''
+ ''
+ ''
+ 'X
', [
+ MathBlockNode(texSource: '\\KaTeX', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.8988, verticalAlignEm: -0.2155),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'K', nodes: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexNegativeMarginNode(leftOffsetEm: -0.17, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.905 + 2.7,
+ node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main', fontSizeEm: 0.7), // .reset-size6.size3
+ text: 'A', nodes: null),
+ ]),
+ ])),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexNegativeMarginNode(leftOffsetEm: -0.15, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'T', nodes: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.7845 + 3,
+ node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'E', nodes: null),
+ ]),
+ ])),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexNegativeMarginNode(leftOffsetEm: -0.125, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'X', nodes: null),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]);
+
+ static const mathBlockKatexNegativeMarginsOnVlistRow = ContentExample(
+ 'math block, KaTeX negative margins on a vlist row',
+ // https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2224918
+ '```math\nX_n\n```',
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'X'
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ 'n'
+ ''
+ ''
+ '
', [
+ MathBlockNode(texSource: 'X_n', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.8333, verticalAlignEm: -0.15),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(
+ marginRightEm: 0.07847,
+ fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'X', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
+ text: null, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.55 + 2.7,
+ node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexNegativeMarginNode(leftOffsetEm: -0.0785, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.05),
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'n', nodes: null),
+ ]),
+ ]),
+ ]),
+ ])),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]),
+ ]);
+}
+
+void main() async {
+ TestZulipBinding.ensureInitialized();
+
+ await testBinding.globalStore.settings.setBool(
+ BoolGlobalSetting.renderKatex, true);
+
+ testParseExample(KatexExample.mathBlockKatexSizing);
+ testParseExample(KatexExample.mathBlockKatexNestedSizing);
+ testParseExample(KatexExample.mathBlockKatexDelimSizing);
+ testParseExample(KatexExample.mathBlockKatexSpace);
+ testParseExample(KatexExample.mathBlockKatexSuperscript);
+ testParseExample(KatexExample.mathBlockKatexSubscript);
+ testParseExample(KatexExample.mathBlockKatexSubSuperScript);
+ testParseExample(KatexExample.mathBlockKatexRaisebox);
+ testParseExample(KatexExample.mathBlockKatexNegativeMargin);
+ testParseExample(KatexExample.mathBlockKatexLogo);
+ testParseExample(KatexExample.mathBlockKatexNegativeMarginsOnVlistRow);
+
+ test('all KaTeX content examples are tested', () {
+ // Check that every ContentExample defined above has a corresponding
+ // actual test case that runs on it. If you've added a new example
+ // and this test breaks, remember to add a `testParseExample` call for it.
+
+ // This implementation is a bit of a hack; it'd be cleaner to get the
+ // actual Dart parse tree using package:analyzer. Unfortunately that
+ // approach takes several seconds just to load the parser library, enough
+ // to add noticeably to the runtime of our whole test suite.
+ final thisFilename = Trace.current().frames[0].uri.path;
+ final source = File(thisFilename).readAsStringSync();
+ final declaredExamples = RegExp(multiLine: true,
+ r'^\s*static\s+(?:const|final)\s+(\w+)\s*=\s*ContentExample\s*(?:\.\s*inline\s*)?\(',
+ ).allMatches(source).map((m) => m.group(1));
+ final testedExamples = RegExp(multiLine: true,
+ r'^\s*testParseExample\s*\(\s*KatexExample\s*\.\s*(\w+)(?:,\s*skip:\s*true)?\s*\);',
+ ).allMatches(source).map((m) => m.group(1));
+ check(testedExamples).unorderedEquals(declaredExamples);
+ }, skip: Platform.isWindows, // [intended] purely analyzes source, so
+ // any one platform is enough; avoid dealing with Windows file paths
+ );
+}
diff --git a/test/widgets/content_test.dart b/test/widgets/content_test.dart
index 46ed10079e..86b6e4cc1c 100644
--- a/test/widgets/content_test.dart
+++ b/test/widgets/content_test.dart
@@ -23,6 +23,7 @@ import '../example_data.dart' as eg;
import '../flutter_checks.dart';
import '../model/binding.dart';
import '../model/content_test.dart';
+import '../model/katex_test.dart';
import '../model/store_checks.dart';
import '../model/test_store.dart';
import '../stdlib_checks.dart';
@@ -579,7 +580,7 @@ void main() {
group('characters render at specific offsets with specific size', () {
const testCases = <(ContentExample, List<(String, Offset, Size)>, {bool? skip})>[
- (ContentExample.mathBlockKatexSizing, skip: false, [
+ (KatexExample.mathBlockKatexSizing, skip: false, [
('1', Offset(0.00, 2.24), Size(25.59, 61.00)),
('2', Offset(25.59, 10.04), Size(21.33, 51.00)),
('3', Offset(46.91, 16.55), Size(17.77, 43.00)),
@@ -591,50 +592,50 @@ void main() {
('9', Offset(119.58, 35.91), Size(7.20, 17.00)),
('0', Offset(126.77, 39.68), Size(5.14, 12.00)),
]),
- (ContentExample.mathBlockKatexNestedSizing, skip: false, [
+ (KatexExample.mathBlockKatexNestedSizing, skip: false, [
('1', Offset(0.00, 40.24), Size(5.14, 12.00)),
('2', Offset(5.14, 2.80), Size(25.59, 61.00)),
]),
- (ContentExample.mathBlockKatexDelimSizing, skip: false, [
+ (KatexExample.mathBlockKatexDelimSizing, skip: false, [
('(', Offset(8.00, 20.14), Size(9.42, 25.00)),
('[', Offset(17.42, 20.14), Size(9.71, 25.00)),
('⌈', Offset(27.12, 20.14), Size(11.99, 25.00)),
('⌊', Offset(39.11, 20.14), Size(13.14, 25.00)),
]),
- (ContentExample.mathBlockKatexSpace, skip: false, [
+ (KatexExample.mathBlockKatexSpace, skip: false, [
('1', Offset(0.00, 2.24), Size(10.28, 25.00)),
(':', Offset(16.00, 2.24), Size(5.72, 25.00)),
('2', Offset(27.43, 2.24), Size(10.28, 25.00)),
]),
- (ContentExample.mathBlockKatexSuperscript, skip: false, [
+ (KatexExample.mathBlockKatexSuperscript, skip: false, [
('a', Offset(0.00, 5.28), Size(10.88, 25.00)),
('′', Offset(10.88, 1.13), Size(3.96, 17.00)),
]),
- (ContentExample.mathBlockKatexSubscript, skip: false, [
+ (KatexExample.mathBlockKatexSubscript, skip: false, [
('x', Offset(0.00, 5.28), Size(11.76, 25.00)),
('n', Offset(11.76, 13.65), Size(8.63, 17.00)),
]),
- (ContentExample.mathBlockKatexSubSuperScript, skip: false, [
+ (KatexExample.mathBlockKatexSubSuperScript, skip: false, [
('u', Offset(0.00, 15.65), Size(8.23, 17.00)),
('o', Offset(0.00, 2.07), Size(6.98, 17.00)),
]),
- (ContentExample.mathBlockKatexRaisebox, skip: false, [
+ (KatexExample.mathBlockKatexRaisebox, skip: false, [
('a', Offset(0.00, 4.16), Size(10.88, 25.00)),
('b', Offset(10.88, -0.66), Size(8.82, 25.00)),
('c', Offset(19.70, 4.16), Size(8.90, 25.00)),
]),
- (ContentExample.mathBlockKatexNegativeMargin, skip: false, [
+ (KatexExample.mathBlockKatexNegativeMargin, skip: false, [
('1', Offset(0.00, 3.12), Size(10.28, 25.00)),
('2', Offset(6.85, 3.36), Size(10.28, 25.00)),
]),
- (ContentExample.mathBlockKatexLogo, skip: false, [
+ (KatexExample.mathBlockKatexLogo, skip: false, [
('K', Offset(0.0, 8.64), Size(16.0, 25.0)),
('A', Offset(12.50, 10.85), Size(10.79, 17.0)),
('T', Offset(20.21, 9.36), Size(14.85, 25.0)),
('E', Offset(31.63, 14.52), Size(14.0, 25.0)),
('X', Offset(43.06, 9.85), Size(15.42, 25.0)),
]),
- (ContentExample.mathBlockKatexNegativeMarginsOnVlistRow, skip: false, [
+ (KatexExample.mathBlockKatexNegativeMarginsOnVlistRow, skip: false, [
('X', Offset(0.00, 7.04), Size(17.03, 25.00)),
('n', Offset(17.03, 15.90), Size(8.63, 17.00)),
]),
From 993bdc23b2e5945e9751f9c11150867f5bcc97f9 Mon Sep 17 00:00:00 2001
From: Greg Price
Date: Sat, 19 Jul 2025 14:09:57 -0700
Subject: [PATCH 02/11] katex test [nfc]: Make the KaTeX examples instances of
KatexExample
---
test/model/katex_test.dart | 33 ++++++++++++++++++---------------
test/widgets/content_test.dart | 2 +-
2 files changed, 19 insertions(+), 16 deletions(-)
diff --git a/test/model/katex_test.dart b/test/model/katex_test.dart
index be2ba56d11..11d43400b3 100644
--- a/test/model/katex_test.dart
+++ b/test/model/katex_test.dart
@@ -10,14 +10,17 @@ import 'package:zulip/model/katex.dart';
import 'binding.dart';
import 'content_test.dart';
-/// Holds examples of KaTeX Zulip content for test cases.
+/// An example of KaTeX Zulip content for test cases.
///
/// For guidance on writing examples, see comments on [ContentExample].
-abstract class KatexExample {
+class KatexExample extends ContentExample {
+ const KatexExample(super.description, super.markdown, super.html,
+ super.expectedNodes, {super.expectedText});
+
// The font sizes can be compared using the katex.css generated
// from katex.scss :
// https://unpkg.com/katex@0.16.21/dist/katex.css
- static const mathBlockKatexSizing = ContentExample(
+ static const mathBlockKatexSizing = KatexExample(
'math block; KaTeX different sizing',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2155476
'```math\n\\Huge 1\n\\huge 2\n\\LARGE 3\n\\Large 4\n\\large 5\n\\normalsize 6\n\\small 7\n\\footnotesize 8\n\\scriptsize 9\n\\tiny 0\n```',
@@ -91,7 +94,7 @@ abstract class KatexExample {
]),
]);
- static const mathBlockKatexNestedSizing = ContentExample(
+ static const mathBlockKatexNestedSizing = KatexExample(
'math block; KaTeX nested sizing',
'```math\n\\tiny {1 \\Huge 2}\n```',
''
@@ -130,7 +133,7 @@ abstract class KatexExample {
]),
]);
- static const mathBlockKatexDelimSizing = ContentExample(
+ static const mathBlockKatexDelimSizing = KatexExample(
'math block; KaTeX delimiter sizing',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2147135
'```math\n⟨ \\big( \\Big[ \\bigg⌈ \\Bigg⌊\n```',
@@ -199,7 +202,7 @@ abstract class KatexExample {
]),
]);
- static const mathBlockKatexSpace = ContentExample(
+ static const mathBlockKatexSpace = KatexExample(
'math block; KaTeX space',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2214883
'```math\n1:2\n```',
@@ -258,7 +261,7 @@ abstract class KatexExample {
]),
]);
- static const mathBlockKatexSuperscript = ContentExample(
+ static const mathBlockKatexSuperscript = KatexExample(
'math block, KaTeX superscript; single vlist-r, single vertical offset row',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176734
'```math\na\'\n```',
@@ -310,7 +313,7 @@ abstract class KatexExample {
]),
]);
- static const mathBlockKatexSubscript = ContentExample(
+ static const mathBlockKatexSubscript = KatexExample(
'math block, KaTeX subscript; two vlist-r, single vertical offset row',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176735
'```math\nx_n\n```',
@@ -366,7 +369,7 @@ abstract class KatexExample {
]),
]);
- static const mathBlockKatexSubSuperScript = ContentExample(
+ static const mathBlockKatexSubSuperScript = KatexExample(
'math block, KaTeX subsup script; two vlist-r, multiple vertical offset rows',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176738
'```math\n_u^o\n```',
@@ -436,7 +439,7 @@ abstract class KatexExample {
]),
]);
- static const mathBlockKatexRaisebox = ContentExample(
+ static const mathBlockKatexRaisebox = KatexExample(
'math block, KaTeX raisebox; single vlist-r, single vertical offset row',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176739
'```math\na\\raisebox{0.25em}{\$b\$}c\n```',
@@ -480,7 +483,7 @@ abstract class KatexExample {
]),
]);
- static const mathBlockKatexNegativeMargin = ContentExample(
+ static const mathBlockKatexNegativeMargin = KatexExample(
'math block, KaTeX negative margin',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2223563
'```math\n1 \\! 2\n```',
@@ -505,7 +508,7 @@ abstract class KatexExample {
]),
]);
- static const mathBlockKatexLogo = ContentExample(
+ static const mathBlockKatexLogo = KatexExample(
'math block, KaTeX logo',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2141902
'```math\n\\KaTeX\n```',
@@ -595,7 +598,7 @@ abstract class KatexExample {
]),
]);
- static const mathBlockKatexNegativeMarginsOnVlistRow = ContentExample(
+ static const mathBlockKatexNegativeMarginsOnVlistRow = KatexExample(
'math block, KaTeX negative margins on a vlist row',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2224918
'```math\nX_n\n```',
@@ -675,7 +678,7 @@ void main() async {
testParseExample(KatexExample.mathBlockKatexNegativeMarginsOnVlistRow);
test('all KaTeX content examples are tested', () {
- // Check that every ContentExample defined above has a corresponding
+ // Check that every KatexExample defined above has a corresponding
// actual test case that runs on it. If you've added a new example
// and this test breaks, remember to add a `testParseExample` call for it.
@@ -686,7 +689,7 @@ void main() async {
final thisFilename = Trace.current().frames[0].uri.path;
final source = File(thisFilename).readAsStringSync();
final declaredExamples = RegExp(multiLine: true,
- r'^\s*static\s+(?:const|final)\s+(\w+)\s*=\s*ContentExample\s*(?:\.\s*inline\s*)?\(',
+ r'^\s*static\s+(?:const|final)\s+(\w+)\s*=\s*KatexExample\s*(?:\.\s*inline\s*)?\(',
).allMatches(source).map((m) => m.group(1));
final testedExamples = RegExp(multiLine: true,
r'^\s*testParseExample\s*\(\s*KatexExample\s*\.\s*(\w+)(?:,\s*skip:\s*true)?\s*\);',
diff --git a/test/widgets/content_test.dart b/test/widgets/content_test.dart
index 86b6e4cc1c..abdcb9d25f 100644
--- a/test/widgets/content_test.dart
+++ b/test/widgets/content_test.dart
@@ -579,7 +579,7 @@ void main() {
});
group('characters render at specific offsets with specific size', () {
- const testCases = <(ContentExample, List<(String, Offset, Size)>, {bool? skip})>[
+ const testCases = <(KatexExample, List<(String, Offset, Size)>, {bool? skip})>[
(KatexExample.mathBlockKatexSizing, skip: false, [
('1', Offset(0.00, 2.24), Size(25.59, 61.00)),
('2', Offset(25.59, 10.04), Size(21.33, 51.00)),
From 6f66b06de42233ffe9584fa2b132b5cdc5ef6fa7 Mon Sep 17 00:00:00 2001
From: Greg Price
Date: Sat, 19 Jul 2025 13:52:10 -0700
Subject: [PATCH 03/11] content test [nfc]: Move some helpers to top level, so
importable
This will let us split some of this file's tests to a different file
and have them still use these helpers.
---
test/widgets/content_test.dart | 68 +++++++++++++++++-----------------
1 file changed, 34 insertions(+), 34 deletions(-)
diff --git a/test/widgets/content_test.dart b/test/widgets/content_test.dart
index abdcb9d25f..cae7414083 100644
--- a/test/widgets/content_test.dart
+++ b/test/widgets/content_test.dart
@@ -108,6 +108,40 @@ TextStyle? mergedStyleOf(WidgetTester tester, Pattern spanPattern, {
/// and reports the target's font size.
typedef TargetFontSizeFinder = double Function(InlineSpan rootSpan);
+Widget plainContent(String html) {
+ return Builder(builder: (context) =>
+ DefaultTextStyle(
+ style: ContentTheme.of(context).textStylePlainParagraph,
+ child: BlockContentList(nodes: parseContent(html).nodes)));
+}
+
+// TODO(#488) For content that we need to show outside a per-message context
+// or a context without a full PerAccountStore, make sure to include tests
+// that don't provide such context.
+Future prepareContent(WidgetTester tester, Widget child, {
+ List navObservers = const [],
+ bool wrapWithPerAccountStoreWidget = false,
+}) async {
+ if (wrapWithPerAccountStoreWidget) {
+ await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
+ }
+
+ addTearDown(testBinding.reset);
+
+ prepareBoringImageHttpClient();
+
+ await tester.pumpWidget(TestZulipApp(
+ accountId: wrapWithPerAccountStoreWidget ? eg.selfAccount.id : null,
+ navigatorObservers: navObservers,
+ child: child));
+ await tester.pump(); // global store
+ if (wrapWithPerAccountStoreWidget) {
+ await tester.pump();
+ }
+
+ debugNetworkImageHttpClientProvider = null;
+}
+
void main() {
// For testing a new content feature:
//
@@ -122,45 +156,11 @@ void main() {
TestZulipBinding.ensureInitialized();
- Widget plainContent(String html) {
- return Builder(builder: (context) =>
- DefaultTextStyle(
- style: ContentTheme.of(context).textStylePlainParagraph,
- child: BlockContentList(nodes: parseContent(html).nodes)));
- }
-
Widget messageContent(String html) {
return MessageContent(message: eg.streamMessage(content: html),
content: parseContent(html));
}
- // TODO(#488) For content that we need to show outside a per-message context
- // or a context without a full PerAccountStore, make sure to include tests
- // that don't provide such context.
- Future prepareContent(WidgetTester tester, Widget child, {
- List navObservers = const [],
- bool wrapWithPerAccountStoreWidget = false,
- }) async {
- if (wrapWithPerAccountStoreWidget) {
- await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
- }
-
- addTearDown(testBinding.reset);
-
- prepareBoringImageHttpClient();
-
- await tester.pumpWidget(TestZulipApp(
- accountId: wrapWithPerAccountStoreWidget ? eg.selfAccount.id : null,
- navigatorObservers: navObservers,
- child: child));
- await tester.pump(); // global store
- if (wrapWithPerAccountStoreWidget) {
- await tester.pump();
- }
-
- debugNetworkImageHttpClientProvider = null;
- }
-
/// Test that the given content example renders without throwing an exception.
///
/// This requires [ContentExample.expectedText] to be non-null in order to
From c5a10af328a2a72742448ccec9d6d665b09ea1e7 Mon Sep 17 00:00:00 2001
From: Greg Price
Date: Sat, 19 Jul 2025 14:20:28 -0700
Subject: [PATCH 04/11] katex [nfc]: Move KaTeX widgets to their own file
This is a pretty self-contained swath of our content widgets -- the
only interface the others use from these is KatexWidget. So it works
well to split into a separate file, paralleling the model files.
---
lib/widgets/content.dart | 227 --------------------------------
lib/widgets/katex.dart | 229 +++++++++++++++++++++++++++++++++
test/widgets/content_test.dart | 147 ++-------------------
test/widgets/katex_test.dart | 153 ++++++++++++++++++++++
4 files changed, 391 insertions(+), 365 deletions(-)
create mode 100644 test/widgets/katex_test.dart
diff --git a/lib/widgets/content.dart b/lib/widgets/content.dart
index 2263b74f8b..551956966e 100644
--- a/lib/widgets/content.dart
+++ b/lib/widgets/content.dart
@@ -1,7 +1,6 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
-import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
@@ -16,7 +15,6 @@ import '../model/binding.dart';
import '../model/content.dart';
import '../model/emoji.dart';
import '../model/internal_link.dart';
-import '../model/katex.dart';
import '../model/presence.dart';
import 'actions.dart';
import 'code_block.dart';
@@ -834,231 +832,6 @@ class MathBlock extends StatelessWidget {
}
}
-/// Creates a base text style for rendering KaTeX content.
-///
-/// This applies the CSS styles defined in .katex class in katex.scss :
-/// https://github.com/KaTeX/KaTeX/blob/613c3da8/src/styles/katex.scss#L13-L15
-///
-/// Requires the [style.fontSize] to be non-null.
-TextStyle mkBaseKatexTextStyle(TextStyle style) {
- return style.copyWith(
- fontSize: style.fontSize! * 1.21,
- fontFamily: 'KaTeX_Main',
- height: 1.2,
- fontWeight: FontWeight.normal,
- fontStyle: FontStyle.normal,
- textBaseline: TextBaseline.alphabetic,
- leadingDistribution: TextLeadingDistribution.even,
- decoration: TextDecoration.none,
- fontFamilyFallback: const []);
-}
-
-@visibleForTesting
-class KatexWidget extends StatelessWidget {
- const KatexWidget({
- super.key,
- required this.textStyle,
- required this.nodes,
- });
-
- final TextStyle textStyle;
- final List nodes;
-
- @override
- Widget build(BuildContext context) {
- Widget widget = _KatexNodeList(nodes: nodes);
-
- return Directionality(
- textDirection: TextDirection.ltr,
- child: DefaultTextStyle(
- style: mkBaseKatexTextStyle(textStyle).copyWith(
- color: ContentTheme.of(context).textStylePlainParagraph.color),
- child: widget));
- }
-}
-
-class _KatexNodeList extends StatelessWidget {
- const _KatexNodeList({required this.nodes});
-
- final List nodes;
-
- @override
- Widget build(BuildContext context) {
- return Text.rich(TextSpan(
- children: List.unmodifiable(nodes.map((e) {
- return WidgetSpan(
- alignment: PlaceholderAlignment.baseline,
- baseline: TextBaseline.alphabetic,
- // Work around a bug where text inside a WidgetSpan could be scaled
- // multiple times incorrectly, if the system font scale is larger
- // than 1x.
- // See: https://github.com/flutter/flutter/issues/126962
- child: MediaQuery(
- data: MediaQueryData(textScaler: TextScaler.noScaling),
- child: switch (e) {
- KatexSpanNode() => _KatexSpan(e),
- KatexStrutNode() => _KatexStrut(e),
- KatexVlistNode() => _KatexVlist(e),
- KatexNegativeMarginNode() => _KatexNegativeMargin(e),
- }));
- }))));
- }
-}
-
-class _KatexSpan extends StatelessWidget {
- const _KatexSpan(this.node);
-
- final KatexSpanNode node;
-
- @override
- Widget build(BuildContext context) {
- var em = DefaultTextStyle.of(context).style.fontSize!;
-
- Widget widget = const SizedBox.shrink();
- if (node.text != null) {
- widget = Text(node.text!);
- } else if (node.nodes != null && node.nodes!.isNotEmpty) {
- widget = _KatexNodeList(nodes: node.nodes!);
- }
-
- final styles = node.styles;
-
- // Currently, we expect `top` to be only present with the
- // vlist inner row span, and parser handles that explicitly.
- assert(styles.topEm == null);
-
- final fontFamily = styles.fontFamily;
- final fontSize = switch (styles.fontSizeEm) {
- double fontSizeEm => fontSizeEm * em,
- null => null,
- };
- if (fontSize != null) em = fontSize;
-
- final fontWeight = switch (styles.fontWeight) {
- KatexSpanFontWeight.bold => FontWeight.bold,
- null => null,
- };
- var fontStyle = switch (styles.fontStyle) {
- KatexSpanFontStyle.normal => FontStyle.normal,
- KatexSpanFontStyle.italic => FontStyle.italic,
- null => null,
- };
-
- TextStyle? textStyle;
- if (fontFamily != null ||
- fontSize != null ||
- fontWeight != null ||
- fontStyle != null) {
- // TODO(upstream) remove this workaround when upstream fixes the broken
- // rendering of KaTeX_Math font with italic font style on Android:
- // https://github.com/flutter/flutter/issues/167474
- if (defaultTargetPlatform == TargetPlatform.android &&
- fontFamily == 'KaTeX_Math') {
- fontStyle = FontStyle.normal;
- }
-
- textStyle = TextStyle(
- fontFamily: fontFamily,
- fontSize: fontSize,
- fontWeight: fontWeight,
- fontStyle: fontStyle,
- );
- }
- final textAlign = switch (styles.textAlign) {
- KatexSpanTextAlign.left => TextAlign.left,
- KatexSpanTextAlign.center => TextAlign.center,
- KatexSpanTextAlign.right => TextAlign.right,
- null => null,
- };
-
- if (textStyle != null || textAlign != null) {
- widget = DefaultTextStyle.merge(
- style: textStyle,
- textAlign: textAlign,
- child: widget);
- }
-
- widget = SizedBox(
- height: styles.heightEm != null
- ? styles.heightEm! * em
- : null,
- child: widget);
-
- final margin = switch ((styles.marginLeftEm, styles.marginRightEm)) {
- (null, null) => null,
- (null, final marginRightEm?) =>
- EdgeInsets.only(right: marginRightEm * em),
- (final marginLeftEm?, null) =>
- EdgeInsets.only(left: marginLeftEm * em),
- (final marginLeftEm?, final marginRightEm?) =>
- EdgeInsets.only(left: marginLeftEm * em, right: marginRightEm * em),
- };
-
- if (margin != null) {
- assert(margin.isNonNegative);
- widget = Padding(padding: margin, child: widget);
- }
-
- return widget;
- }
-}
-
-class _KatexStrut extends StatelessWidget {
- const _KatexStrut(this.node);
-
- final KatexStrutNode node;
-
- @override
- Widget build(BuildContext context) {
- final em = DefaultTextStyle.of(context).style.fontSize!;
-
- final verticalAlignEm = node.verticalAlignEm;
- if (verticalAlignEm == null) {
- return SizedBox(height: node.heightEm * em);
- }
-
- return SizedBox(
- height: node.heightEm * em,
- child: Baseline(
- baseline: (verticalAlignEm + node.heightEm) * em,
- baselineType: TextBaseline.alphabetic,
- child: const Text('')),
- );
- }
-}
-
-class _KatexVlist extends StatelessWidget {
- const _KatexVlist(this.node);
-
- final KatexVlistNode node;
-
- @override
- Widget build(BuildContext context) {
- final em = DefaultTextStyle.of(context).style.fontSize!;
-
- return Stack(children: List.unmodifiable(node.rows.map((row) {
- return Transform.translate(
- offset: Offset(0, row.verticalOffsetEm * em),
- child: _KatexSpan(row.node));
- })));
- }
-}
-
-class _KatexNegativeMargin extends StatelessWidget {
- const _KatexNegativeMargin(this.node);
-
- final KatexNegativeMarginNode node;
-
- @override
- Widget build(BuildContext context) {
- final em = DefaultTextStyle.of(context).style.fontSize!;
-
- return NegativeLeftOffset(
- leftOffset: node.leftOffsetEm * em,
- child: _KatexNodeList(nodes: node.nodes));
- }
-}
-
class WebsitePreview extends StatelessWidget {
const WebsitePreview({super.key, required this.node});
diff --git a/lib/widgets/katex.dart b/lib/widgets/katex.dart
index 9b89270c8b..9d439ffdd3 100644
--- a/lib/widgets/katex.dart
+++ b/lib/widgets/katex.dart
@@ -4,6 +4,235 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';
+import '../model/content.dart';
+import '../model/katex.dart';
+import 'content.dart';
+
+/// Creates a base text style for rendering KaTeX content.
+///
+/// This applies the CSS styles defined in .katex class in katex.scss :
+/// https://github.com/KaTeX/KaTeX/blob/613c3da8/src/styles/katex.scss#L13-L15
+///
+/// Requires the [style.fontSize] to be non-null.
+TextStyle mkBaseKatexTextStyle(TextStyle style) {
+ return style.copyWith(
+ fontSize: style.fontSize! * 1.21,
+ fontFamily: 'KaTeX_Main',
+ height: 1.2,
+ fontWeight: FontWeight.normal,
+ fontStyle: FontStyle.normal,
+ textBaseline: TextBaseline.alphabetic,
+ leadingDistribution: TextLeadingDistribution.even,
+ decoration: TextDecoration.none,
+ fontFamilyFallback: const []);
+}
+
+@visibleForTesting
+class KatexWidget extends StatelessWidget {
+ const KatexWidget({
+ super.key,
+ required this.textStyle,
+ required this.nodes,
+ });
+
+ final TextStyle textStyle;
+ final List nodes;
+
+ @override
+ Widget build(BuildContext context) {
+ Widget widget = _KatexNodeList(nodes: nodes);
+
+ return Directionality(
+ textDirection: TextDirection.ltr,
+ child: DefaultTextStyle(
+ style: mkBaseKatexTextStyle(textStyle).copyWith(
+ color: ContentTheme.of(context).textStylePlainParagraph.color),
+ child: widget));
+ }
+}
+
+class _KatexNodeList extends StatelessWidget {
+ const _KatexNodeList({required this.nodes});
+
+ final List nodes;
+
+ @override
+ Widget build(BuildContext context) {
+ return Text.rich(TextSpan(
+ children: List.unmodifiable(nodes.map((e) {
+ return WidgetSpan(
+ alignment: PlaceholderAlignment.baseline,
+ baseline: TextBaseline.alphabetic,
+ // Work around a bug where text inside a WidgetSpan could be scaled
+ // multiple times incorrectly, if the system font scale is larger
+ // than 1x.
+ // See: https://github.com/flutter/flutter/issues/126962
+ child: MediaQuery(
+ data: MediaQueryData(textScaler: TextScaler.noScaling),
+ child: switch (e) {
+ KatexSpanNode() => _KatexSpan(e),
+ KatexStrutNode() => _KatexStrut(e),
+ KatexVlistNode() => _KatexVlist(e),
+ KatexNegativeMarginNode() => _KatexNegativeMargin(e),
+ }));
+ }))));
+ }
+}
+
+class _KatexSpan extends StatelessWidget {
+ const _KatexSpan(this.node);
+
+ final KatexSpanNode node;
+
+ @override
+ Widget build(BuildContext context) {
+ var em = DefaultTextStyle.of(context).style.fontSize!;
+
+ Widget widget = const SizedBox.shrink();
+ if (node.text != null) {
+ widget = Text(node.text!);
+ } else if (node.nodes != null && node.nodes!.isNotEmpty) {
+ widget = _KatexNodeList(nodes: node.nodes!);
+ }
+
+ final styles = node.styles;
+
+ // Currently, we expect `top` to be only present with the
+ // vlist inner row span, and parser handles that explicitly.
+ assert(styles.topEm == null);
+
+ final fontFamily = styles.fontFamily;
+ final fontSize = switch (styles.fontSizeEm) {
+ double fontSizeEm => fontSizeEm * em,
+ null => null,
+ };
+ if (fontSize != null) em = fontSize;
+
+ final fontWeight = switch (styles.fontWeight) {
+ KatexSpanFontWeight.bold => FontWeight.bold,
+ null => null,
+ };
+ var fontStyle = switch (styles.fontStyle) {
+ KatexSpanFontStyle.normal => FontStyle.normal,
+ KatexSpanFontStyle.italic => FontStyle.italic,
+ null => null,
+ };
+
+ TextStyle? textStyle;
+ if (fontFamily != null ||
+ fontSize != null ||
+ fontWeight != null ||
+ fontStyle != null) {
+ // TODO(upstream) remove this workaround when upstream fixes the broken
+ // rendering of KaTeX_Math font with italic font style on Android:
+ // https://github.com/flutter/flutter/issues/167474
+ if (defaultTargetPlatform == TargetPlatform.android &&
+ fontFamily == 'KaTeX_Math') {
+ fontStyle = FontStyle.normal;
+ }
+
+ textStyle = TextStyle(
+ fontFamily: fontFamily,
+ fontSize: fontSize,
+ fontWeight: fontWeight,
+ fontStyle: fontStyle,
+ );
+ }
+ final textAlign = switch (styles.textAlign) {
+ KatexSpanTextAlign.left => TextAlign.left,
+ KatexSpanTextAlign.center => TextAlign.center,
+ KatexSpanTextAlign.right => TextAlign.right,
+ null => null,
+ };
+
+ if (textStyle != null || textAlign != null) {
+ widget = DefaultTextStyle.merge(
+ style: textStyle,
+ textAlign: textAlign,
+ child: widget);
+ }
+
+ widget = SizedBox(
+ height: styles.heightEm != null
+ ? styles.heightEm! * em
+ : null,
+ child: widget);
+
+ final margin = switch ((styles.marginLeftEm, styles.marginRightEm)) {
+ (null, null) => null,
+ (null, final marginRightEm?) =>
+ EdgeInsets.only(right: marginRightEm * em),
+ (final marginLeftEm?, null) =>
+ EdgeInsets.only(left: marginLeftEm * em),
+ (final marginLeftEm?, final marginRightEm?) =>
+ EdgeInsets.only(left: marginLeftEm * em, right: marginRightEm * em),
+ };
+
+ if (margin != null) {
+ assert(margin.isNonNegative);
+ widget = Padding(padding: margin, child: widget);
+ }
+
+ return widget;
+ }
+}
+
+class _KatexStrut extends StatelessWidget {
+ const _KatexStrut(this.node);
+
+ final KatexStrutNode node;
+
+ @override
+ Widget build(BuildContext context) {
+ final em = DefaultTextStyle.of(context).style.fontSize!;
+
+ final verticalAlignEm = node.verticalAlignEm;
+ if (verticalAlignEm == null) {
+ return SizedBox(height: node.heightEm * em);
+ }
+
+ return SizedBox(
+ height: node.heightEm * em,
+ child: Baseline(
+ baseline: (verticalAlignEm + node.heightEm) * em,
+ baselineType: TextBaseline.alphabetic,
+ child: const Text('')),
+ );
+ }
+}
+
+class _KatexVlist extends StatelessWidget {
+ const _KatexVlist(this.node);
+
+ final KatexVlistNode node;
+
+ @override
+ Widget build(BuildContext context) {
+ final em = DefaultTextStyle.of(context).style.fontSize!;
+
+ return Stack(children: List.unmodifiable(node.rows.map((row) {
+ return Transform.translate(
+ offset: Offset(0, row.verticalOffsetEm * em),
+ child: _KatexSpan(row.node));
+ })));
+ }
+}
+
+class _KatexNegativeMargin extends StatelessWidget {
+ const _KatexNegativeMargin(this.node);
+
+ final KatexNegativeMarginNode node;
+
+ @override
+ Widget build(BuildContext context) {
+ final em = DefaultTextStyle.of(context).style.fontSize!;
+
+ return NegativeLeftOffset(
+ leftOffset: node.leftOffsetEm * em,
+ child: _KatexNodeList(nodes: node.nodes));
+ }
+}
+
class NegativeLeftOffset extends SingleChildRenderObjectWidget {
NegativeLeftOffset({super.key, required this.leftOffset, super.child})
: assert(leftOffset.isNegative),
diff --git a/test/widgets/content_test.dart b/test/widgets/content_test.dart
index cae7414083..e615dc7b81 100644
--- a/test/widgets/content_test.dart
+++ b/test/widgets/content_test.dart
@@ -3,7 +3,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_checks/flutter_checks.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -14,6 +13,7 @@ import 'package:zulip/model/settings.dart';
import 'package:zulip/model/store.dart';
import 'package:zulip/widgets/content.dart';
import 'package:zulip/widgets/icons.dart';
+import 'package:zulip/widgets/katex.dart';
import 'package:zulip/widgets/message_list.dart';
import 'package:zulip/widgets/page.dart';
import 'package:zulip/widgets/store.dart';
@@ -23,7 +23,6 @@ import '../example_data.dart' as eg;
import '../flutter_checks.dart';
import '../model/binding.dart';
import '../model/content_test.dart';
-import '../model/katex_test.dart';
import '../model/store_checks.dart';
import '../model/test_store.dart';
import '../stdlib_checks.dart';
@@ -557,6 +556,10 @@ void main() {
});
group('MathBlock', () {
+ // See also katex_test.dart for detailed tests of
+ // how we render the inside of a math block.
+ // These tests check how it relates to the enclosing Zulip message.
+
testContentSmoke(ContentExample.mathBlock);
testWidgets('displays KaTeX source; experimental flag disabled', (tester) async {
@@ -577,100 +580,6 @@ void main() {
await prepareContent(tester, plainContent(ContentExample.mathBlock.html));
tester.widget(find.text('λ', findRichText: true));
});
-
- group('characters render at specific offsets with specific size', () {
- const testCases = <(KatexExample, List<(String, Offset, Size)>, {bool? skip})>[
- (KatexExample.mathBlockKatexSizing, skip: false, [
- ('1', Offset(0.00, 2.24), Size(25.59, 61.00)),
- ('2', Offset(25.59, 10.04), Size(21.33, 51.00)),
- ('3', Offset(46.91, 16.55), Size(17.77, 43.00)),
- ('4', Offset(64.68, 21.98), Size(14.80, 36.00)),
- ('5', Offset(79.48, 26.50), Size(12.34, 30.00)),
- ('6', Offset(91.82, 30.26), Size(10.28, 25.00)),
- ('7', Offset(102.10, 32.15), Size(9.25, 22.00)),
- ('8', Offset(111.35, 34.03), Size(8.23, 20.00)),
- ('9', Offset(119.58, 35.91), Size(7.20, 17.00)),
- ('0', Offset(126.77, 39.68), Size(5.14, 12.00)),
- ]),
- (KatexExample.mathBlockKatexNestedSizing, skip: false, [
- ('1', Offset(0.00, 40.24), Size(5.14, 12.00)),
- ('2', Offset(5.14, 2.80), Size(25.59, 61.00)),
- ]),
- (KatexExample.mathBlockKatexDelimSizing, skip: false, [
- ('(', Offset(8.00, 20.14), Size(9.42, 25.00)),
- ('[', Offset(17.42, 20.14), Size(9.71, 25.00)),
- ('⌈', Offset(27.12, 20.14), Size(11.99, 25.00)),
- ('⌊', Offset(39.11, 20.14), Size(13.14, 25.00)),
- ]),
- (KatexExample.mathBlockKatexSpace, skip: false, [
- ('1', Offset(0.00, 2.24), Size(10.28, 25.00)),
- (':', Offset(16.00, 2.24), Size(5.72, 25.00)),
- ('2', Offset(27.43, 2.24), Size(10.28, 25.00)),
- ]),
- (KatexExample.mathBlockKatexSuperscript, skip: false, [
- ('a', Offset(0.00, 5.28), Size(10.88, 25.00)),
- ('′', Offset(10.88, 1.13), Size(3.96, 17.00)),
- ]),
- (KatexExample.mathBlockKatexSubscript, skip: false, [
- ('x', Offset(0.00, 5.28), Size(11.76, 25.00)),
- ('n', Offset(11.76, 13.65), Size(8.63, 17.00)),
- ]),
- (KatexExample.mathBlockKatexSubSuperScript, skip: false, [
- ('u', Offset(0.00, 15.65), Size(8.23, 17.00)),
- ('o', Offset(0.00, 2.07), Size(6.98, 17.00)),
- ]),
- (KatexExample.mathBlockKatexRaisebox, skip: false, [
- ('a', Offset(0.00, 4.16), Size(10.88, 25.00)),
- ('b', Offset(10.88, -0.66), Size(8.82, 25.00)),
- ('c', Offset(19.70, 4.16), Size(8.90, 25.00)),
- ]),
- (KatexExample.mathBlockKatexNegativeMargin, skip: false, [
- ('1', Offset(0.00, 3.12), Size(10.28, 25.00)),
- ('2', Offset(6.85, 3.36), Size(10.28, 25.00)),
- ]),
- (KatexExample.mathBlockKatexLogo, skip: false, [
- ('K', Offset(0.0, 8.64), Size(16.0, 25.0)),
- ('A', Offset(12.50, 10.85), Size(10.79, 17.0)),
- ('T', Offset(20.21, 9.36), Size(14.85, 25.0)),
- ('E', Offset(31.63, 14.52), Size(14.0, 25.0)),
- ('X', Offset(43.06, 9.85), Size(15.42, 25.0)),
- ]),
- (KatexExample.mathBlockKatexNegativeMarginsOnVlistRow, skip: false, [
- ('X', Offset(0.00, 7.04), Size(17.03, 25.00)),
- ('n', Offset(17.03, 15.90), Size(8.63, 17.00)),
- ]),
- ];
-
- for (final testCase in testCases) {
- testWidgets(testCase.$1.description, (tester) async {
- await _loadKatexFonts();
-
- addTearDown(testBinding.reset);
- final globalSettings = testBinding.globalStore.settings;
- await globalSettings.setBool(BoolGlobalSetting.renderKatex, true);
- check(globalSettings).getBool(BoolGlobalSetting.renderKatex).isTrue();
-
- await prepareContent(tester, plainContent(testCase.$1.html));
-
- final baseRect = tester.getRect(find.byType(KatexWidget));
-
- for (final characterData in testCase.$2) {
- final character = characterData.$1;
- final expectedTopLeftOffset = characterData.$2;
- final expectedSize = characterData.$3;
-
- final rect = tester.getRect(find.text(character));
- final topLeftOffset = rect.topLeft - baseRect.topLeft;
- final size = rect.size;
-
- check(topLeftOffset)
- .within(distance: 0.05, from: expectedTopLeftOffset);
- check(size)
- .within(distance: 0.05, from: expectedSize);
- }
- }, skip: testCase.skip);
- }
- });
});
/// Make a [TargetFontSizeFinder] to pass to [checkFontSizeRatio],
@@ -1078,6 +987,10 @@ void main() {
});
group('inline math', () {
+ // See also katex_test.dart for detailed tests of
+ // how we render the inside of a math span.
+ // These tests check how it relates to the enclosing Zulip message.
+
testContentSmoke(ContentExample.mathInline);
testWidgets('maintains font-size ratio with surrounding text', (tester) async {
@@ -1486,45 +1399,3 @@ void main() {
});
});
}
-
-Future _loadKatexFonts() async {
- const fonts = {
- 'KaTeX_AMS': ['KaTeX_AMS-Regular.ttf'],
- 'KaTeX_Caligraphic': [
- 'KaTeX_Caligraphic-Regular.ttf',
- 'KaTeX_Caligraphic-Bold.ttf',
- ],
- 'KaTeX_Fraktur': [
- 'KaTeX_Fraktur-Regular.ttf',
- 'KaTeX_Fraktur-Bold.ttf',
- ],
- 'KaTeX_Main': [
- 'KaTeX_Main-Regular.ttf',
- 'KaTeX_Main-Bold.ttf',
- 'KaTeX_Main-Italic.ttf',
- 'KaTeX_Main-BoldItalic.ttf',
- ],
- 'KaTeX_Math': [
- 'KaTeX_Math-Italic.ttf',
- 'KaTeX_Math-BoldItalic.ttf',
- ],
- 'KaTeX_SansSerif': [
- 'KaTeX_SansSerif-Regular.ttf',
- 'KaTeX_SansSerif-Bold.ttf',
- 'KaTeX_SansSerif-Italic.ttf',
- ],
- 'KaTeX_Script': ['KaTeX_Script-Regular.ttf'],
- 'KaTeX_Size1': ['KaTeX_Size1-Regular.ttf'],
- 'KaTeX_Size2': ['KaTeX_Size2-Regular.ttf'],
- 'KaTeX_Size3': ['KaTeX_Size3-Regular.ttf'],
- 'KaTeX_Size4': ['KaTeX_Size4-Regular.ttf'],
- 'KaTeX_Typewriter': ['KaTeX_Typewriter-Regular.ttf'],
- };
- for (final MapEntry(key: fontFamily, value: fontFiles) in fonts.entries) {
- final fontLoader = FontLoader(fontFamily);
- for (final fontFile in fontFiles) {
- fontLoader.addFont(rootBundle.load('assets/KaTeX/$fontFile'));
- }
- await fontLoader.load();
- }
-}
diff --git a/test/widgets/katex_test.dart b/test/widgets/katex_test.dart
new file mode 100644
index 0000000000..eb1c776a30
--- /dev/null
+++ b/test/widgets/katex_test.dart
@@ -0,0 +1,153 @@
+import 'package:checks/checks.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_checks/flutter_checks.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:zulip/model/settings.dart';
+import 'package:zulip/widgets/katex.dart';
+
+import '../model/binding.dart';
+import '../model/katex_test.dart';
+import '../model/store_checks.dart';
+import 'content_test.dart';
+
+void main() {
+ TestZulipBinding.ensureInitialized();
+
+ group('MathBlock', () {
+ group('characters render at specific offsets with specific size', () {
+ const testCases = <(KatexExample, List<(String, Offset, Size)>, {bool? skip})>[
+ (KatexExample.mathBlockKatexSizing, skip: false, [
+ ('1', Offset(0.00, 2.24), Size(25.59, 61.00)),
+ ('2', Offset(25.59, 10.04), Size(21.33, 51.00)),
+ ('3', Offset(46.91, 16.55), Size(17.77, 43.00)),
+ ('4', Offset(64.68, 21.98), Size(14.80, 36.00)),
+ ('5', Offset(79.48, 26.50), Size(12.34, 30.00)),
+ ('6', Offset(91.82, 30.26), Size(10.28, 25.00)),
+ ('7', Offset(102.10, 32.15), Size(9.25, 22.00)),
+ ('8', Offset(111.35, 34.03), Size(8.23, 20.00)),
+ ('9', Offset(119.58, 35.91), Size(7.20, 17.00)),
+ ('0', Offset(126.77, 39.68), Size(5.14, 12.00)),
+ ]),
+ (KatexExample.mathBlockKatexNestedSizing, skip: false, [
+ ('1', Offset(0.00, 40.24), Size(5.14, 12.00)),
+ ('2', Offset(5.14, 2.80), Size(25.59, 61.00)),
+ ]),
+ (KatexExample.mathBlockKatexDelimSizing, skip: false, [
+ ('(', Offset(8.00, 20.14), Size(9.42, 25.00)),
+ ('[', Offset(17.42, 20.14), Size(9.71, 25.00)),
+ ('⌈', Offset(27.12, 20.14), Size(11.99, 25.00)),
+ ('⌊', Offset(39.11, 20.14), Size(13.14, 25.00)),
+ ]),
+ (KatexExample.mathBlockKatexSpace, skip: false, [
+ ('1', Offset(0.00, 2.24), Size(10.28, 25.00)),
+ (':', Offset(16.00, 2.24), Size(5.72, 25.00)),
+ ('2', Offset(27.43, 2.24), Size(10.28, 25.00)),
+ ]),
+ (KatexExample.mathBlockKatexSuperscript, skip: false, [
+ ('a', Offset(0.00, 5.28), Size(10.88, 25.00)),
+ ('′', Offset(10.88, 1.13), Size(3.96, 17.00)),
+ ]),
+ (KatexExample.mathBlockKatexSubscript, skip: false, [
+ ('x', Offset(0.00, 5.28), Size(11.76, 25.00)),
+ ('n', Offset(11.76, 13.65), Size(8.63, 17.00)),
+ ]),
+ (KatexExample.mathBlockKatexSubSuperScript, skip: false, [
+ ('u', Offset(0.00, 15.65), Size(8.23, 17.00)),
+ ('o', Offset(0.00, 2.07), Size(6.98, 17.00)),
+ ]),
+ (KatexExample.mathBlockKatexRaisebox, skip: false, [
+ ('a', Offset(0.00, 4.16), Size(10.88, 25.00)),
+ ('b', Offset(10.88, -0.66), Size(8.82, 25.00)),
+ ('c', Offset(19.70, 4.16), Size(8.90, 25.00)),
+ ]),
+ (KatexExample.mathBlockKatexNegativeMargin, skip: false, [
+ ('1', Offset(0.00, 3.12), Size(10.28, 25.00)),
+ ('2', Offset(6.85, 3.36), Size(10.28, 25.00)),
+ ]),
+ (KatexExample.mathBlockKatexLogo, skip: false, [
+ ('K', Offset(0.0, 8.64), Size(16.0, 25.0)),
+ ('A', Offset(12.50, 10.85), Size(10.79, 17.0)),
+ ('T', Offset(20.21, 9.36), Size(14.85, 25.0)),
+ ('E', Offset(31.63, 14.52), Size(14.0, 25.0)),
+ ('X', Offset(43.06, 9.85), Size(15.42, 25.0)),
+ ]),
+ (KatexExample.mathBlockKatexNegativeMarginsOnVlistRow, skip: false, [
+ ('X', Offset(0.00, 7.04), Size(17.03, 25.00)),
+ ('n', Offset(17.03, 15.90), Size(8.63, 17.00)),
+ ]),
+ ];
+
+ for (final testCase in testCases) {
+ testWidgets(testCase.$1.description, (tester) async {
+ await _loadKatexFonts();
+
+ addTearDown(testBinding.reset);
+ final globalSettings = testBinding.globalStore.settings;
+ await globalSettings.setBool(BoolGlobalSetting.renderKatex, true);
+ check(globalSettings).getBool(BoolGlobalSetting.renderKatex).isTrue();
+
+ await prepareContent(tester, plainContent(testCase.$1.html));
+
+ final baseRect = tester.getRect(find.byType(KatexWidget));
+
+ for (final characterData in testCase.$2) {
+ final character = characterData.$1;
+ final expectedTopLeftOffset = characterData.$2;
+ final expectedSize = characterData.$3;
+
+ final rect = tester.getRect(find.text(character));
+ final topLeftOffset = rect.topLeft - baseRect.topLeft;
+ final size = rect.size;
+
+ check(topLeftOffset)
+ .within(distance: 0.05, from: expectedTopLeftOffset);
+ check(size)
+ .within(distance: 0.05, from: expectedSize);
+ }
+ }, skip: testCase.skip);
+ }
+ });
+ });
+}
+
+Future _loadKatexFonts() async {
+ const fonts = {
+ 'KaTeX_AMS': ['KaTeX_AMS-Regular.ttf'],
+ 'KaTeX_Caligraphic': [
+ 'KaTeX_Caligraphic-Regular.ttf',
+ 'KaTeX_Caligraphic-Bold.ttf',
+ ],
+ 'KaTeX_Fraktur': [
+ 'KaTeX_Fraktur-Regular.ttf',
+ 'KaTeX_Fraktur-Bold.ttf',
+ ],
+ 'KaTeX_Main': [
+ 'KaTeX_Main-Regular.ttf',
+ 'KaTeX_Main-Bold.ttf',
+ 'KaTeX_Main-Italic.ttf',
+ 'KaTeX_Main-BoldItalic.ttf',
+ ],
+ 'KaTeX_Math': [
+ 'KaTeX_Math-Italic.ttf',
+ 'KaTeX_Math-BoldItalic.ttf',
+ ],
+ 'KaTeX_SansSerif': [
+ 'KaTeX_SansSerif-Regular.ttf',
+ 'KaTeX_SansSerif-Bold.ttf',
+ 'KaTeX_SansSerif-Italic.ttf',
+ ],
+ 'KaTeX_Script': ['KaTeX_Script-Regular.ttf'],
+ 'KaTeX_Size1': ['KaTeX_Size1-Regular.ttf'],
+ 'KaTeX_Size2': ['KaTeX_Size2-Regular.ttf'],
+ 'KaTeX_Size3': ['KaTeX_Size3-Regular.ttf'],
+ 'KaTeX_Size4': ['KaTeX_Size4-Regular.ttf'],
+ 'KaTeX_Typewriter': ['KaTeX_Typewriter-Regular.ttf'],
+ };
+ for (final MapEntry(key: fontFamily, value: fontFiles) in fonts.entries) {
+ final fontLoader = FontLoader(fontFamily);
+ for (final fontFile in fontFiles) {
+ fontLoader.addFont(rootBundle.load('assets/KaTeX/$fontFile'));
+ }
+ await fontLoader.load();
+ }
+}
From 7fb722e808119a080e03da6979abecea860f25fd Mon Sep 17 00:00:00 2001
From: Greg Price
Date: Sat, 19 Jul 2025 14:51:51 -0700
Subject: [PATCH 05/11] katex test [nfc]: Make some examples more compact
---
test/model/katex_test.dart | 316 +++++++++++++++----------------------
1 file changed, 124 insertions(+), 192 deletions(-)
diff --git a/test/model/katex_test.dart b/test/model/katex_test.dart
index 11d43400b3..6f4e175a72 100644
--- a/test/model/katex_test.dart
+++ b/test/model/katex_test.dart
@@ -40,57 +40,43 @@ class KatexExample extends ContentExample {
'7'
'8'
'9'
- '0
',
- [
+ '0
', [
MathBlockNode(
texSource: "\\Huge 1\n\\huge 2\n\\LARGE 3\n\\Large 4\n\\large 5\n\\normalsize 6\n\\small 7\n\\footnotesize 8\n\\scriptsize 9\n\\tiny 0",
nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 2.488), // .reset-size6.size11
- text: '1',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 2.074), // .reset-size6.size10
- text: '2',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.728), // .reset-size6.size9
- text: '3',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.44), // .reset-size6.size8
- text: '4',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.2), // .reset-size6.size7
- text: '5',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.0), // .reset-size6.size6
- text: '6',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.9), // .reset-size6.size5
- text: '7',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.8), // .reset-size6.size4
- text: '8',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: '9',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.5), // .reset-size6.size1
- text: '0',
- nodes: null),
- ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 2.488), // .reset-size6.size11
+ text: '1', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 2.074), // .reset-size6.size10
+ text: '2', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.728), // .reset-size6.size9
+ text: '3', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.44), // .reset-size6.size8
+ text: '4', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.2), // .reset-size6.size7
+ text: '5', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.0), // .reset-size6.size6
+ text: '6', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.9), // .reset-size6.size5
+ text: '7', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.8), // .reset-size6.size4
+ text: '8', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: '9', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.5), // .reset-size6.size1
+ text: '0', nodes: null),
+ ]),
]),
]);
@@ -106,31 +92,21 @@ class KatexExample extends ContentExample {
''
''
'1'
- '2',
- [
- MathBlockNode(
- texSource: '\\tiny {1 \\Huge 2}',
- nodes: [
+ '2', [
+ MathBlockNode(texSource: '\\tiny {1 \\Huge 2}', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
+ styles: KatexSpanStyles(fontSizeEm: 0.5), // reset-size6 size1
+ text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(),
+ text: '1', nodes: null),
KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.5), // reset-size6 size1
- text: null,
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: '1',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 4.976), // reset-size1 size11
- text: '2',
- nodes: null),
- ]),
+ styles: KatexSpanStyles(fontSizeEm: 4.976), // reset-size1 size11
+ text: '2', nodes: null),
]),
]),
+ ]),
]);
static const mathBlockKatexDelimSizing = KatexExample(
@@ -148,58 +124,34 @@ class KatexExample extends ContentExample {
'('
'['
'⌈'
- '⌊',
- [
- MathBlockNode(
- texSource: '⟨ \\big( \\Big[ \\bigg⌈ \\Bigg⌊',
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexStrutNode(heightEm: 3, verticalAlignEm: -1.25),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: '⟨',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size1'),
- text: '(',
- nodes: null),
- ]),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size2'),
- text: '[',
- nodes: null),
- ]),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size3'),
- text: '⌈',
- nodes: null),
- ]),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size4'),
- text: '⌊',
- nodes: null),
- ]),
- ]),
+ '⌊', [
+ MathBlockNode(texSource: '⟨ \\big( \\Big[ \\bigg⌈ \\Bigg⌊', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 3, verticalAlignEm: -1.25),
+ KatexSpanNode(styles: KatexSpanStyles(),
+ text: '⟨', nodes: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size1'),
+ text: '(', nodes: null),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size2'),
+ text: '[', nodes: null),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size3'),
+ text: '⌈', nodes: null),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size4'),
+ text: '⌊', nodes: null),
+ ]),
]),
+ ]),
]);
static const mathBlockKatexSpace = KatexExample(
@@ -219,46 +171,26 @@ class KatexExample extends ContentExample {
''
''
'2', [
- MathBlockNode(
- texSource: '1:2',
- nodes: [
+ MathBlockNode(texSource: '1:2', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
+ KatexSpanNode(styles: KatexSpanStyles(),
+ text: '1', nodes: null),
KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexStrutNode(
- heightEm: 0.6444,
- verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: '1',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.2778),
- text: null,
- nodes: []),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: ':',
- nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.2778),
- text: null,
- nodes: []),
- ]),
+ styles: KatexSpanStyles(marginRightEm: 0.2778),
+ text: null, nodes: []),
+ KatexSpanNode(styles: KatexSpanStyles(),
+ text: ':', nodes: null),
KatexSpanNode(
- styles: KatexSpanStyles(),
- text: null,
- nodes: [
- KatexStrutNode(
- heightEm: 0.6444,
- verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(),
- text: '2',
- nodes: null),
- ]),
+ styles: KatexSpanStyles(marginRightEm: 0.2778),
+ text: null, nodes: []),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
+ KatexSpanNode(styles: KatexSpanStyles(),
+ text: '2', nodes: null),
]),
+ ]),
]);
static const mathBlockKatexSuperscript = KatexExample(
@@ -553,46 +485,46 @@ class KatexExample extends ContentExample {
text: 'K', nodes: null),
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.17, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.905 + 2.7,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main', fontSizeEm: 0.7), // .reset-size6.size3
- text: 'A', nodes: null),
- ]),
- ])),
- ]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.15, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'T', nodes: null),
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.905 + 2.7,
+ node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main', fontSizeEm: 0.7), // .reset-size6.size3
+ text: 'A', nodes: null),
+ ]),
+ ])),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexNegativeMarginNode(leftOffsetEm: -0.15, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'T', nodes: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.7845 + 3,
+ node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'E', nodes: null),
+ ]),
+ ])),
+ ]),
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.7845 + 3,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'E', nodes: null),
- ]),
- ])),
- ]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.125, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'X', nodes: null),
- ]),
+ KatexNegativeMarginNode(leftOffsetEm: -0.125, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'X', nodes: null),
]),
]),
]),
]),
+ ]),
]),
]),
]),
From 15c68e0815589fbb60a7ef91c2750b784088db19 Mon Sep 17 00:00:00 2001
From: Greg Price
Date: Sat, 19 Jul 2025 14:59:51 -0700
Subject: [PATCH 06/11] katex test [nfc]: Simplify a bit with a
KatexExample.block constructor
This removes some repetitive noisy bits from these examples,
as well as a level of indentation.
---
test/model/katex_test.dart | 586 +++++++++++++++++------------------
test/widgets/katex_test.dart | 2 +-
2 files changed, 283 insertions(+), 305 deletions(-)
diff --git a/test/model/katex_test.dart b/test/model/katex_test.dart
index 6f4e175a72..581719fdba 100644
--- a/test/model/katex_test.dart
+++ b/test/model/katex_test.dart
@@ -14,16 +14,18 @@ import 'content_test.dart';
///
/// For guidance on writing examples, see comments on [ContentExample].
class KatexExample extends ContentExample {
- const KatexExample(super.description, super.markdown, super.html,
- super.expectedNodes, {super.expectedText});
+ KatexExample.block(String description, String texSource, String html,
+ List? expectedNodes)
+ : super(description, '```math\n$texSource\n```', html,
+ [MathBlockNode(texSource: texSource, nodes: expectedNodes)]);
// The font sizes can be compared using the katex.css generated
// from katex.scss :
// https://unpkg.com/katex@0.16.21/dist/katex.css
- static const mathBlockKatexSizing = KatexExample(
+ static final mathBlockKatexSizing = KatexExample.block(
'math block; KaTeX different sizing',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2155476
- '```math\n\\Huge 1\n\\huge 2\n\\LARGE 3\n\\Large 4\n\\large 5\n\\normalsize 6\n\\small 7\n\\footnotesize 8\n\\scriptsize 9\n\\tiny 0\n```',
+ '\\Huge 1\n\\huge 2\n\\LARGE 3\n\\Large 4\n\\large 5\n\\normalsize 6\n\\small 7\n\\footnotesize 8\n\\scriptsize 9\n\\tiny 0',
''
''
'
', [
- MathBlockNode(
- texSource: "\\Huge 1\n\\huge 2\n\\LARGE 3\n\\Large 4\n\\large 5\n\\normalsize 6\n\\small 7\n\\footnotesize 8\n\\scriptsize 9\n\\tiny 0",
- nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 2.488), // .reset-size6.size11
- text: '1', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 2.074), // .reset-size6.size10
- text: '2', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.728), // .reset-size6.size9
- text: '3', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.44), // .reset-size6.size8
- text: '4', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.2), // .reset-size6.size7
- text: '5', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 1.0), // .reset-size6.size6
- text: '6', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.9), // .reset-size6.size5
- text: '7', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.8), // .reset-size6.size4
- text: '8', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: '9', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.5), // .reset-size6.size1
- text: '0', nodes: null),
- ]),
- ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 2.488), // .reset-size6.size11
+ text: '1', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 2.074), // .reset-size6.size10
+ text: '2', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.728), // .reset-size6.size9
+ text: '3', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.44), // .reset-size6.size8
+ text: '4', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.2), // .reset-size6.size7
+ text: '5', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 1.0), // .reset-size6.size6
+ text: '6', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.9), // .reset-size6.size5
+ text: '7', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.8), // .reset-size6.size4
+ text: '8', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: '9', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.5), // .reset-size6.size1
+ text: '0', nodes: null),
+ ]),
]);
- static const mathBlockKatexNestedSizing = KatexExample(
+ static final mathBlockKatexNestedSizing = KatexExample.block(
'math block; KaTeX nested sizing',
- '```math\n\\tiny {1 \\Huge 2}\n```',
+ '\\tiny {1 \\Huge 2}',
''
''
'
', [
- MathBlockNode(texSource: '\\tiny {1 \\Huge 2}', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.5), // reset-size6 size1
- text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(),
- text: '1', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 4.976), // reset-size1 size11
- text: '2', nodes: null),
- ]),
- ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.5), // reset-size6 size1
+ text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(),
+ text: '1', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 4.976), // reset-size1 size11
+ text: '2', nodes: null),
+ ]),
]),
]);
- static const mathBlockKatexDelimSizing = KatexExample(
+ static final mathBlockKatexDelimSizing = KatexExample.block(
'math block; KaTeX delimiter sizing',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2147135
- '```math\n⟨ \\big( \\Big[ \\bigg⌈ \\Bigg⌊\n```',
+ '⟨ \\big( \\Big[ \\bigg⌈ \\Bigg⌊',
''
''
'
', [
- MathBlockNode(texSource: '⟨ \\big( \\Big[ \\bigg⌈ \\Bigg⌊', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 3, verticalAlignEm: -1.25),
+ KatexSpanNode(styles: KatexSpanStyles(),
+ text: '⟨', nodes: null),
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 3, verticalAlignEm: -1.25),
- KatexSpanNode(styles: KatexSpanStyles(),
- text: '⟨', nodes: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size1'),
- text: '(', nodes: null),
- ]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size2'),
- text: '[', nodes: null),
- ]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size3'),
- text: '⌈', nodes: null),
- ]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Size4'),
- text: '⌊', nodes: null),
- ]),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size1'),
+ text: '(', nodes: null),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size2'),
+ text: '[', nodes: null),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size3'),
+ text: '⌈', nodes: null),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Size4'),
+ text: '⌊', nodes: null),
]),
]),
]);
- static const mathBlockKatexSpace = KatexExample(
+ static final mathBlockKatexSpace = KatexExample.block(
'math block; KaTeX space',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2214883
- '```math\n1:2\n```',
+ '1:2',
''
''
''
@@ -171,32 +165,30 @@ class KatexExample extends ContentExample {
''
''
'2
', [
- MathBlockNode(texSource: '1:2', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(),
- text: '1', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.2778),
- text: null, nodes: []),
- KatexSpanNode(styles: KatexSpanStyles(),
- text: ':', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.2778),
- text: null, nodes: []),
- ]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(),
- text: '2', nodes: null),
- ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
+ KatexSpanNode(styles: KatexSpanStyles(),
+ text: '1', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.2778),
+ text: null, nodes: []),
+ KatexSpanNode(styles: KatexSpanStyles(),
+ text: ':', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.2778),
+ text: null, nodes: []),
+ ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
+ KatexSpanNode(styles: KatexSpanStyles(),
+ text: '2', nodes: null),
]),
]);
- static const mathBlockKatexSuperscript = KatexExample(
+ static final mathBlockKatexSuperscript = KatexExample.block(
'math block, KaTeX superscript; single vlist-r, single vertical offset row',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176734
- '```math\na\'\n```',
+ 'a\'',
''
''
'
', [
- MathBlockNode(texSource: 'a\'', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.8019, verticalAlignEm: null),
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.8019, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(
- fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'a', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -3.113 + 2.7,
- node: KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(fontSizeEm: 0.7), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: '′', nodes: null),
- ]),
+ KatexSpanNode(
+ styles: KatexSpanStyles(
+ fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'a', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
+ text: null, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -3.113 + 2.7,
+ node: KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.05),
+ text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(fontSizeEm: 0.7), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: '′', nodes: null),
]),
- ])),
- ]),
+ ]),
+ ])),
]),
- ]),
+ ]),
]),
]),
]);
- static const mathBlockKatexSubscript = KatexExample(
+ static final mathBlockKatexSubscript = KatexExample.block(
'math block, KaTeX subscript; two vlist-r, single vertical offset row',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176735
- '```math\nx_n\n```',
+ 'x_n',
''
''
''
''
'
', [
- MathBlockNode(texSource: 'x_n', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.5806, verticalAlignEm: -0.15),
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.5806, verticalAlignEm: -0.15),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(
- fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'x', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.55 + 2.7,
- node: KatexSpanNode(
- styles: KatexSpanStyles(marginLeftEm: 0, marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'n', nodes: null),
- ]),
- ])),
- ]),
+ KatexSpanNode(
+ styles: KatexSpanStyles(
+ fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'x', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
+ text: null, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.55 + 2.7,
+ node: KatexSpanNode(
+ styles: KatexSpanStyles(marginLeftEm: 0, marginRightEm: 0.05),
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'n', nodes: null),
+ ]),
+ ])),
]),
- ]),
+ ]),
]),
]),
]);
- static const mathBlockKatexSubSuperScript = KatexExample(
+ static final mathBlockKatexSubSuperScript = KatexExample.block(
'math block, KaTeX subsup script; two vlist-r, multiple vertical offset rows',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176738
- '```math\n_u^o\n```',
+ '_u^o',
''
''
''
''
'
', [
- MathBlockNode(texSource: "_u^o", nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.9614, verticalAlignEm: -0.247),
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.9614, verticalAlignEm: -0.247),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexSpanNode(
- styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.453 + 2.7,
- node: KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'u', nodes: null),
- ]),
- ])),
- KatexVlistRowNode(
- verticalOffsetEm: -3.113 + 2.7,
- node: KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'o', nodes: null),
- ]),
- ])),
- ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexSpanNode(
+ styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
+ text: null, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.453 + 2.7,
+ node: KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.05),
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'u', nodes: null),
+ ]),
+ ])),
+ KatexVlistRowNode(
+ verticalOffsetEm: -3.113 + 2.7,
+ node: KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.05),
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'o', nodes: null),
+ ]),
+ ])),
]),
- ]),
+ ]),
]),
]),
]);
- static const mathBlockKatexRaisebox = KatexExample(
+ static final mathBlockKatexRaisebox = KatexExample.block(
'math block, KaTeX raisebox; single vlist-r, single vertical offset row',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2176739
- '```math\na\\raisebox{0.25em}{\$b\$}c\n```',
+ 'a\\raisebox{0.25em}{\$b\$}c',
''
''
''
'c
', [
- MathBlockNode(texSource: 'a\\raisebox{0.25em}{\$b\$}c', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.9444, verticalAlignEm: null),
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'a', nodes: null),
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -3.25 + 3,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'b', nodes: null),
- ]),
- ])),
- ]),
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'c', nodes: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.9444, verticalAlignEm: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'a', nodes: null),
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -3.25 + 3,
+ node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'b', nodes: null),
+ ]),
+ ])),
]),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'c', nodes: null),
]),
]);
- static const mathBlockKatexNegativeMargin = KatexExample(
+ static final mathBlockKatexNegativeMargin = KatexExample.block(
'math block, KaTeX negative margin',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2223563
- '```math\n1 \\! 2\n```',
+ '1 \\! 2',
''
''
''
@@ -428,22 +412,20 @@ class KatexExample extends ContentExample {
'1'
''
'2
', [
- MathBlockNode(texSource: '1 \\! 2', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: '1', nodes: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: '2', nodes: null),
- ]),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: '1', nodes: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: '2', nodes: null),
]),
]),
]);
- static const mathBlockKatexLogo = KatexExample(
+ static final mathBlockKatexLogo = KatexExample.block(
'math block, KaTeX logo',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2141902
- '```math\n\\KaTeX\n```',
+ '\\KaTeX',
''
''
''
''
'X
', [
- MathBlockNode(texSource: '\\KaTeX', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.8988, verticalAlignEm: -0.2155),
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.8988, verticalAlignEm: -0.2155),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'K', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'K', nodes: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexNegativeMarginNode(leftOffsetEm: -0.17, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.905 + 2.7,
+ node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main', fontSizeEm: 0.7), // .reset-size6.size3
+ text: 'A', nodes: null),
+ ]),
+ ])),
+ ]),
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.17, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.905 + 2.7,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main', fontSizeEm: 0.7), // .reset-size6.size3
- text: 'A', nodes: null),
- ]),
- ])),
- ]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.15, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'T', nodes: null),
+ KatexNegativeMarginNode(leftOffsetEm: -0.15, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'T', nodes: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.7845 + 3,
+ node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'E', nodes: null),
+ ]),
+ ])),
+ ]),
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.7845 + 3,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'E', nodes: null),
- ]),
- ])),
- ]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
- KatexNegativeMarginNode(leftOffsetEm: -0.125, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'X', nodes: null),
- ]),
+ KatexNegativeMarginNode(leftOffsetEm: -0.125, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
+ text: 'X', nodes: null),
]),
]),
]),
@@ -530,10 +510,10 @@ class KatexExample extends ContentExample {
]),
]);
- static const mathBlockKatexNegativeMarginsOnVlistRow = KatexExample(
+ static final mathBlockKatexNegativeMarginsOnVlistRow = KatexExample.block(
'math block, KaTeX negative margins on a vlist row',
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Rajesh/near/2224918
- '```math\nX_n\n```',
+ 'X_n',
''
''
''
@@ -553,39 +533,37 @@ class KatexExample extends ContentExample {
''
''
'
', [
- MathBlockNode(texSource: 'X_n', nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexStrutNode(heightEm: 0.8333, verticalAlignEm: -0.15),
KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexStrutNode(heightEm: 0.8333, verticalAlignEm: -0.15),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(
- marginRightEm: 0.07847,
- fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'X', nodes: null),
- KatexSpanNode(
- styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
- KatexVlistNode(rows: [
- KatexVlistRowNode(
- verticalOffsetEm: -2.55 + 2.7,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexNegativeMarginNode(leftOffsetEm: -0.0785, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
- KatexSpanNode(
- styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'n', nodes: null),
- ]),
- ]),
- ]),
- ])),
- ]),
+ KatexSpanNode(
+ styles: KatexSpanStyles(
+ marginRightEm: 0.07847,
+ fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'X', nodes: null),
+ KatexSpanNode(
+ styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
+ text: null, nodes: [
+ KatexVlistNode(rows: [
+ KatexVlistRowNode(
+ verticalOffsetEm: -2.55 + 2.7,
+ node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexNegativeMarginNode(leftOffsetEm: -0.0785, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(marginRightEm: 0.05),
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
+ text: null, nodes: [
+ KatexSpanNode(
+ styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
+ text: 'n', nodes: null),
+ ]),
+ ]),
+ ]),
+ ])),
]),
- ]),
+ ]),
]),
]),
]);
@@ -621,7 +599,7 @@ void main() async {
final thisFilename = Trace.current().frames[0].uri.path;
final source = File(thisFilename).readAsStringSync();
final declaredExamples = RegExp(multiLine: true,
- r'^\s*static\s+(?:const|final)\s+(\w+)\s*=\s*KatexExample\s*(?:\.\s*inline\s*)?\(',
+ r'^\s*static\s+(?:const|final)\s+(\w+)\s*=\s*KatexExample\s*(?:\.\s*(?:inline|block)\s*)?\(',
).allMatches(source).map((m) => m.group(1));
final testedExamples = RegExp(multiLine: true,
r'^\s*testParseExample\s*\(\s*KatexExample\s*\.\s*(\w+)(?:,\s*skip:\s*true)?\s*\);',
diff --git a/test/widgets/katex_test.dart b/test/widgets/katex_test.dart
index eb1c776a30..325bcf0b6a 100644
--- a/test/widgets/katex_test.dart
+++ b/test/widgets/katex_test.dart
@@ -15,7 +15,7 @@ void main() {
group('MathBlock', () {
group('characters render at specific offsets with specific size', () {
- const testCases = <(KatexExample, List<(String, Offset, Size)>, {bool? skip})>[
+ final testCases = <(KatexExample, List<(String, Offset, Size)>, {bool? skip})>[
(KatexExample.mathBlockKatexSizing, skip: false, [
('1', Offset(0.00, 2.24), Size(25.59, 61.00)),
('2', Offset(25.59, 10.04), Size(21.33, 51.00)),
From 5e1211b1a445f52617e5db660a3c45e5159586ae Mon Sep 17 00:00:00 2001
From: Greg Price
Date: Sat, 19 Jul 2025 15:01:27 -0700
Subject: [PATCH 07/11] katex test [nfc]: Use Dart raw strings to avoid
double-backslash
This is available to us now that these strings don't include the
leading "```math" and trailing "```" lines, and the newlines
separating them from the main content.
---
test/model/katex_test.dart | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/test/model/katex_test.dart b/test/model/katex_test.dart
index 581719fdba..7dc850d09a 100644
--- a/test/model/katex_test.dart
+++ b/test/model/katex_test.dart
@@ -80,7 +80,7 @@ class KatexExample extends ContentExample {
static final mathBlockKatexNestedSizing = KatexExample.block(
'math block; KaTeX nested sizing',
- '\\tiny {1 \\Huge 2}',
+ r'\tiny {1 \Huge 2}',
''
''
'
\n',
[QuotationNode([
MathBlockNode(texSource: 'a', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.4306, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
fontFamily: 'KaTeX_Math',
fontStyle: KatexSpanFontStyle.italic),
- text: 'a',
- nodes: null),
+ text: 'a'),
]),
]),
MathBlockNode(texSource: 'b', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.6944, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
fontFamily: 'KaTeX_Math',
fontStyle: KatexSpanFontStyle.italic),
- text: 'b',
- nodes: null),
+ text: 'b'),
]),
]),
])]);
@@ -680,13 +673,13 @@ class ContentExample {
originalHeight: null),
]),
MathBlockNode(texSource: 'a', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.4306, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
fontFamily: 'KaTeX_Math',
fontStyle: KatexSpanFontStyle.italic),
- text: 'a', nodes: null),
+ text: 'a'),
]),
]),
ImageNodeList([
diff --git a/test/model/katex_test.dart b/test/model/katex_test.dart
index 21ae51058b..babfb270f4 100644
--- a/test/model/katex_test.dart
+++ b/test/model/katex_test.dart
@@ -48,38 +48,38 @@ class KatexExample extends ContentExample {
'8'
'9'
'0', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 2.488), // .reset-size6.size11
- text: '1', nodes: null),
+ text: '1'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 2.074), // .reset-size6.size10
- text: '2', nodes: null),
+ text: '2'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 1.728), // .reset-size6.size9
- text: '3', nodes: null),
+ text: '3'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 1.44), // .reset-size6.size8
- text: '4', nodes: null),
+ text: '4'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 1.2), // .reset-size6.size7
- text: '5', nodes: null),
+ text: '5'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 1.0), // .reset-size6.size6
- text: '6', nodes: null),
+ text: '6'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 0.9), // .reset-size6.size5
- text: '7', nodes: null),
+ text: '7'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 0.8), // .reset-size6.size4
- text: '8', nodes: null),
+ text: '8'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: '9', nodes: null),
+ text: '9'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 0.5), // .reset-size6.size1
- text: '0', nodes: null),
+ text: '0'),
]),
]);
@@ -96,16 +96,16 @@ class KatexExample extends ContentExample {
''
'1'
'2', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 0.5), // reset-size6 size1
- text: null, nodes: [
+ nodes: [
KatexSpanNode(styles: KatexSpanStyles(),
- text: '1', nodes: null),
+ text: '1'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 4.976), // reset-size1 size11
- text: '2', nodes: null),
+ text: '2'),
]),
]),
]);
@@ -126,29 +126,29 @@ class KatexExample extends ContentExample {
'['
'⌈'
'⌊', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 3, verticalAlignEm: -1.25),
KatexSpanNode(styles: KatexSpanStyles(),
- text: '⟨', nodes: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ text: '⟨'),
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Size1'),
- text: '(', nodes: null),
+ text: '('),
]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Size2'),
- text: '[', nodes: null),
+ text: '['),
]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Size3'),
- text: '⌈', nodes: null),
+ text: '⌈'),
]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Size4'),
- text: '⌊', nodes: null),
+ text: '⌊'),
]),
]),
]);
@@ -170,23 +170,23 @@ class KatexExample extends ContentExample {
''
''
'2', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
KatexSpanNode(styles: KatexSpanStyles(),
- text: '1', nodes: null),
+ text: '1'),
KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.2778),
- text: null, nodes: []),
+ nodes: []),
KatexSpanNode(styles: KatexSpanStyles(),
- text: ':', nodes: null),
+ text: ':'),
KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.2778),
- text: null, nodes: []),
+ nodes: []),
]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
KatexSpanNode(styles: KatexSpanStyles(),
- text: '2', nodes: null),
+ text: '2'),
]),
]);
@@ -212,25 +212,25 @@ class KatexExample extends ContentExample {
''
''
'′', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.8019, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(
fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'a', nodes: null),
+ text: 'a'),
KatexSpanNode(
styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
+ nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -3.113 + 2.7,
node: KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(fontSizeEm: 0.7), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: '′', nodes: null),
+ nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(fontSizeEm: 0.7), nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), text: '′'),
]),
]),
])),
@@ -264,28 +264,28 @@ class KatexExample extends ContentExample {
''
''
'', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.5806, verticalAlignEm: -0.15),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(
fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'x', nodes: null),
+ text: 'x'),
KatexSpanNode(
styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
+ nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -2.55 + 2.7,
node: KatexSpanNode(
styles: KatexSpanStyles(marginLeftEm: 0, marginRightEm: 0.05),
- text: null, nodes: [
+ nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
+ nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'n', nodes: null),
+ text: 'n'),
]),
])),
]),
@@ -322,38 +322,38 @@ class KatexExample extends ContentExample {
''
''
'', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.9614, verticalAlignEm: -0.247),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
KatexSpanNode(
styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
+ nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -2.453 + 2.7,
node: KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
+ nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
+ nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'u', nodes: null),
+ text: 'u'),
]),
])),
KatexVlistRowNode(
verticalOffsetEm: -3.113 + 2.7,
node: KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
+ nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
+ nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'o', nodes: null),
+ text: 'o'),
]),
])),
]),
@@ -382,25 +382,25 @@ class KatexExample extends ContentExample {
''
'b'
'c', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.9444, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'a', nodes: null),
+ text: 'a'),
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -3.25 + 3,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ node: KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'b', nodes: null),
+ text: 'b'),
]),
])),
]),
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'c', nodes: null),
+ text: 'c'),
]),
]);
@@ -417,12 +417,12 @@ class KatexExample extends ContentExample {
'1'
''
'2', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: '1', nodes: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexSpanNode(styles: KatexSpanStyles(), text: '1'),
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: '2', nodes: null),
+ KatexSpanNode(styles: KatexSpanStyles(), text: '2'),
]),
]),
]);
@@ -463,49 +463,49 @@ class KatexExample extends ContentExample {
''
''
'X', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.8988, verticalAlignEm: -0.2155),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'K', nodes: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ text: 'K'),
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.17, nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -2.905 + 2.7,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ node: KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Main', fontSizeEm: 0.7), // .reset-size6.size3
- text: 'A', nodes: null),
+ text: 'A'),
]),
])),
]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.15, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'T', nodes: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ text: 'T'),
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -2.7845 + 3,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ node: KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'E', nodes: null),
+ text: 'E'),
]),
])),
]),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: []),
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.125, nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
- text: 'X', nodes: null),
+ text: 'X'),
]),
]),
]),
@@ -538,31 +538,31 @@ class KatexExample extends ContentExample {
''
''
'', [
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexStrutNode(heightEm: 0.8333, verticalAlignEm: -0.15),
- KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexSpanNode(
styles: KatexSpanStyles(
marginRightEm: 0.07847,
fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'X', nodes: null),
+ text: 'X'),
KatexSpanNode(
styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
- text: null, nodes: [
+ nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -2.55 + 2.7,
- node: KatexSpanNode(styles: KatexSpanStyles(), text: null, nodes: [
+ node: KatexSpanNode(styles: KatexSpanStyles(), nodes: [
KatexNegativeMarginNode(leftOffsetEm: -0.0785, nodes: [
KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.05),
- text: null, nodes: [
+ nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 0.7), // .reset-size6.size3
- text: null, nodes: [
+ nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
- text: 'n', nodes: null),
+ text: 'n'),
]),
]),
]),
From 8a39d0453fdb07424f39185a9205f62eb083050e Mon Sep 17 00:00:00 2001
From: Greg Price
Date: Sat, 19 Jul 2025 15:14:49 -0700
Subject: [PATCH 10/11] katex [nfc]: Let KatexSpanNode.styles default to empty
We have a lot of calls to the KatexSpanNode constructor in our tests,
and a large fraction of them pass an empty `styles`. Let that be the
default, reducing some noise. It's a natural default, since it
effectively means "do nothing".
---
lib/model/content.dart | 2 +-
lib/model/katex.dart | 1 -
test/model/content_test.dart | 16 +++----
test/model/katex_test.dart | 93 +++++++++++++++++-------------------
4 files changed, 53 insertions(+), 59 deletions(-)
diff --git a/lib/model/content.dart b/lib/model/content.dart
index 94bab3e6ee..c0a6d3bc9d 100644
--- a/lib/model/content.dart
+++ b/lib/model/content.dart
@@ -380,7 +380,7 @@ sealed class KatexNode extends ContentNode {
class KatexSpanNode extends KatexNode {
const KatexSpanNode({
- required this.styles,
+ this.styles = const KatexSpanStyles(),
this.text,
this.nodes,
super.debugHtmlNode,
diff --git a/lib/model/katex.dart b/lib/model/katex.dart
index 7e278987f0..d7d09d5ea2 100644
--- a/lib/model/katex.dart
+++ b/lib/model/katex.dart
@@ -345,7 +345,6 @@ class _KatexParser {
if (marginLeftIsNegative) {
child = KatexSpanNode(
- styles: KatexSpanStyles(),
nodes: [KatexNegativeMarginNode(
leftOffsetEm: marginLeftEm!,
nodes: [child])]);
diff --git a/test/model/content_test.dart b/test/model/content_test.dart
index 24126357b1..5eaf5500fa 100644
--- a/test/model/content_test.dart
+++ b/test/model/content_test.dart
@@ -518,7 +518,7 @@ class ContentExample {
' \\lambda '
'λ',
MathInlineNode(texSource: r'\lambda', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.6944, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
@@ -537,7 +537,7 @@ class ContentExample {
'\\lambda'
'λ',
[MathBlockNode(texSource: r'\lambda', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.6944, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
@@ -561,7 +561,7 @@ class ContentExample {
'b'
'b', [
MathBlockNode(texSource: 'a', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.4306, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
@@ -571,7 +571,7 @@ class ContentExample {
]),
]),
MathBlockNode(texSource: 'b', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.6944, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
@@ -598,7 +598,7 @@ class ContentExample {
'
\n\n',
[QuotationNode([
MathBlockNode(texSource: r'\lambda', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.6944, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
@@ -626,7 +626,7 @@ class ContentExample {
'
\n\n',
[QuotationNode([
MathBlockNode(texSource: 'a', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.4306, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
@@ -636,7 +636,7 @@ class ContentExample {
]),
]),
MathBlockNode(texSource: 'b', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.6944, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
@@ -673,7 +673,7 @@ class ContentExample {
originalHeight: null),
]),
MathBlockNode(texSource: 'a', nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.4306, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(
diff --git a/test/model/katex_test.dart b/test/model/katex_test.dart
index babfb270f4..3b0916d3c0 100644
--- a/test/model/katex_test.dart
+++ b/test/model/katex_test.dart
@@ -48,7 +48,7 @@ class KatexExample extends ContentExample {
'8'
'9'
'0', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 2.488), // .reset-size6.size11
@@ -96,13 +96,12 @@ class KatexExample extends ContentExample {
''
'1'
'2', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 1.6034, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 0.5), // reset-size6 size1
nodes: [
- KatexSpanNode(styles: KatexSpanStyles(),
- text: '1'),
+ KatexSpanNode(text: '1'),
KatexSpanNode(
styles: KatexSpanStyles(fontSizeEm: 4.976), // reset-size1 size11
text: '2'),
@@ -126,26 +125,25 @@ class KatexExample extends ContentExample {
'['
'⌈'
'⌊', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 3, verticalAlignEm: -1.25),
- KatexSpanNode(styles: KatexSpanStyles(),
- text: '⟨'),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(text: '⟨'),
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Size1'),
text: '('),
]),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Size2'),
text: '['),
]),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Size3'),
text: '⌈'),
]),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Size4'),
text: '⌊'),
@@ -170,23 +168,20 @@ class KatexExample extends ContentExample {
''
''
'2', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(),
- text: '1'),
+ KatexSpanNode(text: '1'),
KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.2778),
nodes: []),
- KatexSpanNode(styles: KatexSpanStyles(),
- text: ':'),
+ KatexSpanNode(text: ':'),
KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.2778),
nodes: []),
]),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(),
- text: '2'),
+ KatexSpanNode(text: '2'),
]),
]);
@@ -212,9 +207,9 @@ class KatexExample extends ContentExample {
''
''
'′', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.8019, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(
fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
@@ -229,8 +224,8 @@ class KatexExample extends ContentExample {
styles: KatexSpanStyles(marginRightEm: 0.05),
nodes: [
KatexSpanNode(styles: KatexSpanStyles(fontSizeEm: 0.7), nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: '′'),
+ KatexSpanNode(nodes: [
+ KatexSpanNode(text: '′'),
]),
]),
])),
@@ -264,9 +259,9 @@ class KatexExample extends ContentExample {
''
''
'', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.5806, verticalAlignEm: -0.15),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(
fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
@@ -322,10 +317,10 @@ class KatexExample extends ContentExample {
''
''
'', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.9614, verticalAlignEm: -0.247),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
+ KatexSpanNode(nodes: [
+ KatexSpanNode(nodes: []),
KatexSpanNode(
styles: KatexSpanStyles(textAlign: KatexSpanTextAlign.left),
nodes: [
@@ -382,7 +377,7 @@ class KatexExample extends ContentExample {
''
'b'
'c', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.9444, verticalAlignEm: null),
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
@@ -390,8 +385,8 @@ class KatexExample extends ContentExample {
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -3.25 + 3,
- node: KatexSpanNode(styles: KatexSpanStyles(), nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ node: KatexSpanNode(nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
text: 'b'),
@@ -417,12 +412,12 @@ class KatexExample extends ContentExample {
'1'
''
'2', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.6444, verticalAlignEm: null),
- KatexSpanNode(styles: KatexSpanStyles(), text: '1'),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
+ KatexSpanNode(text: '1'),
+ KatexSpanNode(nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), text: '2'),
+ KatexSpanNode(text: '2'),
]),
]),
]);
@@ -463,45 +458,45 @@ class KatexExample extends ContentExample {
''
''
'X', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.8988, verticalAlignEm: -0.2155),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
text: 'K'),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
+ KatexSpanNode(nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.17, nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -2.905 + 2.7,
- node: KatexSpanNode(styles: KatexSpanStyles(), nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ node: KatexSpanNode(nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Main', fontSizeEm: 0.7), // .reset-size6.size3
text: 'A'),
]),
])),
]),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
+ KatexSpanNode(nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.15, nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
text: 'T'),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
+ KatexSpanNode(nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.1667, nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -2.7845 + 3,
- node: KatexSpanNode(styles: KatexSpanStyles(), nodes: [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ node: KatexSpanNode(nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
text: 'E'),
]),
])),
]),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: []),
+ KatexSpanNode(nodes: []),
KatexNegativeMarginNode(leftOffsetEm: -0.125, nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Main'),
@@ -538,9 +533,9 @@ class KatexExample extends ContentExample {
''
''
'', [
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.8333, verticalAlignEm: -0.15),
- KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(
marginRightEm: 0.07847,
@@ -552,7 +547,7 @@ class KatexExample extends ContentExample {
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -2.55 + 2.7,
- node: KatexSpanNode(styles: KatexSpanStyles(), nodes: [
+ node: KatexSpanNode(nodes: [
KatexNegativeMarginNode(leftOffsetEm: -0.0785, nodes: [
KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.05),
From 5f85799648832593a1974540a1214dcfde23962c Mon Sep 17 00:00:00 2001
From: Greg Price
Date: Sat, 19 Jul 2025 16:34:32 -0700
Subject: [PATCH 11/11] katex [nfc]: Add docs explaining the different
content-node classes
---
lib/model/content.dart | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/lib/model/content.dart b/lib/model/content.dart
index c0a6d3bc9d..78d7af24bc 100644
--- a/lib/model/content.dart
+++ b/lib/model/content.dart
@@ -341,6 +341,11 @@ class CodeBlockSpanNode extends ContentNode {
}
}
+/// A complete KaTeX math expression within Zulip content,
+/// whether block or inline.
+///
+/// The content nodes that are descendants of this node
+/// will all be of KaTeX-specific types, such as [KatexNode].
sealed class MathNode extends ContentNode {
const MathNode({
super.debugHtmlNode,
@@ -374,10 +379,15 @@ sealed class MathNode extends ContentNode {
}
}
+/// A content node that expects a generic KaTeX context from its parent.
+///
+/// Each of these will have a [MathNode] as an ancestor.
sealed class KatexNode extends ContentNode {
const KatexNode({super.debugHtmlNode});
}
+/// A generic KaTeX content node, corresponding to any span in KaTeX HTML
+/// that we don't otherwise specially handle.
class KatexSpanNode extends KatexNode {
const KatexSpanNode({
this.styles = const KatexSpanStyles(),
@@ -411,6 +421,7 @@ class KatexSpanNode extends KatexNode {
}
}
+/// A KaTeX strut, corresponding to a `span.strut` node in KaTeX HTML.
class KatexStrutNode extends KatexNode {
const KatexStrutNode({
required this.heightEm,
@@ -429,6 +440,12 @@ class KatexStrutNode extends KatexNode {
}
}
+/// A KaTeX "vertical list", corresponding to a `span.vlist-t` in KaTeX HTML.
+///
+/// These nodes in KaTeX HTML have a very specific structure.
+/// The children of these nodes in our tree correspond in the HTML to
+/// certain great-grandchildren (certain `> .vlist-r > .vlist > span`)
+/// of the `.vlist-t` node.
class KatexVlistNode extends KatexNode {
const KatexVlistNode({
required this.rows,
@@ -443,6 +460,11 @@ class KatexVlistNode extends KatexNode {
}
}
+/// An element of a KaTeX "vertical list"; a child of a [KatexVlistNode].
+///
+/// These correspond to certain `.vlist-t > .vlist-r > .vlist > span` nodes
+/// in KaTeX HTML. The [KatexVlistNode] parent in our tree
+/// corresponds to the `.vlist-t` great-grandparent in the HTML.
class KatexVlistRowNode extends ContentNode {
const KatexVlistRowNode({
required this.verticalOffsetEm,
@@ -465,6 +487,11 @@ class KatexVlistRowNode extends ContentNode {
}
}
+/// A KaTeX node corresponding to negative values for `margin-left`
+/// or `margin-right` in the inline CSS style of a KaTeX HTML node.
+///
+/// The parser synthesizes these as additional nodes, not corresponding
+/// directly to any node in the HTML.
class KatexNegativeMarginNode extends KatexNode {
const KatexNegativeMarginNode({
required this.leftOffsetEm,