Skip to content

Commit 8e12ca9

Browse files
content: Support parsing and handling inline styles for KaTeX content
1 parent 4c62181 commit 8e12ca9

File tree

3 files changed

+116
-14
lines changed

3 files changed

+116
-14
lines changed

lib/model/katex.dart

Lines changed: 85 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

@@ -350,11 +352,70 @@ class _KatexParser {
350352
}
351353
if (text == null && spans == null) throw KatexHtmlParseError();
352354

355+
final inlineStyles = _parseSpanInlineStyles(element);
356+
353357
return KatexNode(
354-
styles: styles,
358+
styles: inlineStyles != null
359+
? styles.merge(inlineStyles)
360+
: styles,
355361
text: text,
356362
nodes: spans);
357363
}
364+
365+
KatexSpanStyles? _parseSpanInlineStyles(dom.Element element) {
366+
if (element.attributes case {'style': final styleStr}) {
367+
// `package:csslib` doesn't seem to have a way to parse inline styles:
368+
// https://github.com/dart-lang/tools/issues/1173
369+
// So, workaround that by wrapping it in a universal declaration.
370+
final stylesheet = css_parser.parse('*{$styleStr}');
371+
if (stylesheet.topLevels case [css_visitor.RuleSet() && final rule]) {
372+
double? heightEm;
373+
double? verticalAlignEm;
374+
375+
for (final declaration in rule.declarationGroup.declarations) {
376+
if (declaration case css_visitor.Declaration(
377+
:final property,
378+
expression: css_visitor.Expressions(
379+
expressions: [css_visitor.Expression() && final expression]),
380+
)) {
381+
switch (property) {
382+
case 'height':
383+
heightEm = _getEm(expression);
384+
if (heightEm != null) continue;
385+
386+
case 'vertical-align':
387+
verticalAlignEm = _getEm(expression);
388+
if (verticalAlignEm != null) continue;
389+
}
390+
391+
// TODO handle more CSS properties
392+
assert(debugLog('KaTeX: Unsupported CSS expression:'
393+
' ${expression.toDebugString()}'));
394+
_hasError = true;
395+
} else {
396+
throw KatexHtmlParseError();
397+
}
398+
}
399+
400+
return KatexSpanStyles(
401+
heightEm: heightEm,
402+
verticalAlignEm: verticalAlignEm,
403+
);
404+
} else {
405+
throw KatexHtmlParseError();
406+
}
407+
}
408+
return null;
409+
}
410+
411+
/// Returns the CSS `em` unit value if the given [expression] is actually an
412+
/// `em` unit expression, else returns null.
413+
double? _getEm(css_visitor.Expression expression) {
414+
if (expression is css_visitor.EmTerm && expression.value is num) {
415+
return (expression.value as num).toDouble();
416+
}
417+
return null;
418+
}
358419
}
359420

