1+ import 'package:flutter/foundation.dart' ;
12import 'package:html/dom.dart' as dom;
23
34import 'content.dart' ;
@@ -16,7 +17,205 @@ class KatexParser {
1617 }));
1718 }
1819
20+ static final _resetSizeClassRegExp = RegExp (r'^reset-size(\d\d?)$' );
21+ static final _sizeClassRegExp = RegExp (r'^size(\d\d?)$' );
22+
1923 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+
20219 String ? text;
21220 List <KatexSpanNode >? spans;
22221 if (element.nodes case [dom.Text (data: final data)]) {
@@ -28,10 +227,91 @@ class KatexParser {
28227
29228 return KatexSpanNode (
30229 text: text,
230+ styles: styles,
31231 nodes: spans ?? const []);
32232 }
33233}
34234
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+
35315class KatexHtmlParseError extends Error {
36316 final String ? message;
37317 KatexHtmlParseError ([this .message]);
0 commit comments