Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 101 additions & 1 deletion lib/model/katex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ class _KatexParser {
KatexSpanFontWeight? fontWeight;
KatexSpanFontStyle? fontStyle;
KatexSpanTextAlign? textAlign;
KatexBorderStyle? borderStyle;
var index = 0;
while (index < spanClasses.length) {
final spanClass = spanClasses[index++];
Expand Down Expand Up @@ -626,6 +627,32 @@ class _KatexParser {
case 'nobreak':
case 'allowbreak':
case 'mathdefault':
case 'tag':
case 'eqn-num':
case 'mtable':
case 'col-align-l':
case 'col-align-c':
case 'col-align-r':
case 'delimcenter':
case 'accent':
case 'accent-body':
case 'vlist':
case 'vlist-r':
case 'vlist-s':
case 'svg-align':
case 'hide-tail':
case 'halfarrow-left':
case 'halfarrow-right':
case 'brace-left':
case 'brace-center':
case 'brace-right':
case 'root':
case 'sqrt':
case 'pstrut':
case 'arraycolsep':
case 'vertical-separator':
case 'frac-line':
case 'mfrac':
// Ignore these classes because they don't have a CSS definition
// in katex.scss, but we encounter them in the generated HTML.
// (Why are they there if they're not used? The story seems to be:
Expand All @@ -636,6 +663,24 @@ class _KatexParser {
// )
break;

case 'overline':
case 'underline':
break;

case 'overline-line':
borderStyle = KatexBorderStyle(
position: KatexBorderPosition.bottom,
widthEm: 0.04,
);
break;

case 'underline-line':
borderStyle = KatexBorderStyle(
position: KatexBorderPosition.bottom,
widthEm: 0.04,
);
break;

default:
assert(debugLog('KaTeX: Unsupported CSS class: $spanClass'));
unsupportedCssClasses.add(spanClass);
Expand All @@ -644,6 +689,18 @@ class _KatexParser {
}

final inlineStyles = _parseInlineStyles(element);
// Extract border width if borderStyle was set
if (borderStyle != null) {
if (inlineStyles != null) {
final borderWidthEm = _takeStyleEm(inlineStyles, 'border-bottom-width');
if (borderWidthEm != null) {
borderStyle = KatexBorderStyle(
position: borderStyle.position,
widthEm: borderWidthEm,
color: borderStyle.color,
);
}}
}
final styles = KatexSpanStyles(
widthEm: widthEm,
fontFamily: fontFamily,
Expand All @@ -657,10 +714,13 @@ class _KatexParser {
marginRightEm: _takeStyleEm(inlineStyles, 'margin-right'),
color: _takeStyleColor(inlineStyles, 'color'),
position: _takeStylePosition(inlineStyles, 'position'),
borderStyle: borderStyle,
// TODO handle more CSS properties
);
if (inlineStyles != null && inlineStyles.isNotEmpty) {
for (final property in inlineStyles.keys) {
// Ignore known properties that don't need special handling
if (property == 'width' || property == 'min-width' || property == 'border-bottom-width') {continue;}
assert(debugLog('KaTeX: Unexpected inline CSS property: $property'));
unsupportedInlineCssProperties.add(property);
_hasError = true;
Expand Down Expand Up @@ -840,6 +900,39 @@ enum KatexSpanPosition {
relative,
}

enum KatexBorderPosition {
top,
bottom,
}

class KatexBorderStyle {
const KatexBorderStyle({
required this.position,
required this.widthEm,
this.color,
});

final KatexBorderPosition position;
final double widthEm;
final KatexSpanColor? color;

@override
bool operator ==(Object other) {
return other is KatexBorderStyle &&
other.position == position &&
other.widthEm == widthEm &&
other.color == color;
}

@override
int get hashCode => Object.hash('KatexBorderStyle', position, widthEm, color);

@override
String toString() {
return '${objectRuntimeType(this, 'KatexBorderStyle')}($position, $widthEm, $color)';
}
}

class KatexSpanColor {
const KatexSpanColor(this.r, this.g, this.b, this.a);

Expand Down Expand Up @@ -893,6 +986,7 @@ class KatexSpanStyles {

final KatexSpanColor? color;
final KatexSpanPosition? position;
final KatexBorderStyle? borderStyle;

const KatexSpanStyles({
this.widthEm,
Expand All @@ -907,6 +1001,7 @@ class KatexSpanStyles {
this.textAlign,
this.color,
this.position,
this.borderStyle,
});

@override
Expand All @@ -924,6 +1019,7 @@ class KatexSpanStyles {
textAlign,
color,
position,
borderStyle,
);

@override
Expand All @@ -940,7 +1036,8 @@ class KatexSpanStyles {
other.fontStyle == fontStyle &&
other.textAlign == textAlign &&
other.color == color &&
other.position == position;
other.position == position &&
other.borderStyle == borderStyle;
}

@override
Expand All @@ -958,6 +1055,7 @@ class KatexSpanStyles {
if (textAlign != null) args.add('textAlign: $textAlign');
if (color != null) args.add('color: $color');
if (position != null) args.add('position: $position');
if (borderStyle != null) args.add('borderStyle: $borderStyle');
return '${objectRuntimeType(this, 'KatexSpanStyles')}(${args.join(', ')})';
}

Expand All @@ -975,6 +1073,7 @@ class KatexSpanStyles {
bool textAlign = true,
bool color = true,
bool position = true,
bool borderStyle = true,
}) {
return KatexSpanStyles(
widthEm: widthEm ? this.widthEm : null,
Expand All @@ -989,6 +1088,7 @@ class KatexSpanStyles {
textAlign: textAlign ? this.textAlign : null,
color: color ? this.color : null,
position: position ? this.position : null,
borderStyle: borderStyle ? this.borderStyle : null,
);
}
}
Expand Down
22 changes: 18 additions & 4 deletions lib/widgets/katex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ class KatexWidget extends StatelessWidget {
Widget build(BuildContext context) {
Widget widget = _KatexNodeList(nodes: nodes);

return Directionality(
return IntrinsicWidth(
child: Directionality(
textDirection: TextDirection.ltr,
child: DefaultTextStyle(
style: mkBaseKatexTextStyle(textStyle).copyWith(
color: ContentTheme.of(context).textStylePlainParagraph.color),
child: widget));
child: widget)));
}
}

Expand Down Expand Up @@ -122,6 +123,19 @@ class _KatexSpan extends StatelessWidget {
Color.fromARGB(katexColor.a, katexColor.r, katexColor.g, katexColor.b),
null => null,
};
if (styles.borderStyle case final borderStyle?) {
final currentColor = color ?? DefaultTextStyle.of(context).style.color!;
final Color borderColor = borderStyle.color != null
? Color.fromARGB(borderStyle.color!.a, borderStyle.color!.r, borderStyle.color!.g, borderStyle.color!.b)
: currentColor;
final double borderWidth = borderStyle.widthEm * em;

return Container(
constraints: const BoxConstraints(minWidth: double.infinity),
height: borderWidth,
color: borderColor,
);
}

TextStyle? textStyle;
if (fontFamily != null ||
Expand Down Expand Up @@ -232,11 +246,11 @@ class _KatexVlist extends StatelessWidget {
Widget build(BuildContext context) {
final em = DefaultTextStyle.of(context).style.fontSize!;

return Stack(children: List.unmodifiable(node.rows.map((row) {
return IntrinsicWidth(child: Stack(children: List.unmodifiable(node.rows.map((row) {
return Transform.translate(
offset: Offset(0, row.verticalOffsetEm * em),
child: _KatexSpan(row.node));
})));
}))));
}
}

Expand Down
107 changes: 106 additions & 1 deletion test/model/katex_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,109 @@ class KatexExample extends ContentExample {
]),
]),
]);

static final overline = KatexExample.block(
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Saif.20KaTeX/near/2285099
r'overline: \overline{AB}',
r'\overline{AB}',
'<p>'
'<span class="katex-display"><span class="katex">'
'<span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mover accent="true"><mrow><mi>A</mi><mi>B</mi></mrow><mo stretchy="true">‾</mo></mover></mrow><annotation encoding="application/x-tex">\\overline{AB}</annotation></semantics></math></span>'
'<span class="katex-html" aria-hidden="true">'
'<span class="base">'
'<span class="strut" style="height:0.8833em;"></span>'
'<span class="mord overline">'
'<span class="vlist-t">'
'<span class="vlist-r">'
'<span class="vlist" style="height:0.8833em;">'
'<span style="top:-3em;">'
'<span class="pstrut" style="height:3em;"></span>'
'<span class="mord">'
'<span class="mord mathnormal">A</span>'
'<span class="mord mathnormal" style="margin-right:0.05017em;">B</span></span></span>'
'<span style="top:-3.8033em;">'
'<span class="pstrut" style="height:3em;"></span>'
'<span class="overline-line" style="border-bottom-width:0.04em;"></span></span></span></span></span></span></span></span></span></p>',[
KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.8833, verticalAlignEm: null),
KatexSpanNode(nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -3 + 3,
node: KatexSpanNode(nodes: [
KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
text: 'A'),
KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.05017, fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
text: 'B'),
]),
])),
KatexVlistRowNode(
verticalOffsetEm: -3.8033 + 3,
node: KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(borderStyle: KatexBorderStyle(position: KatexBorderPosition.bottom, widthEm: 0.04, color: null)),
nodes: []),
])),
]),
]),
]),
]);

