Skip to content

Commit 5d39bef

Browse files
content: Support parsing and handling inline styles for KaTeX content
1 parent b5493b6 commit 5d39bef

File tree

3 files changed

+107
-13
lines changed

3 files changed

+107
-13
lines changed

lib/model/katex.dart

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'package:csslib/parser.dart' as css_parser;
2+
import 'package:csslib/visitor.dart' as css_visitor;
13
import 'package:flutter/foundation.dart';
24
import 'package:html/dom.dart' as dom;
35

@@ -391,11 +393,67 @@ class KatexParser {
391393
}
392394
if (text == null && spans == null) throw KatexHtmlParseError();
393395

396+
final inlineStyles = _parseSpanInlineStyles(element);
397+
394398
return KatexNode(
395-
styles: styles,
399+
styles: inlineStyles != null
400+
? styles.merge(inlineStyles)
401+
: styles,
396402
text: text,
397403
nodes: spans);
398404
}
405+
406+
KatexSpanStyles? _parseSpanInlineStyles(dom.Element element) {
407+
if (element.attributes case {'style': final styleStr}) {
408+
// `package:csslib` doesn't seem to have a way to parse inline styles:
409+
// https://github.com/dart-lang/tools/issues/1173
410+
// So, workaround that by wrapping it in a universal declaration.
411+
final stylesheet = css_parser.parse('*{$styleStr}');
412+
if (stylesheet.topLevels case [css_visitor.RuleSet() && final rule]) {
413+
double? heightEm;
414+
double? verticalAlignEm;
415+
416+
for (final declaration in rule.declarationGroup.declarations) {
417+
if (declaration case css_visitor.Declaration(
418+
:final property,
419+
expression: css_visitor.Expressions(
420+
expressions: [css_visitor.Expression() && final expression]),
421+
)) {
422+
switch (property) {
423+
case 'height':
424+
heightEm = _getEm(expression);
425+
if (heightEm != null) continue;
426+
427+
case 'vertical-align':
428+
verticalAlignEm = _getEm(expression);
429+
if (verticalAlignEm != null) continue;
430+
}
431+
432+
// TODO handle more CSS properties
433+
_logError('KaTeX: Unsupported CSS property: $property of '
434+
'type ${expression.runtimeType}');
435+
} else {
436+
throw KatexHtmlParseError();
437+
}
438+
}
439+
440+
return KatexSpanStyles(
441+
heightEm: heightEm,
442+
verticalAlignEm: verticalAlignEm,
443+
);
444+
} else {
445+
throw KatexHtmlParseError();
446+
}
447+
}
448+
return null;
449+
}
450+
451+
double? _getEm(css_visitor.Expression expression) {
452+
if (expression is css_visitor.EmTerm && expression.value is num) {
453+
return (expression.value as num).toDouble();
454+
}
455+
return null;
456+
}
399457
}
400458

