Skip to content

Commit ed9b04c

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 babe5c8 commit ed9b04c

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) {
@@ -879,7 +882,10 @@ class _KatexSpan extends StatelessWidget {
879882
return WidgetSpan(
880883
alignment: PlaceholderAlignment.baseline,
881884
baseline: TextBaseline.alphabetic,
882-
child: _KatexSpan(e));
885+
child: switch (e) {
886+
KatexSpanNode() => _KatexSpan(e),
887+
KatexNegativeMarginNode() => _KatexNegativeMargin(e),
888+
});
883889
}))));
884890
}
885891

@@ -946,6 +952,37 @@ class _KatexSpan extends StatelessWidget {
946952
}
947953
}
948954

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

0 commit comments

Comments
 (0)