Skip to content

Commit b17033a

Browse files
content: Support negative right-margin on KaTeX spans
Negative margin spans on web render to the offset being applied to the specific span and all the adjacent spans, so mimic the same behaviour here.
1 parent 4c667d0 commit b17033a

File tree

3 files changed

+86
-6
lines changed

3 files changed

+86
-6
lines changed

lib/model/content.dart

+22
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,28 @@ class KatexVlistRowNode extends ContentNode {
441441
}
442442
}
443443

444+
class KatexNegativeMarginNode extends KatexNode {
445+
const KatexNegativeMarginNode({
446+
required this.marginRightEm,
447+
required this.nodes,
448+
super.debugHtmlNode,
449+
});
450+
451+
final double marginRightEm;
452+
final List<KatexNode> nodes;
453+
454+
@override
455+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
456+
super.debugFillProperties(properties);
457+
properties.add(StringProperty('marginRightEm', '$marginRightEm'));
458+
}
459+
460+
@override
461+
List<DiagnosticsNode> debugDescribeChildren() {
462+
return nodes.map((node) => node.toDiagnosticsNode()).toList();
463+
}
464+
}
465+
444466
class MathBlockNode extends MathNode implements BlockContentNode {
445467
const MathBlockNode({
446468
super.debugHtmlNode,

lib/model/katex.dart

+33-5
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,29 @@ class _KatexParser {
123123
}
124124

125125
List<KatexNode> _parseChildSpans(dom.Element element) {
126-
return List.unmodifiable(element.nodes.map((node) {
127-
if (node case dom.Element(localName: 'span')) {
128-
return _parseSpan(node);
129-
} else {
126+
var resultSpans = <KatexNode>[];
127+
for (final node in element.nodes.reversed) {
128+
if (node is! dom.Element || node.localName != 'span') {
130129
throw KatexHtmlParseError();
131130
}
132-
}));
131+
132+
final span = _parseSpan(node);
133+
resultSpans.add(span);
134+
135+
if (span is KatexSpanNode) {
136+
final marginRightEm = span.styles.marginRightEm;
137+
if (marginRightEm != null && marginRightEm.isNegative) {
138+
final previousSpansReversed =
139+
resultSpans.reversed.toList(growable: false);
140+
resultSpans = [];
141+
resultSpans.add(KatexNegativeMarginNode(
142+
marginRightEm: marginRightEm,
143+
nodes: previousSpansReversed));
144+
}
145+
}
146+
}
147+
148+
return resultSpans.reversed.toList(growable: false);
133149
}
134150

135151
static final _resetSizeClassRegExp = RegExp(r'^reset-size(\d\d?)$');
@@ -477,6 +493,7 @@ class _KatexParser {
477493
final stylesheet = css_parser.parse('*{$styleStr}');
478494
if (stylesheet.topLevels case [css_visitor.RuleSet() && final rule]) {
479495
double? heightEm;
496+
double? marginRightEm;
480497
double? topEm;
481498
double? verticalAlignEm;
482499

@@ -491,6 +508,10 @@ class _KatexParser {
491508
heightEm = _getEm(expression);
492509
if (heightEm != null) continue;
493510

511+
case 'margin-right':
512+
marginRightEm = _getEm(expression);
513+
if (marginRightEm != null) continue;
514+
494515
case 'top':
495516
topEm = _getEm(expression);
496517
if (topEm != null) continue;
@@ -510,6 +531,7 @@ class _KatexParser {
510531

511532
return KatexSpanStyles(
512533
heightEm: heightEm,
534+
marginRightEm: marginRightEm,
513535
topEm: topEm,
514536
verticalAlignEm: verticalAlignEm,
515537
);
@@ -546,6 +568,7 @@ enum KatexSpanTextAlign {
546568
@immutable
547569
class KatexSpanStyles {
548570
final double? heightEm;
571+
final double? marginRightEm;
549572
final double? topEm;
550573
final double? verticalAlignEm;
551574

@@ -557,6 +580,7 @@ class KatexSpanStyles {
557580

558581
const KatexSpanStyles({
559582
this.heightEm,
583+
this.marginRightEm,
560584
this.topEm,
561585
this.verticalAlignEm,
562586
this.fontFamily,
@@ -570,6 +594,7 @@ class KatexSpanStyles {
570594
int get hashCode => Object.hash(
571595
'KatexSpanStyles',
572596
heightEm,
597+
marginRightEm,
573598
topEm,
574599
verticalAlignEm,
575600
fontFamily,
@@ -583,6 +608,7 @@ class KatexSpanStyles {
583608
bool operator ==(Object other) {
584609
return other is KatexSpanStyles &&
585610
other.heightEm == heightEm &&
611+
other.marginRightEm == marginRightEm &&
586612
other.topEm == topEm &&
587613
other.verticalAlignEm == verticalAlignEm &&
588614
other.fontFamily == fontFamily &&
@@ -596,6 +622,7 @@ class KatexSpanStyles {
596622
String toString() {
597623
final args = <String>[];
598624
if (heightEm != null) args.add('heightEm: $heightEm');
625+
if (marginRightEm != null) args.add('marginRightEm: $marginRightEm');
599626
if (topEm != null) args.add('topEm: $topEm');
600627
if (verticalAlignEm != null) args.add('verticalAlignEm: $verticalAlignEm');
601628
if (fontFamily != null) args.add('fontFamily: $fontFamily');
@@ -609,6 +636,7 @@ class KatexSpanStyles {
609636
KatexSpanStyles merge(KatexSpanStyles other) {
610637
return KatexSpanStyles(
611638
heightEm: other.heightEm ?? heightEm,
639+
marginRightEm: other.marginRightEm ?? marginRightEm,
612640
topEm: other.topEm ?? topEm,
613641
verticalAlignEm: other.verticalAlignEm ?? verticalAlignEm,
614642
fontFamily: other.fontFamily ?? fontFamily,

lib/widgets/content.dart

+31-1
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,7 @@ class _KatexNodeList extends StatelessWidget {
880880
child: switch (e) {
881881
KatexSpanNode() => _KatexSpan(e),
882882
KatexVlistNode() => _KatexVlist(e),
883+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
883884
});
884885
}))));
885886
}
@@ -959,7 +960,10 @@ class _KatexSpan extends StatelessWidget {
959960
child: widget);
960961
}
961962

962-
return SizedBox(
963+
return Container(
964+
margin: styles.marginRightEm != null && !styles.marginRightEm!.isNegative
965+
? EdgeInsets.only(right: styles.marginRightEm! * em)
966+
: null,
963967
height: styles.heightEm != null
964968
? styles.heightEm! * em
965969
: null,
@@ -988,12 +992,38 @@ class _KatexVlist extends StatelessWidget {
988992
child: switch (e) {
989993
KatexSpanNode() => _KatexSpan(e),
990994
KatexVlistNode() => _KatexVlist(e),
995+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
991996
});
992997
})))));
993998
})));
994999
}
9951000
}
9961001

1002+
class _KatexNegativeMargin extends StatelessWidget {
1003+
const _KatexNegativeMargin(this.node);
1004+
1005+
final KatexNegativeMarginNode node;
1006+
1007+
@override
1008+
Widget build(BuildContext context) {
1009+
final em = DefaultTextStyle.of(context).style.fontSize!;
1010+
1011+
return Transform.translate(
1012+
offset: Offset(node.marginRightEm * em, 0),
1013+
child: Text.rich(TextSpan(
1014+
children: List.unmodifiable(node.nodes.map((e) {
1015+
return WidgetSpan(
1016+
alignment: PlaceholderAlignment.baseline,
1017+
baseline: TextBaseline.alphabetic,
1018+
child: switch (e) {
1019+
KatexSpanNode() => _KatexSpan(e),
1020+
KatexVlistNode() => _KatexVlist(e),
1021+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
1022+
});
1023+
})))));
1024+
}
1025+
}
1026+
9971027
class WebsitePreview extends StatelessWidget {
9981028
const WebsitePreview({super.key, required this.node});
9991029

0 commit comments

Comments
 (0)