accent.dart
6.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import '../../render/layout/min_dimension.dart';
import '../../render/layout/reset_dimension.dart';
import '../../render/layout/shift_baseline.dart';
import '../../render/layout/vlist.dart';
import '../../render/svg/static.dart';
import '../../render/svg/stretchy.dart';
import '../../render/symbols/make_symbol.dart';
import '../../utils/unicode_literal.dart';
import '../accents.dart';
import '../options.dart';
import '../size.dart';
import '../syntax_tree.dart';
import '../types.dart';
/// Accent node.
///
/// Examples: `\hat`
class AccentNode extends SlotableNode<EquationRowNode> {
/// Base where the accent is applied upon.
final EquationRowNode base;
/// Unicode symbol of the accent character.
final String label;
/// Is the accent strecthy?
///
/// Stretchy accent will stretch according to the width of [base].
final bool isStretchy;
/// Is the accent shifty?
///
/// Shifty accent will shift according to the italic of [base].
final bool isShifty;
AccentNode({
required this.base,
required this.label,
required this.isStretchy,
required this.isShifty,
});
@override
BuildResult buildWidget(
MathOptions options, List<BuildResult?> childBuildResults) {
// Checking of character box is done automatically by the passing of
// BuildResult, so we don't need to check it here.
final baseResult = childBuildResults[0]!;
final skew = isShifty ? baseResult.skew : 0.0;
Widget accentWidget;
if (!isStretchy) {
Widget accentSymbolWidget;
// Following comment are selected from KaTeX:
//
// Before version 0.9, \vec used the combining font glyph U+20D7.
// But browsers, especially Safari, are not consistent in how they
// render combining characters when not preceded by a character.
// So now we use an SVG.
// If Safari reforms, we should consider reverting to the glyph.
if (label == '\u2192') {
// We need non-null baseline. Because ShiftBaseline cannot deal with a
// baseline distance of null due to Flutter rendering pipeline design.
accentSymbolWidget = staticSvg('vec', options, needBaseline: true);
} else {
final accentRenderConfig = accentRenderConfigs[label];
if (accentRenderConfig == null || accentRenderConfig.overChar == null) {
accentSymbolWidget = Container();
} else {
accentSymbolWidget = makeBaseSymbol(
symbol: accentRenderConfig.overChar!,
variantForm: false,
atomType: AtomType.ord,
mode: Mode.text,
options: options,
).widget;
}
}
// Non stretchy accent can not contribute to overall width, thus they must
// fit exactly with the width even if it means overflow.
accentWidget = LayoutBuilder(
builder: (context, constraints) => ResetDimension(
depth: 0.0, // Cut off xHeight
width: constraints.minWidth, // Ensure width
child: ShiftBaseline(
// \tilde is submerged below baseline in KaTeX fonts
relativePos: 1.0,
// Shift baseline up by xHeight
offset: -options.fontMetrics.xHeight.cssEm.toLpUnder(options),
child: accentSymbolWidget,
),
),
);
} else {
// Strechy accent
accentWidget = LayoutBuilder(
builder: (context, constraints) {
// \overline needs a special case, as KaTeX does.
if (label == '\u00AF') {
final defaultRuleThickness = options
.fontMetrics.defaultRuleThickness.cssEm
.toLpUnder(options);
return Padding(
padding: EdgeInsets.only(bottom: 3 * defaultRuleThickness),
child: Container(
width: constraints.minWidth,
height: defaultRuleThickness, // TODO minRuleThickness
color: options.color,
),
);
} else {
final accentRenderConfig = accentRenderConfigs[label];
if (accentRenderConfig == null ||
accentRenderConfig.overImageName == null) {
return Container();
}
var svgWidget = strechySvgSpan(
accentRenderConfig.overImageName!,
constraints.minWidth,
options,
);
// \horizBrace also needs a special case, as KaTeX does.
if (label == '\u23de') {
return Padding(
padding: EdgeInsets.only(bottom: 0.1.cssEm.toLpUnder(options)),
child: svgWidget,
);
} else {
return svgWidget;
}
}
},
);
}
return BuildResult(
options: options,
italic: baseResult.italic,
skew: baseResult.skew,
widget: VList(
baselineReferenceWidgetIndex: 1,
children: <Widget>[
VListElement(
customCrossSize: (width) =>
BoxConstraints(minWidth: width - 2 * skew),
hShift: skew,
child: accentWidget,
),
// Set min height
MinDimension(
minHeight: options.fontMetrics.xHeight.cssEm.toLpUnder(options),
topPadding: 0,
child: baseResult.widget,
),
],
),
);
}
@override
List<MathOptions> computeChildOptions(MathOptions options) =>
[options.havingCrampedStyle()];
@override
List<EquationRowNode> computeChildren() => [base];
@override
AtomType get leftType => AtomType.ord;
@override
AtomType get rightType => AtomType.ord;
@override
bool shouldRebuildWidget(MathOptions oldOptions, MathOptions newOptions) =>
false;
@override
AccentNode updateChildren(List<EquationRowNode> newChildren) =>
copyWith(base: newChildren[0]);
@override
Map<String, Object?> toJson() => super.toJson()
..addAll({
'base': base.toJson(),
'label': unicodeLiteral(label),
'isStretchy': isStretchy,
'isShifty': isShifty,
});
AccentNode copyWith({
EquationRowNode? base,
String? label,
bool? isStretchy,
bool? isShifty,
}) =>
AccentNode(
base: base ?? this.base,
label: label ?? this.label,
isStretchy: isStretchy ?? this.isStretchy,
isShifty: isShifty ?? this.isShifty,
);
}