Skip to content

Commit 3dac839

Browse files
committed
Merge remote-tracking branch 'pr/1698'
2 parents 1fe817c + de8b678 commit 3dac839

File tree

5 files changed

+569
-1
lines changed

5 files changed

+569
-1
lines changed

lib/model/content.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,37 @@ class KatexStrutNode extends KatexNode {
429429
}
430430
}
431431

432+
class KatexVlistNode extends KatexNode {
433+
const KatexVlistNode({
434+
required this.rows,
435+
super.debugHtmlNode,
436+
});
437+
438+
final List<KatexVlistRowNode> rows;
439+
440+
@override
441+
List<DiagnosticsNode> debugDescribeChildren() {
442+
return rows.map((row) => row.toDiagnosticsNode()).toList();
443+
}
444+
}
445+
446+
class KatexVlistRowNode extends ContentNode {
447+
const KatexVlistRowNode({
448+
required this.verticalOffsetEm,
449+
required this.node,
450+
super.debugHtmlNode,
451+
});
452+
453+
final double verticalOffsetEm;
454+
final KatexSpanNode node;
455+
456+
@override
457+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
458+
super.debugFillProperties(properties);
459+
properties.add(DoubleProperty('verticalOffsetEm', verticalOffsetEm));
460+
}
461+
}
462+
432463
class MathBlockNode extends MathNode implements BlockContentNode {
433464
const MathBlockNode({
434465
super.debugHtmlNode,

lib/model/katex.dart

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,102 @@ class _KatexParser {
209209
debugHtmlNode: debugHtmlNode);
210210
}
211211

212+
if (element.className == 'vlist-t'
213+
|| element.className == 'vlist-t vlist-t2') {
214+
final vlistT = element;
215+
if (vlistT.nodes.isEmpty) throw _KatexHtmlParseError();
216+
if (vlistT.attributes.containsKey('style')) throw _KatexHtmlParseError();
217+
218+
final hasTwoVlistR = vlistT.className == 'vlist-t vlist-t2';
219+
if (!hasTwoVlistR && vlistT.nodes.length != 1) throw _KatexHtmlParseError();
220+
221+
if (hasTwoVlistR) {
222+
if (vlistT.nodes case [
223+
_,
224+
dom.Element(localName: 'span', className: 'vlist-r', nodes: [
225+
dom.Element(localName: 'span', className: 'vlist', nodes: [
226+
dom.Element(localName: 'span', className: '', nodes: []),
227+
]),
228+
]),
229+
]) {
230+
// Do nothing.
231+
} else {
232+
throw _KatexHtmlParseError();
233+
}
234+
}
235+
236+
if (vlistT.nodes.first
237+
case dom.Element(localName: 'span', className: 'vlist-r') &&
238+
final vlistR) {
239+
if (vlistR.attributes.containsKey('style')) throw _KatexHtmlParseError();
240+
241+
if (vlistR.nodes.first
242+
case dom.Element(localName: 'span', className: 'vlist') &&
243+
final vlist) {
244+
final rows = <KatexVlistRowNode>[];
245+
246+
for (final innerSpan in vlist.nodes) {
247+
if (innerSpan case dom.Element(
248+
localName: 'span',
249+
nodes: [
250+
dom.Element(localName: 'span', className: 'pstrut') &&
251+
final pstrutSpan,
252+
...final otherSpans,
253+
],
254+
)) {
255+
if (innerSpan.className != '') {
256+
throw _KatexHtmlParseError('unexpected CSS class for '
257+
'vlist inner span: ${innerSpan.className}');
258+
}
259+
260+
var styles = _parseSpanInlineStyles(innerSpan);
261+
if (styles == null) throw _KatexHtmlParseError();
262+
if (styles.verticalAlignEm != null) throw _KatexHtmlParseError();
263+
final topEm = styles.topEm ?? 0;
264+
265+
styles = styles.filter(topEm: false);
266+
267+
final pstrutStyles = _parseSpanInlineStyles(pstrutSpan);
268+
if (pstrutStyles == null) throw _KatexHtmlParseError();
269+
if (pstrutStyles.filter(heightEm: false)
270+
!= const KatexSpanStyles()) {
271+
throw _KatexHtmlParseError();
272+
}
273+
final pstrutHeight = pstrutStyles.heightEm ?? 0;
274+
275+
rows.add(KatexVlistRowNode(
276+
verticalOffsetEm: topEm + pstrutHeight,
277+
debugHtmlNode: kDebugMode ? innerSpan : null,
278+
node: KatexSpanNode(
279+
styles: styles,
280+
text: null,
281+
nodes: _parseChildSpans(otherSpans))));
282+
} else {
283+
throw _KatexHtmlParseError();
284+
}
285+
}
286+
287+
return KatexVlistNode(
288+
rows: rows,
289+
debugHtmlNode: kDebugMode ? vlistT : null,
290+
);
291+
} else {
292+
throw _KatexHtmlParseError();
293+
}
294+
} else {
295+
throw _KatexHtmlParseError();
296+
}
297+
}
298+
212299
final inlineStyles = _parseSpanInlineStyles(element);
213300
if (inlineStyles != null) {
214301
// We expect `vertical-align` inline style to be only present on a
215302
// `strut` span, for which we emit `KatexStrutNode` separately.
216303
if (inlineStyles.verticalAlignEm != null) throw _KatexHtmlParseError();
304+
305+
// Currently, we expect `top` to only be inside a vlist, and
306+
// we handle that case separately above.
307+
if (inlineStyles.topEm != null) throw _KatexHtmlParseError();
217308
}
218309

219310
// Aggregate the CSS styles that apply, in the same order as the CSS
@@ -224,7 +315,9 @@ class _KatexParser {
224315
// https://github.com/KaTeX/KaTeX/blob/2fe1941b/src/styles/katex.scss
225316
// A copy of class definition (where possible) is accompanied in a comment
226317
// with each case statement to keep track of updates.
227-
final spanClasses = List<String>.unmodifiable(element.className.split(' '));
318+
final spanClasses = element.className != ''
319+
? List<String>.unmodifiable(element.className.split(' '))
320+
: const <String>[];
228321
String? fontFamily;
229322
double? fontSizeEm;
230323
KatexSpanFontWeight? fontWeight;
@@ -492,6 +585,7 @@ class _KatexParser {
492585
if (stylesheet.topLevels case [css_visitor.RuleSet() && final rule]) {
493586
double? heightEm;
494587
double? verticalAlignEm;
588+
double? topEm;
495589
double? marginRightEm;
496590
double? marginLeftEm;
497591

@@ -510,6 +604,10 @@ class _KatexParser {
510604
verticalAlignEm = _getEm(expression);
511605
if (verticalAlignEm != null) continue;
512606

607+
case 'top':
608+
topEm = _getEm(expression);
609+
if (topEm != null) continue;
610+
513611
case 'margin-right':
514612
marginRightEm = _getEm(expression);
515613
if (marginRightEm != null) {
@@ -537,6 +635,7 @@ class _KatexParser {
537635

538636
return KatexSpanStyles(
539637
heightEm: heightEm,
638+
topEm: topEm,
540639
verticalAlignEm: verticalAlignEm,
541640
marginRightEm: marginRightEm,
542641
marginLeftEm: marginLeftEm,
@@ -578,6 +677,8 @@ class KatexSpanStyles {
578677
final double? heightEm;
579678
final double? verticalAlignEm;
580679

680+
final double? topEm;
681+
581682
final double? marginRightEm;
582683
final double? marginLeftEm;
583684

@@ -590,6 +691,7 @@ class KatexSpanStyles {
590691
const KatexSpanStyles({
591692
this.heightEm,
592693
this.verticalAlignEm,
694+
this.topEm,
593695
this.marginRightEm,
594696
this.marginLeftEm,
595697
this.fontFamily,
@@ -604,6 +706,7 @@ class KatexSpanStyles {
604706
'KatexSpanStyles',
605707
heightEm,
606708
verticalAlignEm,
709+
topEm,
607710
marginRightEm,
608711
marginLeftEm,
609712
fontFamily,
@@ -618,6 +721,7 @@ class KatexSpanStyles {
618721
return other is KatexSpanStyles &&
619722
other.heightEm == heightEm &&
620723
other.verticalAlignEm == verticalAlignEm &&
724+
other.topEm == topEm &&
621725
other.marginRightEm == marginRightEm &&
622726
other.marginLeftEm == marginLeftEm &&
623727
other.fontFamily == fontFamily &&
@@ -632,6 +736,7 @@ class KatexSpanStyles {
632736
final args = <String>[];
633737
if (heightEm != null) args.add('heightEm: $heightEm');
634738
if (verticalAlignEm != null) args.add('verticalAlignEm: $verticalAlignEm');
739+
if (topEm != null) args.add('topEm: $topEm');
635740
if (marginRightEm != null) args.add('marginRightEm: $marginRightEm');
636741
if (marginLeftEm != null) args.add('marginLeftEm: $marginLeftEm');
637742
if (fontFamily != null) args.add('fontFamily: $fontFamily');
@@ -653,6 +758,7 @@ class KatexSpanStyles {
653758
return KatexSpanStyles(
654759
heightEm: other.heightEm ?? heightEm,
655760
verticalAlignEm: other.verticalAlignEm ?? verticalAlignEm,
761+
topEm: other.topEm ?? topEm,
656762
marginRightEm: other.marginRightEm ?? marginRightEm,
657763
marginLeftEm: other.marginLeftEm ?? marginLeftEm,
658764
fontFamily: other.fontFamily ?? fontFamily,
@@ -666,6 +772,7 @@ class KatexSpanStyles {
666772
KatexSpanStyles filter({
667773
bool heightEm = true,
668774
bool verticalAlignEm = true,
775+
bool topEm = true,
669776
bool marginRightEm = true,
670777
bool marginLeftEm = true,
671778
bool fontFamily = true,
@@ -677,6 +784,7 @@ class KatexSpanStyles {
677784
return KatexSpanStyles(
678785
heightEm: heightEm ? this.heightEm : null,
679786
verticalAlignEm: verticalAlignEm ? this.verticalAlignEm : null,
787+
topEm: topEm ? this.topEm : null,
680788
marginRightEm: marginRightEm ? this.marginRightEm : null,
681789
marginLeftEm: marginLeftEm ? this.marginLeftEm : null,
682790
fontFamily: fontFamily ? this.fontFamily : null,

lib/widgets/content.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,7 @@ class _KatexNodeList extends StatelessWidget {
897897
child: switch (e) {
898898
KatexSpanNode() => _KatexSpan(e),
899899
KatexStrutNode() => _KatexStrut(e),
900+
KatexVlistNode() => _KatexVlist(e),
900901
}));
901902
}))));
902903
}
@@ -924,6 +925,10 @@ class _KatexSpan extends StatelessWidget {
924925
// So, this should always be null for non `strut` spans.
925926
assert(styles.verticalAlignEm == null);
926927

928+
// Currently, we expect `top` to be only present with the
929+
// vlist inner row span, and parser handles that explicitly.
930+
assert(styles.topEm == null);
931+
927932
final fontFamily = styles.fontFamily;
928933
final fontSize = switch (styles.fontSizeEm) {
929934
double fontSizeEm => fontSizeEm * em,
@@ -1024,6 +1029,23 @@ class _KatexStrut extends StatelessWidget {
10241029
}
10251030
}
10261031

1032+
class _KatexVlist extends StatelessWidget {
1033+
const _KatexVlist(this.node);
1034+
1035+
final KatexVlistNode node;
1036+
1037+
@override
1038+
Widget build(BuildContext context) {
1039+
final em = DefaultTextStyle.of(context).style.fontSize!;
1040+
1041+
return Stack(children: List.unmodifiable(node.rows.map((row) {
1042+
return Transform.translate(
1043+
offset: Offset(0, row.verticalOffsetEm * em),
1044+
child: _KatexSpan(row.node));
1045+
})));
1046+
}
1047+
}
1048+
10271049
class WebsitePreview extends StatelessWidget {
10281050
const WebsitePreview({super.key, required this.node});
10291051

0 commit comments

Comments
 (0)