1use crate::engine::extension::CSS;
2use crate::engine::nodes::RusTeXNode;
3use crate::engine::stomach::RusTeXStomach;
4use crate::engine::{Refs, Res, Types, register_command};
5use crate::utils::{VecMap, VecSet};
6use tex_engine::add_node;
7use tex_engine::commands::primitives::{register_simple_expandable, register_unexpandable};
8use tex_engine::commands::{CommandScope, PrimitiveCommand};
9use tex_engine::engine::DefaultEngine;
10use tex_engine::engine::mouth::Mouth;
11use tex_engine::engine::state::State;
12use tex_engine::engine::stomach::Stomach;
13use tex_engine::prelude::*;
14use tex_engine::tex::nodes::horizontal::HNode;
15use tex_engine::tex::nodes::math::{
16 MathAtom, MathClass, MathKernel, MathNode, MathNucleus, UnresolvedMarkers,
17};
18use tex_engine::tex::nodes::vertical::VNode;
19use tex_engine::tex::nodes::{ListTarget, NodeList};
20use tex_engine::tex::tokens::CompactToken;
21use tex_engine::utils::errors::TeXError;
22
23pub fn register_primitives_preinit(engine: &mut DefaultEngine<Types>) {
24 register_simple_expandable(engine, CLOSE_FONT, close_font);
25 engine.state.register_primitive(
26 &mut engine.aux,
27 "rustexBREAK",
28 PrimitiveCommand::Unexpandable {
29 scope: CommandScope::Any,
30 apply: |_, _| {
31 println!("HERE!");
32 Ok(())
33 },
34 },
35 );
36}
37
38pub fn register_primitives_postinit(engine: &mut DefaultEngine<Types>) {
39 crate::engine::pgf::register_pgf(engine);
44 register_unexpandable(
45 engine,
46 "rustex@annotateHTML",
47 CommandScope::Any,
48 annot_begin,
49 );
50 register_unexpandable(engine, "rustex@HTMLNode", CommandScope::Any, node_begin);
51 register_unexpandable(
52 engine,
53 "rustex@annotateHTMLEnd",
54 CommandScope::Any,
55 annot_end,
56 );
57 engine.state.register_primitive(
58 &mut engine.aux,
59 "if@rustex",
60 PrimitiveCommand::Conditional(tex_engine::commands::tex::iftrue),
61 );
62 register_unexpandable(
63 engine,
64 "rustex@addNamespaceAbbrev",
65 CommandScope::Any,
66 namespace,
67 );
68 register_unexpandable(engine, "rustex@addMeta", CommandScope::Any, meta);
69 register_unexpandable(engine, "rustex@annotateTop", CommandScope::Any, annot_top);
70 register_unexpandable(engine, "rustex@cssLink", CommandScope::Any, css_link);
71 register_unexpandable(engine, "rustex@cssLiteral", CommandScope::Any, css_literal);
72 register_unexpandable(
73 engine,
74 "rustex@HTMLLiteral",
75 CommandScope::Any,
76 html_literal,
77 );
78 register_unexpandable(
79 engine,
80 "rustex@annotateInvisible",
81 CommandScope::Any,
82 invisible_begin,
83 );
84 register_unexpandable(
85 engine,
86 "rustex@annotateInvisibleEnd",
87 CommandScope::Any,
88 invisible_end,
89 );
90 register_unexpandable(
91 engine,
92 "rustex@@underbrace",
93 CommandScope::MathOnly,
94 underbrace,
95 );
96 register_unexpandable(
97 engine,
98 "rustex@@overbrace",
99 CommandScope::MathOnly,
100 overbrace,
101 );
102 }
105
106fn html_literal(engine: Refs, token: CompactToken) -> Res<()> {
107 let mut lit = String::new();
108 engine.read_braced_string(true, true, &token, &mut lit)?;
109 let node = RusTeXNode::Literal(lit);
110 add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
111 Ok(())
112}
113fn css_link(engine: Refs, token: CompactToken) -> Res<()> {
114 let mut file = String::new();
115 engine.read_braced_string(true, true, &token, &mut file)?;
116 engine.aux.extension.css.insert(CSS::File(file));
117 Ok(())
118}
119fn css_literal(engine: Refs, token: CompactToken) -> Res<()> {
120 let mut literal = String::new();
121 engine.read_braced_string(true, true, &token, &mut literal)?;
122 engine.aux.extension.css.insert(CSS::Literal(literal));
123 Ok(())
124}
125
126fn namespace(engine: Refs, token: CompactToken) -> Res<()> {
127 let mut key = String::new();
128 engine.read_braced_string(true, true, &token, &mut key)?;
129 let mut value = String::new();
130 engine.read_braced_string(true, true, &token, &mut value)?;
131 engine.aux.extension.namespaces.insert(key, value);
132 Ok(())
133}
134
135fn meta(engine: Refs, token: CompactToken) -> Res<()> {
136 let mut attrs = VecMap::default();
137 let mut str = String::new();
138 engine.read_braced_string(true, true, &token, &mut str)?;
139 let mut s = str[..].trim();
140 while !s.is_empty() {
141 let key = if let Some(i) = s.find('=') {
142 let (n, v) = s.split_at(i);
143 s = v[1..].trim_start();
144 n.trim()
145 } else {
146 todo!()
147 };
148 let delim = if s.starts_with('"') {
149 s = &s[1..];
150 '"'
151 } else if s.starts_with('\'') {
152 s = &s[1..];
153 '\''
154 } else {
155 todo!()
156 };
157 let val = if let Some(i) = s.find(delim) {
158 let (v, r) = s.split_at(i);
159 s = r[1..].trim_start();
160 v.trim()
161 } else {
162 todo!()
163 };
164 attrs.insert(key.to_string(), val.to_string());
165 }
166 engine.aux.extension.metas.push(attrs);
167 Ok(())
168}
169
170fn annot_top(engine: Refs, token: CompactToken) -> Res<()> {
171 let mut str = String::new();
172 engine.read_braced_string(true, true, &token, &mut str)?;
173 let mut s = str[..].trim();
174 let top = &mut engine.aux.extension.top;
175 while !s.is_empty() {
176 let key = if let Some(i) = s.find('=') {
177 let (n, v) = s.split_at(i);
178 s = v[1..].trim_start();
179 n.trim()
180 } else {
181 todo!()
182 };
183 let delim = if s.starts_with('"') {
184 s = &s[1..];
185 '"'
186 } else if s.starts_with('\'') {
187 s = &s[1..];
188 '\''
189 } else {
190 todo!()
191 };
192 let val = if let Some(i) = s.find(delim) {
193 let (v, r) = s.split_at(i);
194 s = r[1..].trim_start();
195 v.trim()
196 } else {
197 todo!()
198 };
199 top.insert(key.to_string(), val.to_string());
200 }
201 Ok(())
202}
203
204fn parse_annotations(
205 orig: &str,
206) -> Res<(
207 VecMap<String, String>,
208 VecMap<String, String>,
209 VecSet<String>,
210)> {
211 let mut attrs = VecMap::default();
212 let mut styles = VecMap::default();
213 let mut classes = VecSet::default();
214 let mut s = orig.trim();
215 while !s.is_empty() {
216 let style = if s.starts_with("style:") {
217 s = &s[6..];
218 Some(true)
219 } else if s.starts_with("class:") {
220 s = &s[6..];
221 Some(false)
222 } else {
223 None
224 };
225 let key = if let Some(i) = s.find('=') {
226 let (n, v) = s.split_at(i);
227 s = v[1..].trim_start();
228 n.trim()
229 } else {
230 return Err(TeXError::General(format!("Invalid annotation: {orig}")));
231 };
232 let delim = if s.starts_with('"') {
233 s = &s[1..];
234 '"'
235 } else if s.starts_with('\'') {
236 s = &s[1..];
237 '\''
238 } else {
239 return Err(TeXError::General(format!("Invalid annotation: {orig}")));
240 };
241 let val = if let Some(i) = s.find(delim) {
242 let (v, r) = s.split_at(i);
243 s = r[1..].trim_start();
244 v.trim()
245 } else {
246 return Err(TeXError::General(format!("Invalid annotation: {orig}")));
247 };
248 if style == Some(true) {
249 styles.insert(key.to_string(), val.to_string());
250 } else if style == Some(false) {
251 if !val.is_empty() {
252 return Err(TeXError::General("Invalid class annotation".to_string()));
253 }
254 classes.insert(key.to_string());
255 } else {
256 attrs.insert(key.to_string(), val.to_string());
257 }
258 }
259 Ok((attrs, styles, classes))
260}
261
262fn node_begin(engine: Refs, token: CompactToken) -> Res<()> {
263 let start = engine.mouth.start_ref();
264 let mut tag = String::new();
265 engine.read_braced_string(true, true, &token, &mut tag)?;
266 let mut str = String::new();
267 engine.read_braced_string(true, true, &token, &mut str)?;
268 let (attrs, styles, classes) = parse_annotations(&str)?;
269 let node = RusTeXNode::AnnotBegin {
270 attrs,
271 styles,
272 start,
273 classes,
274 tag: Some(tag),
275 };
276 add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
277 Ok(())
278}
279fn invisible_begin(engine: Refs, _token: CompactToken) -> Res<()> {
280 let node = RusTeXNode::InvisibleBegin;
281 add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
282 Ok(())
283}
284fn invisible_end(engine: Refs, _token: CompactToken) -> Res<()> {
285 let node = RusTeXNode::InvisibleEnd;
286 add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
287 Ok(())
288}
289fn annot_begin(engine: Refs, token: CompactToken) -> Res<()> {
290 let start = engine.mouth.start_ref();
291 let mut str = String::new();
292 engine.read_braced_string(true, true, &token, &mut str)?;
293 let (attrs, styles, classes) = parse_annotations(&str)?;
294 let node = RusTeXNode::AnnotBegin {
295 attrs,
296 styles,
297 start,
298 classes,
299 tag: None,
300 };
301 add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
302 Ok(())
303}
304fn annot_end(engine: Refs, _token: CompactToken) -> Res<()> {
305 let node = RusTeXNode::AnnotEnd(engine.mouth.current_sourceref());
306 add_node!(RusTeXStomach;engine, VNode::Custom(node),HNode::Custom(node),MathNode::Custom(node));
307 Ok(())
308}
309
310pub const CLOSE_FONT: &str = "!\"$%&/(closefont)\\&%$\"!";
311pub fn close_font(engine: Refs, _token: CompactToken) -> Res<()> {
312 match engine.stomach.data_mut().open_lists.last_mut() {
313 Some(NodeList::Vertical { children, .. }) => match children.last() {
314 Some(VNode::Custom(RusTeXNode::FontChange(_, _))) => {
315 children.pop();
316 }
317 _ => children.push(VNode::Custom(RusTeXNode::FontChangeEnd)),
318 },
319 Some(NodeList::Horizontal { children, .. }) => match children.last() {
320 Some(HNode::Custom(RusTeXNode::FontChange(_, _))) => {
321 children.pop();
322 }
323 _ => children.push(HNode::Custom(RusTeXNode::FontChangeEnd)),
324 },
325 Some(NodeList::Math { children, .. }) => match children.list_mut().last() {
326 Some(MathNode::Custom(RusTeXNode::FontChange(_, _))) => {
327 children.list_mut().pop();
328 }
329 _ => children.push(MathNode::Custom(RusTeXNode::FontChangeEnd)),
330 },
331 _ => engine
332 .stomach
333 .data_mut()
334 .page
335 .push(VNode::Custom(RusTeXNode::FontChangeEnd)),
336 }
337 Ok(())
338}
339
340fn underbrace(engine: Refs, token: CompactToken) -> Res<()> {
343 const LITERAL: &str = "<mo stretchy=\"true\">⏟</mo>";
344 engine.read_char_or_math_group(
346 &token,
347 |(), engine, char| {
348 let node = MathNode::Atom(MathAtom {
349 sup: None,
350 sub: Some(Box::new([MathNode::Custom(RusTeXNode::Literal(
351 LITERAL.to_string(),
352 ))])),
353 nucleus: MathNucleus::Simple {
354 cls: char.cls,
355 kernel: MathKernel::Char {
356 char: char.char,
357 style: char.style,
358 },
359 limits: Some(true),
360 },
361 });
362 let node = MathNode::Atom(MathAtom {
363 sup: None,
364 sub: None,
365 nucleus: MathNucleus::Simple {
366 cls: MathClass::Ord,
367 limits: Some(true),
368 kernel: MathKernel::List {
369 start: engine.mouth.current_sourceref(),
370 end: engine.mouth.current_sourceref(),
371 children: Box::new([node]),
372 },
373 },
374 });
375 RusTeXStomach::add_node_m(engine, node);
377 Ok(())
378 },
379 |()| {
380 ListTarget::<Types, _>::new(|engine, mut children, rf| {
381 children.insert(0, MathNode::Marker(UnresolvedMarkers::Display));
382 let node = MathNode::Atom(MathAtom {
383 sup: None,
384 sub: Some(Box::new([MathNode::Custom(RusTeXNode::Literal(
385 LITERAL.to_string(),
386 ))])),
387 nucleus: MathNucleus::Simple {
388 cls: MathClass::Ord,
389 limits: Some(true),
390 kernel: MathKernel::List {
391 start: rf,
392 children: children.into(),
393 end: engine.mouth.current_sourceref(),
394 },
395 },
396 });
397 let node = MathNode::Atom(MathAtom {
398 sup: None,
399 sub: None,
400 nucleus: MathNucleus::Simple {
401 cls: MathClass::Ord,
402 limits: Some(true),
403 kernel: MathKernel::List {
404 start: engine.mouth.current_sourceref(),
405 end: engine.mouth.current_sourceref(),
406 children: Box::new([node]),
407 },
408 },
409 });
410 RusTeXStomach::add_node_m(engine, node);
411 Ok(())
412 })
413 },
414 (),
415 )
416}
417fn overbrace(engine: Refs, token: CompactToken) -> Res<()> {
418 const LITERAL: &str = "<mo stretchy=\"true\">⏞</mo>";
419 RusTeXStomach::add_node_m(engine, MathNode::Marker(UnresolvedMarkers::Display));
420 engine.read_char_or_math_group(
421 &token,
422 |(), engine, char| {
423 let node = MathNode::Atom(MathAtom {
424 sub: None,
425 sup: Some(Box::new([MathNode::Custom(RusTeXNode::Literal(
426 LITERAL.to_string(),
427 ))])),
428 nucleus: MathNucleus::Simple {
429 cls: char.cls,
430 kernel: MathKernel::Char {
431 char: char.char,
432 style: char.style,
433 },
434 limits: Some(true),
435 },
436 });
437 let node = MathNode::Atom(MathAtom {
438 sup: None,
439 sub: None,
440 nucleus: MathNucleus::Simple {
441 cls: MathClass::Ord,
442 limits: Some(true),
443 kernel: MathKernel::List {
444 start: engine.mouth.current_sourceref(),
445 end: engine.mouth.current_sourceref(),
446 children: Box::new([node]),
447 },
448 },
449 });
450 RusTeXStomach::add_node_m(engine, node);
451 Ok(())
452 },
453 |()| {
454 ListTarget::<Types, _>::new(|engine, mut children, rf| {
455 children.insert(0, MathNode::Marker(UnresolvedMarkers::Display));
456 let node = MathNode::Atom(MathAtom {
457 sub: None,
458 sup: Some(Box::new([MathNode::Custom(RusTeXNode::Literal(
459 LITERAL.to_string(),
460 ))])),
461 nucleus: MathNucleus::Simple {
462 cls: MathClass::Ord,
463 limits: Some(true),
464 kernel: MathKernel::List {
465 start: rf,
466 children: children.into(),
467 end: engine.mouth.current_sourceref(),
468 },
469 },
470 });
471 let node = MathNode::Atom(MathAtom {
472 sup: None,
473 sub: None,
474 nucleus: MathNucleus::Simple {
475 cls: MathClass::Ord,
476 limits: Some(true),
477 kernel: MathKernel::List {
478 start: engine.mouth.current_sourceref(),
479 end: engine.mouth.current_sourceref(),
480 children: Box::new([node]),
481 },
482 },
483 });
484 RusTeXStomach::add_node_m(engine, node);
485 Ok(())
486 })
487 },
488 (),
489 )
490}