@@ -185,9 +185,122 @@ class _KatexParser {
185
185
KatexNode _parseSpan (dom.Element element) {
186
186
// TODO maybe check if the sequence of ancestors matter for spans.
187
187
188
+ if (element.className.startsWith ('strut' )) {
189
+ if (element.className == 'strut' && element.nodes.isEmpty) {
190
+ final styles = _parseSpanInlineStyles (element);
191
+ if (styles == null ) throw _KatexHtmlParseError ();
192
+
193
+ final heightEm = styles.heightEm;
194
+ if (heightEm == null ) throw _KatexHtmlParseError ();
195
+ final verticalAlignEm = styles.verticalAlignEm;
196
+
197
+ // Ensure only `height` and `vertical-align` inline styles are present.
198
+ if (styles.filter (heightEm: false , verticalAlignEm: false ) !=
199
+ KatexSpanStyles ()) {
200
+ throw _KatexHtmlParseError ();
201
+ }
202
+
203
+ return KatexStrutNode (
204
+ heightEm: heightEm,
205
+ verticalAlignEm: verticalAlignEm);
206
+ } else {
207
+ throw _KatexHtmlParseError ();
208
+ }
209
+ }
210
+
211
+ if (element.className.startsWith ('vlist' )) {
212
+ if (element case dom.Element (
213
+ localName: 'span' ,
214
+ className: 'vlist-t' || 'vlist-t vlist-t2' ,
215
+ nodes: [...],
216
+ ) && final vlistT) {
217
+ if (vlistT.attributes.containsKey ('style' )) throw _KatexHtmlParseError ();
218
+
219
+ final hasTwoVlistR = vlistT.className == 'vlist-t vlist-t2' ;
220
+ if (! hasTwoVlistR && vlistT.nodes.length != 1 ) throw _KatexHtmlParseError ();
221
+
222
+ if (hasTwoVlistR) {
223
+ if (vlistT.nodes case [
224
+ _,
225
+ dom.Element (localName: 'span' , className: 'vlist-r' , nodes: [
226
+ dom.Element (localName: 'span' , className: 'vlist' , nodes: [
227
+ dom.Element (localName: 'span' , className: '' , nodes: []),
228
+ ]),
229
+ ]),
230
+ ]) {
231
+ // Do nothing.
232
+ } else {
233
+ throw _KatexHtmlParseError ();
234
+ }
235
+ }
236
+
237
+ if (vlistT.nodes.first
238
+ case dom.Element (localName: 'span' , className: 'vlist-r' ) &&
239
+ final vlistR) {
240
+ if (vlistR.attributes.containsKey ('style' )) throw _KatexHtmlParseError ();
241
+
242
+ if (vlistR.nodes.first
243
+ case dom.Element (localName: 'span' , className: 'vlist' ) &&
244
+ final vlist) {
245
+ final rows = < KatexVlistRowNode > [];
246
+
247
+ for (final innerSpan in vlist.nodes) {
248
+ if (innerSpan case dom.Element (
249
+ localName: 'span' ,
250
+ nodes: [
251
+ dom.Element (localName: 'span' , className: 'pstrut' ) &&
252
+ final pstrutSpan,
253
+ ...final otherSpans,
254
+ ],
255
+ )) {
256
+ if (innerSpan.className != '' ) {
257
+ throw _KatexHtmlParseError ('unexpected CSS class for '
258
+ 'vlist inner span: ${innerSpan .className }' );
259
+ }
260
+
261
+ var styles = _parseSpanInlineStyles (innerSpan)! ;
262
+ final topEm = styles.topEm ?? 0 ;
263
+
264
+ styles = styles.filter (topEm: false );
265
+
266
+ final pstrutStyles = _parseSpanInlineStyles (pstrutSpan)! ;
267
+ final pstrutHeight = pstrutStyles.heightEm ?? 0 ;
268
+
269
+ rows.add (KatexVlistRowNode (
270
+ verticalOffsetEm: topEm + pstrutHeight,
271
+ debugHtmlNode: kDebugMode ? innerSpan : null ,
272
+ node: KatexSpanNode (
273
+ styles: styles,
274
+ text: null ,
275
+ nodes: _parseChildSpans (otherSpans))));
276
+ } else {
277
+ throw _KatexHtmlParseError ();
278
+ }
279
+ }
280
+
281
+ return KatexVlistNode (
282
+ rows: rows,
283
+ debugHtmlNode: kDebugMode ? vlistT : null ,
284
+ );
285
+ } else {
286
+ throw _KatexHtmlParseError ();
287
+ }
288
+ } else {
289
+ throw _KatexHtmlParseError ();
290
+ }
291
+ } else {
292
+ throw _KatexHtmlParseError ();
293
+ }
294
+ }
295
+
188
296
final debugHtmlNode = kDebugMode ? element : null ;
189
297
190
298
final inlineStyles = _parseSpanInlineStyles (element);
299
+ if (inlineStyles != null ) {
300
+ // We expect `vertical-align` inline style to be only present on a
301
+ // `strut` span, for which we emit `KatexStrutNode` separately.
302
+ if (inlineStyles.verticalAlignEm != null ) throw _KatexHtmlParseError ();
303
+ }
191
304
192
305
// Aggregate the CSS styles that apply, in the same order as the CSS
193
306
// classes specified for this span, mimicking the behaviour on web.
@@ -197,7 +310,9 @@ class _KatexParser {
197
310
// https://github.com/KaTeX/KaTeX/blob/2fe1941b/src/styles/katex.scss
198
311
// A copy of class definition (where possible) is accompanied in a comment
199
312
// with each case statement to keep track of updates.
200
- final spanClasses = List <String >.unmodifiable (element.className.split (' ' ));
313
+ final spanClasses = element.className != ''
314
+ ? List <String >.unmodifiable (element.className.split (' ' ))
315
+ : const < String > [];
201
316
String ? fontFamily;
202
317
double ? fontSizeEm;
203
318
KatexSpanFontWeight ? fontWeight;
@@ -214,8 +329,9 @@ class _KatexParser {
214
329
215
330
case 'strut' :
216
331
// .strut { ... }
217
- // Do nothing, it has properties that don't need special handling.
218
- break ;
332
+ // We expect the 'strut' class to be the only class in a span,
333
+ // in which case we handle it separately and emit `KatexStrutNode`.
334
+ throw _KatexHtmlParseError ();
219
335
220
336
case 'textbf' :
221
337
// .textbf { font-weight: bold; }
@@ -463,6 +579,10 @@ class _KatexParser {
463
579
final stylesheet = css_parser.parse ('*{$styleStr }' );
464
580
if (stylesheet.topLevels case [css_visitor.RuleSet () && final rule]) {
465
581
double ? heightEm;
582
+ double ? verticalAlignEm;
583
+ double ? topEm;
584
+ double ? marginRightEm;
585
+ double ? marginLeftEm;
466
586
467
587
for (final declaration in rule.declarationGroup.declarations) {
468
588
if (declaration case css_visitor.Declaration (
@@ -474,6 +594,28 @@ class _KatexParser {
474
594
case 'height' :
475
595
heightEm = _getEm (expression);
476
596
if (heightEm != null ) continue ;
597
+
598
+ case 'vertical-align' :
599
+ verticalAlignEm = _getEm (expression);
600
+ if (verticalAlignEm != null ) continue ;
601
+
602
+ case 'top' :
603
+ topEm = _getEm (expression);
604
+ if (topEm != null ) continue ;
605
+
606
+ case 'margin-right' :
607
+ marginRightEm = _getEm (expression);
608
+ if (marginRightEm != null ) {
609
+ if (marginRightEm < 0 ) throw _KatexHtmlParseError ();
610
+ continue ;
611
+ }
612
+
613
+ case 'margin-left' :
614
+ marginLeftEm = _getEm (expression);
615
+ if (marginLeftEm != null ) {
616
+ if (marginLeftEm < 0 ) throw _KatexHtmlParseError ();
617
+ continue ;
618
+ }
477
619
}
478
620
479
621
// TODO handle more CSS properties
@@ -488,6 +630,10 @@ class _KatexParser {
488
630
489
631
return KatexSpanStyles (
490
632
heightEm: heightEm,
633
+ topEm: topEm,
634
+ verticalAlignEm: verticalAlignEm,
635
+ marginRightEm: marginRightEm,
636
+ marginLeftEm: marginLeftEm,
491
637
);
492
638
} else {
493
639
throw _KatexHtmlParseError ();
@@ -524,6 +670,12 @@ enum KatexSpanTextAlign {
524
670
@immutable
525
671
class KatexSpanStyles {
526
672
final double ? heightEm;
673
+ final double ? verticalAlignEm;
674
+
675
+ final double ? topEm;
676
+
677
+ final double ? marginRightEm;
678
+ final double ? marginLeftEm;
527
679
528
680
final String ? fontFamily;
529
681
final double ? fontSizeEm;
@@ -533,6 +685,10 @@ class KatexSpanStyles {
533
685
534
686
const KatexSpanStyles ({
535
687
this .heightEm,
688
+ this .verticalAlignEm,
689
+ this .topEm,
690
+ this .marginRightEm,
691
+ this .marginLeftEm,
536
692
this .fontFamily,
537
693
this .fontSizeEm,
538
694
this .fontWeight,
@@ -544,6 +700,10 @@ class KatexSpanStyles {
544
700
int get hashCode => Object .hash (
545
701
'KatexSpanStyles' ,
546
702
heightEm,
703
+ verticalAlignEm,
704
+ topEm,
705
+ marginRightEm,
706
+ marginLeftEm,
547
707
fontFamily,
548
708
fontSizeEm,
549
709
fontWeight,
@@ -555,6 +715,10 @@ class KatexSpanStyles {
555
715
bool operator == (Object other) {
556
716
return other is KatexSpanStyles &&
557
717
other.heightEm == heightEm &&
718
+ other.verticalAlignEm == verticalAlignEm &&
719
+ other.topEm == topEm &&
720
+ other.marginRightEm == marginRightEm &&
721
+ other.marginLeftEm == marginLeftEm &&
558
722
other.fontFamily == fontFamily &&
559
723
other.fontSizeEm == fontSizeEm &&
560
724
other.fontWeight == fontWeight &&
@@ -566,6 +730,10 @@ class KatexSpanStyles {
566
730
String toString () {
567
731
final args = < String > [];
568
732
if (heightEm != null ) args.add ('heightEm: $heightEm ' );
733
+ if (verticalAlignEm != null ) args.add ('verticalAlignEm: $verticalAlignEm ' );
734
+ if (topEm != null ) args.add ('topEm: $topEm ' );
735
+ if (marginRightEm != null ) args.add ('marginRightEm: $marginRightEm ' );
736
+ if (marginLeftEm != null ) args.add ('marginLeftEm: $marginLeftEm ' );
569
737
if (fontFamily != null ) args.add ('fontFamily: $fontFamily ' );
570
738
if (fontSizeEm != null ) args.add ('fontSizeEm: $fontSizeEm ' );
571
739
if (fontWeight != null ) args.add ('fontWeight: $fontWeight ' );
@@ -584,13 +752,43 @@ class KatexSpanStyles {
584
752
KatexSpanStyles merge (KatexSpanStyles other) {
585
753
return KatexSpanStyles (
586
754
heightEm: other.heightEm ?? heightEm,
755
+ verticalAlignEm: other.verticalAlignEm ?? verticalAlignEm,
756
+ topEm: other.topEm ?? topEm,
757
+ marginRightEm: other.marginRightEm ?? marginRightEm,
758
+ marginLeftEm: other.marginLeftEm ?? marginLeftEm,
587
759
fontFamily: other.fontFamily ?? fontFamily,
588
760
fontSizeEm: other.fontSizeEm ?? fontSizeEm,
589
761
fontStyle: other.fontStyle ?? fontStyle,
590
762
fontWeight: other.fontWeight ?? fontWeight,
591
763
textAlign: other.textAlign ?? textAlign,
592
764
);
593
765
}
766
+
767
+ KatexSpanStyles filter ({
768
+ bool heightEm = true ,
769
+ bool verticalAlignEm = true ,
770
+ bool topEm = true ,
771
+ bool marginRightEm = true ,
772
+ bool marginLeftEm = true ,
773
+ bool fontFamily = true ,
774
+ bool fontSizeEm = true ,
775
+ bool fontWeight = true ,
776
+ bool fontStyle = true ,
777
+ bool textAlign = true ,
778
+ }) {
779
+ return KatexSpanStyles (
780
+ heightEm: heightEm ? this .heightEm : null ,
781
+ verticalAlignEm: verticalAlignEm ? this .verticalAlignEm : null ,
782
+ topEm: topEm ? this .topEm : null ,
783
+ marginRightEm: marginRightEm ? this .marginRightEm : null ,
784
+ marginLeftEm: marginLeftEm ? this .marginLeftEm : null ,
785
+ fontFamily: fontFamily ? this .fontFamily : null ,
786
+ fontSizeEm: fontSizeEm ? this .fontSizeEm : null ,
787
+ fontWeight: fontWeight ? this .fontWeight : null ,
788
+ fontStyle: fontStyle ? this .fontStyle : null ,
789
+ textAlign: textAlign ? this .textAlign : null ,
790
+ );
791
+ }
594
792
}
595
793
596
794
class _KatexHtmlParseError extends Error {
0 commit comments