1
+ import 'package:flutter/foundation.dart' ;
1
2
import 'package:html/dom.dart' as dom;
2
3
3
4
import 'content.dart' ;
@@ -16,7 +17,205 @@ class KatexParser {
16
17
}));
17
18
}
18
19
20
+ static final _resetSizeClassRegExp = RegExp (r'^reset-size(\d\d?)$' );
21
+ static final _sizeClassRegExp = RegExp (r'^size(\d\d?)$' );
22
+
19
23
KatexSpanNode _parseSpan (dom.Element element) {
24
+ final spanClasses = List <String >.unmodifiable (element.className.split (' ' ));
25
+
26
+ var styles = KatexSpanStyles ();
27
+ var index = 0 ;
28
+ while (index < spanClasses.length) {
29
+ final spanClass = spanClasses[index];
30
+ switch (spanClass) {
31
+ case 'textbf' :
32
+ // .textbf { font-weight: bold; }
33
+ styles.fontWeight = KatexSpanFontWeight .bold;
34
+
35
+ case 'textit' :
36
+ // .textit { font-style: italic; }
37
+ styles.fontStyle = KatexSpanFontStyle .italic;
38
+
39
+ case 'textrm' :
40
+ // .textrm { font-family: KaTeX_Main; }
41
+ styles.fontFamily = 'KaTeX_Main' ;
42
+
43
+ case 'textsf' :
44
+ // .textsf { font-family: KaTeX_SansSerif; }
45
+ styles.fontFamily = 'KaTeX_SansSerif' ;
46
+
47
+ case 'texttt' :
48
+ // .texttt { font-family: KaTeX_Typewriter; }
49
+ styles.fontFamily = 'KaTeX_Typewriter' ;
50
+
51
+ case 'mathnormal' :
52
+ // .mathnormal { font-family: KaTeX_Math; font-style: italic; }
53
+ styles.fontFamily = 'KaTeX_Math' ;
54
+ styles.fontStyle = KatexSpanFontStyle .italic;
55
+
56
+ case 'mathit' :
57
+ // .mathit { font-family: KaTeX_Main; font-style: italic; }
58
+ styles.fontFamily = 'KaTeX_Main' ;
59
+ styles.fontStyle = KatexSpanFontStyle .italic;
60
+
61
+ case 'mathrm' :
62
+ // .mathrm { font-style: normal; }
63
+ styles.fontStyle = KatexSpanFontStyle .normal;
64
+
65
+ case 'mathbf' :
66
+ // .mathbf { font-family: KaTeX_Main; font-weight: bold; }
67
+ styles.fontFamily = 'KaTeX_Main' ;
68
+ styles.fontWeight = KatexSpanFontWeight .bold;
69
+
70
+ case 'boldsymbol' :
71
+ // .boldsymbol { font-family: KaTeX_Math; font-weight: bold; font-style: italic; }
72
+ styles.fontFamily = 'KaTeX_Math' ;
73
+ styles.fontWeight = KatexSpanFontWeight .bold;
74
+ styles.fontStyle = KatexSpanFontStyle .italic;
75
+
76
+ case 'amsrm' :
77
+ // .amsrm { font-family: KaTeX_AMS; }
78
+ styles.fontFamily = 'KaTeX_AMS' ;
79
+
80
+ case 'mathbb' :
81
+ case 'textbb' :
82
+ // .mathbb,
83
+ // .textbb { font-family: KaTeX_AMS; }
84
+ styles.fontFamily = 'KaTeX_AMS' ;
85
+
86
+ case 'mathcal' :
87
+ // .mathcal { font-family: KaTeX_Caligraphic; }
88
+ styles.fontFamily = 'KaTeX_Caligraphic' ;
89
+
90
+ case 'mathfrak' :
91
+ case 'textfrak' :
92
+ // .mathfrak,
93
+ // .textfrak { font-family: KaTeX_Fraktur; }
94
+ styles.fontFamily = 'KaTeX_Fraktur' ;
95
+
96
+ case 'mathboldfrak' :
97
+ case 'textboldfrak' :
98
+ // .mathboldfrak,
99
+ // .textboldfrak { font-family: KaTeX_Fraktur; font-weight: bold; }
100
+ styles.fontFamily = 'KaTeX_Fraktur' ;
101
+ styles.fontWeight = KatexSpanFontWeight .bold;
102
+
103
+ case 'mathtt' :
104
+ // .mathtt { font-family: KaTeX_Typewriter; }
105
+ styles.fontFamily = 'KaTeX_Typewriter' ;
106
+
107
+ case 'mathscr' :
108
+ case 'textscr' :
109
+ // .mathscr,
110
+ // .textscr { font-family: KaTeX_Script; }
111
+ styles.fontFamily = 'KaTeX_Script' ;
112
+ }
113
+
114
+ switch (spanClass) {
115
+ case 'mathsf' :
116
+ case 'textsf' :
117
+ // .mathsf,
118
+ // .textsf { font-family: KaTeX_SansSerif; }
119
+ styles.fontFamily = 'KaTeX_SansSerif' ;
120
+
121
+ case 'mathboldsf' :
122
+ case 'textboldsf' :
123
+ // .mathboldsf,
124
+ // .textboldsf { font-family: KaTeX_SansSerif; font-weight: bold; }
125
+ styles.fontFamily = 'KaTeX_SansSerif' ;
126
+ styles.fontWeight = KatexSpanFontWeight .bold;
127
+
128
+ case 'mathsfit' :
129
+ case 'mathitsf' :
130
+ case 'textitsf' :
131
+ // .mathsfit,
132
+ // .mathitsf,
133
+ // .textitsf { font-family: KaTeX_SansSerif; font-style: italic; }
134
+ styles.fontFamily = 'KaTeX_SansSerif' ;
135
+ styles.fontStyle = KatexSpanFontStyle .italic;
136
+
137
+ case 'mainrm' :
138
+ // .mainrm { font-family: KaTeX_Main; font-style: normal; }
139
+ styles.fontFamily = 'KaTeX_Main' ;
140
+ styles.fontStyle = KatexSpanFontStyle .normal;
141
+
142
+ case 'sizing' :
143
+ case 'fontsize-ensurer' :
144
+ // .sizing,
145
+ // .fontsize-ensurer { ... }
146
+ if (index + 2 < spanClass.length) {
147
+ final resetSizeClass = spanClasses[index + 1 ];
148
+ final sizeClass = spanClasses[index + 2 ];
149
+
150
+ final resetSizeClassSuffix = _resetSizeClassRegExp.firstMatch (resetSizeClass)? .group (1 );
151
+ final sizeClassSuffix = _sizeClassRegExp.firstMatch (sizeClass)? .group (1 );
152
+
153
+ if (resetSizeClassSuffix != null && sizeClassSuffix != null ) {
154
+ const sizes = < double > [0.5 , 0.6 , 0.7 , 0.8 , 0.9 , 1 , 1.2 , 1.44 , 1.728 , 2.074 , 2.488 ];
155
+
156
+ final resetSizeIdx = int .parse (resetSizeClassSuffix, radix: 10 );
157
+ final sizeIdx = int .parse (sizeClassSuffix, radix: 10 );
158
+
159
+ // These indexes start at 1.
160
+ if (resetSizeIdx <= sizes.length && sizeIdx <= sizes.length) {
161
+ styles.fontSizeEm = sizes[resetSizeIdx - 1 ] * sizes[sizeIdx - 1 ];
162
+ index += 3 ;
163
+ continue ;
164
+ }
165
+ }
166
+ }
167
+
168
+ // Should be unreachable.
169
+ throw KatexHtmlParseError ();
170
+
171
+ case 'delimsizing' :
172
+ // .delimsizing { ... }
173
+ if (index + 1 < spanClasses.length) {
174
+ final nextClass = spanClasses[index + 1 ];
175
+ switch (nextClass) {
176
+ case 'size1' :
177
+ styles.fontFamily = 'KaTeX_Size1' ;
178
+ case 'size2' :
179
+ styles.fontFamily = 'KaTeX_Size2' ;
180
+ case 'size3' :
181
+ styles.fontFamily = 'KaTeX_Size3' ;
182
+ case 'size4' :
183
+ styles.fontFamily = 'KaTeX_Size4' ;
184
+ }
185
+ if (styles.fontFamily == null ) throw KatexHtmlParseError ();
186
+
187
+ index += 2 ;
188
+ continue ;
189
+ }
190
+
191
+ // Should be unreachable.
192
+ throw KatexHtmlParseError ();
193
+
194
+ case 'op-symbol' :
195
+ // .op-symbol { ... }
196
+ if (index + 1 < spanClasses.length) {
197
+ final nextClass = spanClasses[index + 1 ];
198
+ switch (nextClass) {
199
+ case 'small-op' :
200
+ styles.fontFamily = 'KaTeX_Size1' ;
201
+ case 'large-op' :
202
+ styles.fontFamily = 'KaTeX_Size2' ;
203
+ }
204
+ if (styles.fontFamily == null ) throw KatexHtmlParseError ();
205
+
206
+ index += 2 ;
207
+ continue ;
208
+ }
209
+
210
+ // Should be unreachable.
211
+ throw KatexHtmlParseError ();
212
+
213
+ // TODO more classes from katex.scss
214
+ }
215
+
216
+ index++ ;
217
+ }
218
+
20
219
String ? text;
21
220
List <KatexSpanNode >? spans;
22
221
if (element.nodes case [dom.Text (data: final data)]) {
@@ -28,10 +227,91 @@ class KatexParser {
28
227
29
228
return KatexSpanNode (
30
229
text: text,
230
+ styles: styles,
31
231
nodes: spans ?? const []);
32
232
}
33
233
}
34
234
235
+ enum KatexSpanFontWeight {
236
+ bold,
237
+ }
238
+
239
+ enum KatexSpanFontStyle {
240
+ normal,
241
+ italic,
242
+ }
243
+
244
+ enum KatexSpanTextAlign {
245
+ left,
246
+ center,
247
+ right,
248
+ }
249
+
250
+ class KatexSpanStyles {
251
+ String ? fontFamily;
252
+ double ? fontSizeEm;
253
+ KatexSpanFontStyle ? fontStyle;
254
+ KatexSpanFontWeight ? fontWeight;
255
+ KatexSpanTextAlign ? textAlign;
256
+
257
+ KatexSpanStyles ({
258
+ this .fontFamily,
259
+ this .fontSizeEm,
260
+ this .fontStyle,
261
+ this .fontWeight,
262
+ this .textAlign,
263
+ });
264
+
265
+ @override
266
+ int get hashCode => Object .hash (
267
+ 'KatexSpanStyles' ,
268
+ fontFamily,
269
+ fontSizeEm,
270
+ fontStyle,
271
+ fontWeight,
272
+ textAlign,
273
+ );
274
+
275
+ @override
276
+ bool operator == (Object other) {
277
+ return other is KatexSpanStyles &&
278
+ other.fontFamily == fontFamily &&
279
+ other.fontSizeEm == fontSizeEm &&
280
+ other.fontStyle == fontStyle &&
281
+ other.fontWeight == fontWeight &&
282
+ other.textAlign == textAlign;
283
+ }
284
+
285
+ static final _zero = KatexSpanStyles ();
286
+
287
+ @override
288
+ String toString () {
289
+ if (this == _zero) return '${objectRuntimeType (this , 'KatexSpanStyles' )}()' ;
290
+
291
+ final args = < String > [];
292
+ if (fontFamily != null ) args.add ('fontFamily: $fontFamily ' );
293
+ if (fontSizeEm != null ) args.add ('fontSizeEm: $fontSizeEm ' );
294
+ if (fontStyle != null ) args.add ('fontStyle: $fontStyle ' );
295
+ if (fontWeight != null ) args.add ('fontWeight: $fontWeight ' );
296
+ if (textAlign != null ) args.add ('textAlign: $textAlign ' );
297
+ return '${objectRuntimeType (this , 'KatexSpanStyles' )}(${args .join (', ' )})' ;
298
+ }
299
+
300
+ KatexSpanStyles merge (KatexSpanStyles other) {
301
+ return KatexSpanStyles (
302
+ fontFamily: other.fontFamily ?? fontFamily,
303
+ fontSizeEm: other.fontSizeEm ?? fontSizeEm,
304
+ fontStyle: other.fontStyle ?? fontStyle,
305
+ fontWeight: other.fontWeight ?? fontWeight,
306
+ textAlign: other.textAlign ?? textAlign,
307
+ );
308
+ }
309
+ }
310
+
311
+ class KatexSpanStylesProperty extends DiagnosticsProperty <KatexSpanStyles > {
312
+ KatexSpanStylesProperty (super .name, super .value);
313
+ }
314
+
35
315
class KatexHtmlParseError extends Error {
36
316
final String ? message;
37
317
KatexHtmlParseError ([this .message]);
0 commit comments