Skip to content

Commit c0dd0a1

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 e268041 commit c0dd0a1

File tree

3 files changed

+87
-6
lines changed

3 files changed

+87
-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 ImageNodeList extends BlockContentNode {
445467
const ImageNodeList(this.images, {super.debugHtmlNode});
446468

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?)$');
@@ -524,6 +540,7 @@ class _KatexParser {
524540
final stylesheet = css_parser.parse('*{$styleStr}');
525541
if (stylesheet.topLevels case [css_visitor.RuleSet() && final rule]) {
526542
double? heightEm;
543+
double? marginRightEm;
527544
double? topEm;
528545
double? verticalAlignEm;
529546

@@ -538,6 +555,10 @@ class _KatexParser {
538555
heightEm = _getEm(expression);
539556
if (heightEm != null) continue;
540557

558+
case 'margin-right':
559+
marginRightEm = _getEm(expression);
560+
if (marginRightEm != null) continue;
561+
541562
case 'top':
542563
topEm = _getEm(expression);
543564
if (topEm != null) continue;
@@ -557,6 +578,7 @@ class _KatexParser {
557578

558579
return KatexSpanStyles(
559580
heightEm: heightEm,
581+
marginRightEm: marginRightEm,
560582
topEm: topEm,
561583
verticalAlignEm: verticalAlignEm,
562584
);
@@ -592,6 +614,7 @@ enum KatexSpanTextAlign {
592614

593615
class KatexSpanStyles {
594616
double? heightEm;
617+
double? marginRightEm;
595618
double? topEm;
596619
double? verticalAlignEm;
597620

@@ -603,6 +626,7 @@ class KatexSpanStyles {
603626

604627
KatexSpanStyles({
605628
this.heightEm,
629+
this.marginRightEm,
606630
this.topEm,
607631
this.verticalAlignEm,
608632
this.fontFamily,
@@ -616,6 +640,7 @@ class KatexSpanStyles {
616640
int get hashCode => Object.hash(
617641
'KatexSpanStyles',
618642
heightEm,
643+
marginRightEm,
619644
topEm,
620645
verticalAlignEm,
621646
fontFamily,
@@ -629,6 +654,7 @@ class KatexSpanStyles {
629654
bool operator ==(Object other) {
630655
return other is KatexSpanStyles &&
631656
other.heightEm == heightEm &&
657+
other.marginRightEm == marginRightEm &&
632658
other.topEm == topEm &&
633659
other.verticalAlignEm == verticalAlignEm &&
634660
other.fontFamily == fontFamily &&
@@ -642,6 +668,7 @@ class KatexSpanStyles {
642668
String toString() {
643669
final args = <String>[];
644670
if (heightEm != null) args.add('heightEm: $heightEm');
671+
if (marginRightEm != null) args.add('marginRightEm: $marginRightEm');
645672
if (topEm != null) args.add('topEm: $topEm');
646673
if (verticalAlignEm != null) args.add('verticalAlignEm: $verticalAlignEm');
647674
if (fontFamily != null) args.add('fontFamily: $fontFamily');
@@ -655,6 +682,7 @@ class KatexSpanStyles {
655682
KatexSpanStyles merge(KatexSpanStyles other) {
656683
return KatexSpanStyles(
657684
heightEm: other.heightEm ?? heightEm,
685+
marginRightEm: other.marginRightEm ?? marginRightEm,
658686
topEm: other.topEm ?? topEm,
659687
verticalAlignEm: other.verticalAlignEm ?? verticalAlignEm,
660688
fontFamily: other.fontFamily ?? fontFamily,

lib/widgets/content.dart

+32-1
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,7 @@ class _Katex extends StatelessWidget {
856856
child: switch (e) {
857857
KatexSpanNode() => _KatexSpan(e),
858858
KatexVlistNode() => _KatexVlist(e),
859+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
859860
});
860861
}))));
861862

@@ -895,6 +896,7 @@ class _KatexSpan extends StatelessWidget {
895896
child: switch (e) {
896897
KatexSpanNode() => _KatexSpan(e),
897898
KatexVlistNode() => _KatexVlist(e),
899+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
898900
});
899901
}))));
900902
}
@@ -957,7 +959,10 @@ class _KatexSpan extends StatelessWidget {
957959
child: widget);
958960
}
959961

960-
return SizedBox(
962+
return Container(
963+
margin: styles.marginRightEm != null && !styles.marginRightEm!.isNegative
964+
? EdgeInsets.only(right: styles.marginRightEm! * em)
965+
: null,
961966
height: styles.heightEm != null
962967
? styles.heightEm! * em
963968
: null,
@@ -986,12 +991,38 @@ class _KatexVlist extends StatelessWidget {
986991
child: switch (e) {
987992
KatexSpanNode() => _KatexSpan(e),
988993
KatexVlistNode() => _KatexVlist(e),
994+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
989995
});
990996
})))));
991997
})));
992998
}
993999
}
9941000

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

0 commit comments

Comments
 (0)