Skip to content

Commit 6cfcc0d

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 e509efe commit 6cfcc0d

File tree

6 files changed

+445
-24
lines changed

6 files changed

+445
-24
lines changed

lib/model/content.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,28 @@ class KatexVlistRowNode extends ContentNode {
455455
}
456456
}
457457

458+
class KatexNegativeMarginNode extends KatexNode {
459+
const KatexNegativeMarginNode({
460+
required this.leftOffsetEm,
461+
required this.nodes,
462+
super.debugHtmlNode,
463+
}) : assert(leftOffsetEm < 0);
464+
465+
final double leftOffsetEm;
466+
final List<KatexNode> nodes;
467+
468+
@override
469+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
470+
super.debugFillProperties(properties);
471+
properties.add(DoubleProperty('leftOffsetEm', leftOffsetEm));
472+
}
473+
474+
@override
475+
List<DiagnosticsNode> debugDescribeChildren() {
476+
return nodes.map((node) => node.toDiagnosticsNode()).toList();
477+
}
478+
}
479+
458480
class MathBlockNode extends MathNode implements BlockContentNode {
459481
const MathBlockNode({
460482
super.debugHtmlNode,

lib/model/katex.dart

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:collection/collection.dart';
12
import 'package:csslib/parser.dart' as css_parser;
23
import 'package:csslib/visitor.dart' as css_visitor;
34
import 'package:flutter/foundation.dart';
@@ -118,13 +119,39 @@ class _KatexParser {
118119
}
119120

120121
List<KatexNode> _parseChildSpans(List<dom.Node> nodes) {
121-
return List.unmodifiable(nodes.map((node) {
122-
if (node case dom.Element(localName: 'span')) {
123-
return _parseSpan(node);
124-
} else {
122+
var resultSpans = QueueList<KatexNode>();
123+
for (final node in nodes.reversed) {
124+
if (node is! dom.Element || node.localName != 'span') {
125125
throw KatexHtmlParseError();
126126
}
127-
}));
127+
128+
final span = _parseSpan(node);
129+
130+
if (span is KatexSpanNode) {
131+
final marginRightEm = span.styles.marginRightEm;
132+
if (marginRightEm != null && marginRightEm.isNegative) {
133+
final previousSpans = resultSpans;
134+
resultSpans = QueueList<KatexNode>();
135+
resultSpans.addFirst(KatexNegativeMarginNode(
136+
leftOffsetEm: marginRightEm,
137+
nodes: previousSpans));
138+
}
139+
}
140+
141+
resultSpans.addFirst(span);
142+
143+
if (span is KatexSpanNode) {
144+
final marginLeftEm = span.styles.marginLeftEm;
145+
if (marginLeftEm != null && marginLeftEm.isNegative) {
146+
final previousSpans = resultSpans;
147+
resultSpans = QueueList<KatexNode>();
148+
resultSpans.addFirst(KatexNegativeMarginNode(
149+
leftOffsetEm: marginLeftEm,
150+
nodes: previousSpans));
151+
}
152+
}
153+
}
154+
return resultSpans;
128155
}
129156

130157
static final _resetSizeClassRegExp = RegExp(r'^reset-size(\d\d?)$');
@@ -210,13 +237,31 @@ class _KatexParser {
210237
final pstrutStyles = _parseSpanInlineStyles(pstrutSpan)!;
211238
final pstrutHeight = pstrutStyles.heightEm ?? 0;
212239

240+
KatexSpanNode innerSpanNode = KatexSpanNode(
241+
styles: styles,
242+
text: null,
243+
nodes: _parseChildSpans(otherSpans));
244+
245+
final marginRightEm = styles.marginRightEm;
246+
final marginLeftEm = styles.marginLeftEm;
247+
if (marginRightEm != null && marginRightEm.isNegative) {
248+
throw KatexHtmlParseError();
249+
}
250+
if (marginLeftEm != null && marginLeftEm.isNegative) {
251+
innerSpanNode = KatexSpanNode(
252+
styles: KatexSpanStyles(),
253+
text: null,
254+
nodes: [
255+
KatexNegativeMarginNode(
256+
leftOffsetEm: marginLeftEm,
257+
nodes: [innerSpanNode]),
258+
]);
259+
}
260+
213261
rows.add(KatexVlistRowNode(
214262
verticalOffsetEm: topEm + pstrutHeight,
215263
debugHtmlNode: kDebugMode ? innerSpan : null,
216-
node: KatexSpanNode(
217-
styles: styles,
218-
text: null,
219-
nodes: _parseChildSpans(otherSpans))));
264+
node: innerSpanNode));
220265
} else {
221266
throw KatexHtmlParseError();
222267
}
@@ -532,17 +577,11 @@ class _KatexParser {
532577

533578
case 'margin-right':
534579
marginRightEm = _getEm(expression);
535-
if (marginRightEm != null) {
536-
if (marginRightEm < 0) throw KatexHtmlParseError();
537-
continue;
538-
}
580+
if (marginRightEm != null) continue;
539581

540582
case 'margin-left':
541583
marginLeftEm = _getEm(expression);
542-
if (marginLeftEm != null) {
543-
if (marginLeftEm < 0) throw KatexHtmlParseError();
544-
continue;
545-
}
584+
if (marginLeftEm != null) continue;
546585
}
547586

548587
// TODO handle more CSS properties
@@ -596,8 +635,8 @@ enum KatexSpanTextAlign {
596635
@immutable
597636
class KatexSpanStyles {
598637
final double? heightEm;
599-
final double? verticalAlignEm;
600638

639+
final double? verticalAlignEm;
601640
final double? topEm;
602641

603642
final double? marginRightEm;

lib/widgets/content.dart

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import 'code_block.dart';
2020
import 'dialog.dart';
2121
import 'icons.dart';
2222
import 'inset_shadow.dart';
23+
import 'katex.dart';
2324
import 'lightbox.dart';
2425
import 'message_list.dart';
2526
import 'poll.dart';
@@ -881,6 +882,7 @@ class _KatexNodeList extends StatelessWidget {
881882
KatexSpanNode() => _KatexSpan(e),
882883
KatexStrutNode() => _KatexStrut(e),
883884
KatexVlistNode() => _KatexVlist(e),
885+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
884886
}));
885887
}))));
886888
}
@@ -954,23 +956,21 @@ class _KatexSpan extends StatelessWidget {
954956
}
955957

