Skip to content

Commit ad10be1

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 fe49e16 commit ad10be1

File tree

3 files changed

+92
-15
lines changed

3 files changed

+92
-15
lines changed

lib/model/content.dart

+32-6
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ class MathBlockNode extends BlockContentNode {
350350
});
351351

352352
final String texSource;
353-
final List<KatexSpanNode>? nodes;
353+
final List<KatexNode>? nodes;
354354

355355
@override
356356
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@@ -364,7 +364,11 @@ class MathBlockNode extends BlockContentNode {
364364
}
365365
}
366366

367-
class KatexSpanNode extends ContentNode {
367+
sealed class KatexNode extends ContentNode {
368+
const KatexNode({super.debugHtmlNode});
369+
}
370+
371+
class KatexSpanNode extends KatexNode {
368372
const KatexSpanNode({
369373
required this.styles,
370374
required this.text,
@@ -374,7 +378,7 @@ class KatexSpanNode extends ContentNode {
374378

375379
final KatexSpanStyles styles;
376380
final String? text;
377-
final List<KatexSpanNode> nodes;
381+
final List<KatexNode> nodes;
378382

379383
@override
380384
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@@ -389,6 +393,28 @@ class KatexSpanNode extends ContentNode {
389393
}
390394
}
391395

396+
class KatexNegativeMarginNode extends KatexNode {
397+
const KatexNegativeMarginNode({
398+
this.marginRightEm,
399+
this.nodes = const [],
400+
super.debugHtmlNode,
401+
});
402+
403+
final double? marginRightEm;
404+
final List<KatexNode> nodes;
405+
406+
@override
407+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
408+
super.debugFillProperties(properties);
409+
properties.add(StringProperty('marginRightEm', '$marginRightEm'));
410+
}
411+
412+
@override
413+
List<DiagnosticsNode> debugDescribeChildren() {
414+
return nodes.map((node) => node.toDiagnosticsNode()).toList();
415+
}
416+
}
417+
392418
class ImageNodeList extends BlockContentNode {
393419
const ImageNodeList(this.images, {super.debugHtmlNode});
394420

@@ -858,7 +884,7 @@ class MathInlineNode extends InlineContentNode {
858884
});
859885

860886
final String texSource;
861-
final List<KatexSpanNode>? nodes;
887+
final List<KatexNode>? nodes;
862888

863889
@override
864890
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@@ -895,7 +921,7 @@ class GlobalTimeNode extends InlineContentNode {
895921

896922
////////////////////////////////////////////////////////////////
897923
898-
({List<KatexSpanNode>? spans, String texSource})? _parseMath(
924+
({List<KatexNode>? spans, String texSource})? _parseMath(
899925
dom.Element element, {
900926
required bool block,
901927
}) {
@@ -945,7 +971,7 @@ class GlobalTimeNode extends InlineContentNode {
945971
return null;
946972
}
947973

948-
List<KatexSpanNode>? spans;
974+
List<KatexNode>? spans;
949975
try {
950976
spans = KatexParser().parseKatexHTML(katexHtmlElement);
951977
} on KatexHtmlParseError catch (e, st) {

lib/model/katex.dart

+20-6
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,31 @@ import '../log.dart';
77
import 'content.dart';
88

99
class KatexParser {
10-
List<KatexSpanNode> parseKatexHTML(dom.Element element) {
10+
List<KatexNode> parseKatexHTML(dom.Element element) {
1111
assert(element.localName == 'span');
1212
assert(element.className == 'katex-html');
1313
return _parseChildSpans(element);
1414
}
1515

16-
List<KatexSpanNode> _parseChildSpans(dom.Element element) {
17-
return List.unmodifiable(element.nodes.map((node) {
16+
List<KatexNode> _parseChildSpans(dom.Element element) {
17+
var resultSpans = <KatexNode>[];
18+
for (final node in element.nodes.reversed) {
1819
if (node is! dom.Element) throw KatexHtmlParseError();
19-
return _parseSpan(node);
20-
}));
20+
final span = _parseSpan(node);
21+
resultSpans.add(span);
22+
23+
final marginRightEm = span.styles.marginRightEm;
24+
if (marginRightEm != null && marginRightEm.isNegative) {
25+
final previousSpansReversed =
26+
resultSpans.reversed.toList(growable: false);
27+
resultSpans = [];
28+
resultSpans.add(KatexNegativeMarginNode(
29+
marginRightEm: marginRightEm,
30+
nodes: previousSpansReversed));
31+
}
32+
}
33+
34+
return resultSpans.reversed.toList(growable: false);
2135
}
2236

2337
static final _resetSizeClassRegExp = RegExp(r'^reset-size(\d\d?)$');
@@ -220,7 +234,7 @@ class KatexParser {
220234
}
221235

222236
String? text;
223-
List<KatexSpanNode>? spans;
237+
List<KatexNode>? spans;
224238
if (element.nodes case [dom.Text(data: final data)]) {
225239
text = data;
226240
} else {

lib/widgets/content.dart

+40-3
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,7 @@ class _Katex extends StatelessWidget {
830830
});
831831

832832
final bool inline;
833-
final List<KatexSpanNode> nodes;
833+
final List<KatexNode> nodes;
834834

835835
@override
836836
Widget build(BuildContext context) {
@@ -840,7 +840,10 @@ class _Katex extends StatelessWidget {
840840
return WidgetSpan(
841841
alignment: PlaceholderAlignment.baseline,
842842
baseline: TextBaseline.alphabetic,
843-
child: _KatexSpan(e));
843+
child: switch (e) {
844+
KatexSpanNode() => _KatexSpan(e),
845+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
846+
});
844847
}))));
845848

846849
if (!inline) {
@@ -880,7 +883,10 @@ class _KatexSpan extends StatelessWidget {
880883
return WidgetSpan(
881884
alignment: PlaceholderAlignment.baseline,
882885
baseline: TextBaseline.alphabetic,
883-
child: _KatexSpan(e));
886+
child: switch (e) {
887+
KatexSpanNode() => _KatexSpan(e),
888+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
889+
});
884890
}))));
885891
}
886892

@@ -947,6 +953,37 @@ class _KatexSpan extends StatelessWidget {
947953
}
948954
}
949955

956+
class _KatexNegativeMargin extends StatelessWidget {
957+
const _KatexNegativeMargin(this.node);
958+
959+
final KatexNegativeMarginNode node;
960+
961+
@override
962+
Widget build(BuildContext context) {
963+
final em = DefaultTextStyle.of(context).style.fontSize!;
964+
965+
final widget = RichText(
966+
text: TextSpan(
967+
children: List.unmodifiable(
968+
node.nodes.map((e) {
969+
return WidgetSpan(
970+
alignment: PlaceholderAlignment.baseline,
971+
baseline: TextBaseline.alphabetic,
972+
child: switch (e) {
973+
KatexSpanNode() => _KatexSpan(e),
974+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
975+
});
976+
}))));
977+
978+
var offset = Offset.zero;
979+
final marginRightEm = node.marginRightEm;
980+
if (marginRightEm != null) offset += Offset(marginRightEm * em, 0);
981+
return offset == Offset.zero
982+
? widget
983+
: Transform.translate(offset: offset, child: widget);
984+
}
985+
}
986+
950987
class WebsitePreview extends StatelessWidget {
951988
const WebsitePreview({super.key, required this.node});
952989

0 commit comments

Comments
 (0)