360421
enum KatexSpanFontWeight {
@@ -374,13 +435,18 @@ enum KatexSpanTextAlign {
374435

375436
@immutable
376437
class KatexSpanStyles {
438+
final double? heightEm;
439+
final double? verticalAlignEm;
440+
377441
final String? fontFamily;
378442
final double? fontSizeEm;
379443
final KatexSpanFontWeight? fontWeight;
380444
final KatexSpanFontStyle? fontStyle;
381445
final KatexSpanTextAlign? textAlign;
382446

383447
const KatexSpanStyles({
448+
this.heightEm,
449+
this.verticalAlignEm,
384450
this.fontFamily,
385451
this.fontSizeEm,
386452
this.fontWeight,
@@ -391,6 +457,8 @@ class KatexSpanStyles {
391457
@override
392458
int get hashCode => Object.hash(
393459
'KatexSpanStyles',
460+
heightEm,
461+
verticalAlignEm,
394462
fontFamily,
395463
fontSizeEm,
396464
fontWeight,
@@ -401,6 +469,8 @@ class KatexSpanStyles {
401469
@override
402470
bool operator ==(Object other) {
403471
return other is KatexSpanStyles &&
472+
other.heightEm == heightEm &&
473+
other.verticalAlignEm == verticalAlignEm &&
404474
other.fontFamily == fontFamily &&
405475
other.fontSizeEm == fontSizeEm &&
406476
other.fontWeight == fontWeight &&
@@ -411,13 +481,27 @@ class KatexSpanStyles {
411481
@override
412482
String toString() {
413483
final args = <String>[];
484+
if (heightEm != null) args.add('heightEm: $heightEm');
485+
if (verticalAlignEm != null) args.add('verticalAlignEm: $verticalAlignEm');
414486
if (fontFamily != null) args.add('fontFamily: $fontFamily');
415487
if (fontSizeEm != null) args.add('fontSizeEm: $fontSizeEm');
416488
if (fontWeight != null) args.add('fontWeight: $fontWeight');
417489
if (fontStyle != null) args.add('fontStyle: $fontStyle');
418490
if (textAlign != null) args.add('textAlign: $textAlign');
419491
return '${objectRuntimeType(this, 'KatexSpanStyles')}(${args.join(', ')})';
420492
}
493+
494+
KatexSpanStyles merge(KatexSpanStyles other) {
495+
return KatexSpanStyles(
496+
heightEm: other.heightEm ?? heightEm,
497+
verticalAlignEm: other.verticalAlignEm ?? verticalAlignEm,
498+
fontFamily: other.fontFamily ?? fontFamily,
499+
fontSizeEm: other.fontSizeEm ?? fontSizeEm,
500+
fontStyle: other.fontStyle ?? fontStyle,
501+
fontWeight: other.fontWeight ?? fontWeight,
502+
textAlign: other.textAlign ?? textAlign,
503+
);
504+
}
421505
}
422506

423507
class KatexHtmlParseError extends Error {

lib/widgets/content.dart

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,9 @@ class _KatexSpan extends StatelessWidget {
895895
Widget build(BuildContext context) {
896896
final em = DefaultTextStyle.of(context).style.fontSize!;
897897

898-
Widget widget = const SizedBox.shrink();
898+
const defaultWidget = SizedBox.shrink();
899+
900+
Widget widget = defaultWidget;
899901
if (node.text != null) {
900902
widget = Text(node.text!);
901903
} else if (node.nodes != null && node.nodes!.isNotEmpty) {
@@ -952,7 +954,21 @@ class _KatexSpan extends StatelessWidget {
952954
textAlign: textAlign,
953955
child: widget);
954956
}
955-
return widget;
957+
958+
if (styles.verticalAlignEm != null && styles.heightEm != null) {
959+
assert(widget == defaultWidget);
960+
widget = Baseline(
961+
baseline: (styles.verticalAlignEm! + styles.heightEm!) * em,
962+
baselineType: TextBaseline.alphabetic,
963+
child: const Text(''));
964+
}
965+
966+
return SizedBox(
967+
height: styles.heightEm != null
968+
? styles.heightEm! * em
969+
: null,
970+
child: widget,
971+
);
956972
}
957973
}
958974

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',
@@ -732,7 +732,7 @@ class ContentExample {
732732
text: null,
733733
nodes: [
734734
KatexNode(
735-
styles: KatexSpanStyles(),
735+
styles: KatexSpanStyles(heightEm: 1.6034),
736736
text: null,
737737
nodes: []),
738738
KatexNode(
@@ -801,7 +801,7 @@ class ContentExample {
801801
text: null,
802802
nodes: [
803803
KatexNode(
804-
styles: KatexSpanStyles(),
804+
styles: KatexSpanStyles(heightEm: 1.6034),
805805
text: null,
806806
nodes: []),
807807
KatexNode(
@@ -846,7 +846,9 @@ class ContentExample {
846846
text: null,
847847
nodes: [
848848
KatexNode(
849-
styles: KatexSpanStyles(),
849+
styles: KatexSpanStyles(
850+
heightEm: 3.0,
851+
verticalAlignEm: -1.25),
850852
text: null,
851853
nodes: []),
852854
KatexNode(

0 commit comments

Comments
 (0)