956958
final marginRight = switch (styles.marginRightEm) {
957-
double marginRightEm => marginRightEm * em,
958-
null => null,
959+
double marginRightEm when marginRightEm >= 0 => marginRightEm * em,
960+
_ => null,
959961
};
960962
final marginLeft = switch (styles.marginLeftEm) {
961-
double marginLeftEm => marginLeftEm * em,
962-
null => null,
963+
double marginLeftEm when marginLeftEm >= 0 => marginLeftEm * em,
964+
_ => null,
963965
};
964966

965967
EdgeInsets? margin;
966968
if (marginRight != null || marginLeft != null) {
967969
margin = EdgeInsets.zero;
968970
if (marginRight != null) {
969-
assert(marginRight >= 0);
970971
margin += EdgeInsets.only(right: marginRight);
971972
}
972973
if (marginLeft != null) {
973-
assert(marginLeft >= 0);
974974
margin += EdgeInsets.only(left: marginLeft);
975975
}
976976
}
@@ -1029,6 +1029,21 @@ class _KatexVlist extends StatelessWidget {
10291029
}
10301030
}
10311031

1032+
class _KatexNegativeMargin extends StatelessWidget {
1033+
const _KatexNegativeMargin(this.node);
1034+
1035+
final KatexNegativeMarginNode node;
1036+
1037+
@override
1038+
Widget build(BuildContext context) {
1039+
final em = DefaultTextStyle.of(context).style.fontSize!;
1040+
1041+
return NegativeLeftOffset(
1042+
leftOffset: node.leftOffsetEm * em,
1043+
child: _KatexNodeList(nodes: node.nodes));
1044+
}
1045+
}
1046+
10321047
class WebsitePreview extends StatelessWidget {
10331048
const WebsitePreview({super.key, required this.node});
10341049

0 commit comments

Comments
 (0)