static final underline = KatexExample.block(
// https://chat.zulip.org/#narrow/channel/7-test-here/topic/Saif.20KaTeX/near/2285099
r'underline: \underline{AB}',
r'\underline{AB}',
'<p>'
'<span class="katex-display"><span class="katex">'
'<span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><munder accentunder="true"><mrow><mi>A</mi><mi>B</mi></mrow><mo stretchy="true">‾</mo></munder></mrow><annotation encoding="application/x-tex">\\underline{AB}</annotation></semantics></math></span>'
'<span class="katex-html" aria-hidden="true">'
'<span class="base">'
'<span class="strut" style="height:0.8833em;vertical-align:-0.2em;"></span>'
'<span class="mord underline">'
'<span class="vlist-t vlist-t2">'
'<span class="vlist-r">'
'<span class="vlist" style="height:0.6833em;">'
'<span style="top:-2.84em;">'
'<span class="pstrut" style="height:3em;"></span>'
'<span class="underline-line" style="border-bottom-width:0.04em;"></span></span>'
'<span style="top:-3em;">'
'<span class="pstrut" style="height:3em;"></span>'
'<span class="mord">'
'<span class="mord mathnormal">A</span>'
'<span class="mord mathnormal" style="margin-right:0.05017em;">B</span></span></span></span>'
'<span class="vlist-s">​</span></span>'
'<span class="vlist-r">'
'<span class="vlist" style="height:0.2em;"><span></span></span></span></span></span></span></span></span></span></p>',[
KatexSpanNode(nodes: [
KatexStrutNode(heightEm: 0.8833, verticalAlignEm: -0.2),
KatexSpanNode(nodes: [
KatexVlistNode(rows: [
KatexVlistRowNode(
verticalOffsetEm: -2.84 + 3,
node: KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(borderStyle: KatexBorderStyle(position: KatexBorderPosition.bottom, widthEm: 0.04, color: null)),
nodes: []),
])),
KatexVlistRowNode(
verticalOffsetEm: -3 + 3,
node: KatexSpanNode(nodes: [
KatexSpanNode(nodes: [
KatexSpanNode(
styles: KatexSpanStyles(fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
text: 'A'),
KatexSpanNode(
styles: KatexSpanStyles(marginRightEm: 0.05017, fontFamily: 'KaTeX_Math', fontStyle: KatexSpanFontStyle.italic),
text: 'B'),
]),
])),
]),
]),
]),
]);
}

void main() async {
Expand All @@ -754,6 +857,8 @@ void main() async {
testParseExample(KatexExample.bigOperators);
testParseExample(KatexExample.colonEquals);
testParseExample(KatexExample.nulldelimiter);
testParseExample(KatexExample.overline);
testParseExample(KatexExample.underline);

group('parseCssHexColor', () {
const testCases = [
Expand Down Expand Up @@ -821,4 +926,4 @@ void main() async {
}, skip: Platform.isWindows, // [intended] purely analyzes source, so
// any one platform is enough; avoid dealing with Windows file paths
);
}
}
6 changes: 6 additions & 0 deletions test/widgets/katex_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ void main() {
('a', Offset(2.47, 3.36), Size(10.88, 25.00)),
('b', Offset(15.81, 3.36), Size(8.82, 25.00)),
]),
(KatexExample.overline, skip: false, [
('A', Offset(0.0, 5.6), Size(15.4, 25.0)),
]),
(KatexExample.underline, skip: false, [
('B', Offset(15.4, 5.6), Size(15.6, 25.0)),
]),
];

for (final testCase in testCases) {
Expand Down