401459
enum KatexSpanFontWeight {
@@ -414,13 +472,18 @@ enum KatexSpanTextAlign {
414472
}
415473

416474
class KatexSpanStyles {
475+
double? heightEm;
476+
double? verticalAlignEm;
477+
417478
String? fontFamily;
418479
double? fontSizeEm;
419480
KatexSpanFontStyle? fontStyle;
420481
KatexSpanFontWeight? fontWeight;
421482
KatexSpanTextAlign? textAlign;
422483

423484
KatexSpanStyles({
485+
this.heightEm,
486+
this.verticalAlignEm,
424487
this.fontFamily,
425488
this.fontSizeEm,
426489
this.fontStyle,
@@ -431,6 +494,8 @@ class KatexSpanStyles {
431494
@override
432495
int get hashCode => Object.hash(
433496
'KatexSpanStyles',
497+
heightEm,
498+
verticalAlignEm,
434499
fontFamily,
435500
fontSizeEm,
436501
fontStyle,
@@ -441,6 +506,8 @@ class KatexSpanStyles {
441506
@override
442507
bool operator ==(Object other) {
443508
return other is KatexSpanStyles &&
509+
other.heightEm == heightEm &&
510+
other.verticalAlignEm == verticalAlignEm &&
444511
other.fontFamily == fontFamily &&
445512
other.fontSizeEm == fontSizeEm &&
446513
other.fontStyle == fontStyle &&
@@ -455,13 +522,27 @@ class KatexSpanStyles {
455522
if (this == _zero) return '${objectRuntimeType(this, 'KatexSpanStyles')}()';
456523

457524
final args = <String>[];
525+
if (heightEm != null) args.add('heightEm: $heightEm');
526+
if (verticalAlignEm != null) args.add('verticalAlignEm: $verticalAlignEm');
458527
if (fontFamily != null) args.add('fontFamily: $fontFamily');
459528
if (fontSizeEm != null) args.add('fontSizeEm: $fontSizeEm');
460529
if (fontStyle != null) args.add('fontStyle: $fontStyle');
461530
if (fontWeight != null) args.add('fontWeight: $fontWeight');
462531
if (textAlign != null) args.add('textAlign: $textAlign');
463532
return '${objectRuntimeType(this, 'KatexSpanStyles')}(${args.join(', ')})';
464533
}
534+
535+
KatexSpanStyles merge(KatexSpanStyles other) {
536+
return KatexSpanStyles(
537+
heightEm: other.heightEm ?? heightEm,
538+
verticalAlignEm: other.verticalAlignEm ?? verticalAlignEm,
539+
fontFamily: other.fontFamily ?? fontFamily,
540+
fontSizeEm: other.fontSizeEm ?? fontSizeEm,
541+
fontStyle: other.fontStyle ?? fontStyle,
542+
fontWeight: other.fontWeight ?? fontWeight,
543+
textAlign: other.textAlign ?? textAlign,
544+
);
545+
}
465546
}
466547

467548
class KatexSpanStylesProperty extends DiagnosticsProperty<KatexSpanStyles> {

lib/widgets/content.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,18 @@ class _KatexSpan extends StatelessWidget {
916916
textAlign: textAlign,
917917
child: widget);
918918
}
919-
return widget;
919+
920+
if (styles.verticalAlignEm != null) {
921+
widget = Baseline(
922+
baseline: styles.verticalAlignEm! * em,
923+
baselineType: TextBaseline.alphabetic,
924+
child: widget);
925+
}
926+
927+
return SizedBox(
928+
height: styles.heightEm != null ? styles.heightEm! * em : null,
929+
child: widget,
930+
);
920931
}
921932
}
922933

test/model/content_test.dart

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ class ContentExample {
519519
'<span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">λ</span></span></span></span></p>',
520520
MathInlineNode(texSource: r'\lambda', nodes: [
521521
KatexNode(styles: KatexSpanStyles(), text: null, nodes: [
522-
KatexNode(styles: KatexSpanStyles(), text: null, nodes: []),
522+
KatexNode(styles: KatexSpanStyles(heightEm: 0.6944), text: null, nodes: []),
523523
KatexNode(
524524
styles: KatexSpanStyles(
525525
fontFamily: 'KaTeX_Math',
@@ -539,7 +539,7 @@ class ContentExample {
539539
'<span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">λ</span></span></span></span></span></p>',
540540
[MathBlockNode(texSource: r'\lambda', nodes: [
541541
KatexNode(styles: KatexSpanStyles(), text: null, nodes: [
542-
KatexNode(styles: KatexSpanStyles(), text: null, nodes: []),
542+
KatexNode(styles: KatexSpanStyles(heightEm: 0.6944), text: null, nodes: []),
543543
KatexNode(
544544
styles: KatexSpanStyles(
545545
fontFamily: 'KaTeX_Math',
@@ -564,7 +564,7 @@ class ContentExample {
564564
'<span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">b</span></span></span></span></span></p>', [
565565
MathBlockNode(texSource: 'a', nodes: [
566566
KatexNode(styles: KatexSpanStyles(), text: null, nodes: [
567-
KatexNode(styles: KatexSpanStyles(), text: null, nodes: []),
567+
KatexNode(styles: KatexSpanStyles(heightEm: 0.4306), text: null, nodes: []),
568568
KatexNode(
569569
styles: KatexSpanStyles(
570570
fontFamily: 'KaTeX_Math',
@@ -575,7 +575,7 @@ class ContentExample {
575575
]),
576576
MathBlockNode(texSource: 'b', nodes: [
577577
KatexNode(styles: KatexSpanStyles(), text: null, nodes: [
578-
KatexNode(styles: KatexSpanStyles(), text: null, nodes: []),
578+
KatexNode(styles: KatexSpanStyles(heightEm: 0.6944), text: null, nodes: []),
579579
KatexNode(
580580
styles: KatexSpanStyles(
581581
fontFamily: 'KaTeX_Math',
@@ -603,7 +603,7 @@ class ContentExample {
603603
[QuotationNode([
604604
MathBlockNode(texSource: r'\lambda', nodes: [
605605
KatexNode(styles: KatexSpanStyles(), text: null, nodes: [
606-
KatexNode(styles: KatexSpanStyles(), text: null, nodes: []),
606+
KatexNode(styles: KatexSpanStyles(heightEm: 0.6944), text: null, nodes: []),
607607
KatexNode(
608608
styles: KatexSpanStyles(
609609
fontFamily: 'KaTeX_Math',
@@ -632,7 +632,7 @@ class ContentExample {
632632
[QuotationNode([
633633
MathBlockNode(texSource: 'a', nodes: [
634634
KatexNode(styles: KatexSpanStyles(), text: null, nodes: [
635-
KatexNode(styles: KatexSpanStyles(), text: null, nodes: []),
635+
KatexNode(styles: KatexSpanStyles(heightEm: 0.4306), text: null, nodes: []),
636636
KatexNode(
637637
styles: KatexSpanStyles(
638638
fontFamily: 'KaTeX_Math',
@@ -643,7 +643,7 @@ class ContentExample {
643643
]),
644644
MathBlockNode(texSource: 'b', nodes: [
645645
KatexNode(styles: KatexSpanStyles(), text: null, nodes: [
646-
KatexNode(styles: KatexSpanStyles(), text: null, nodes: []),
646+
KatexNode(styles: KatexSpanStyles(heightEm: 0.6944), text: null, nodes: []),
647647
KatexNode(
648648
styles: KatexSpanStyles(
649649
fontFamily: 'KaTeX_Math',
@@ -681,7 +681,7 @@ class ContentExample {
681681
]),
682682
MathBlockNode(texSource: 'a', nodes: [
683683
KatexNode(styles: KatexSpanStyles(), text: null, nodes: [
684-
KatexNode(styles: KatexSpanStyles(),text: null, nodes: []),
684+
KatexNode(styles: KatexSpanStyles(heightEm: 0.4306),text: null, nodes: []),
685685
KatexNode(
686686
styles: KatexSpanStyles(
687687
fontFamily: 'KaTeX_Math',
@@ -741,7 +741,7 @@ class ContentExample {
741741
text: null,
742742
nodes: [
743743
KatexNode(
744-
styles: KatexSpanStyles(),
744+
styles: KatexSpanStyles(heightEm: 1.7278),
745745
text: null,
746746
nodes: []),
747747
KatexNode(
@@ -772,7 +772,7 @@ class ContentExample {
772772
text: null,
773773
nodes: [
774774
KatexNode(
775-
styles: KatexSpanStyles(),
775+
styles: KatexSpanStyles(heightEm: 1.7278),
776776
text: null,
777777
nodes: []),
778778
KatexNode(
@@ -877,7 +877,9 @@ class ContentExample {
877877
text: null,
878878
nodes: [
879879
KatexNode(
880-
styles: KatexSpanStyles(),
880+
styles: KatexSpanStyles(
881+
heightEm: 3.0,
882+
verticalAlignEm: -1.25),
881883
text: null,
882884
nodes: []),
883885
KatexNode(

0 commit comments

Comments
 (0)