1use std::{collections::VecDeque, rc::Rc};
12
13use crate::{
14 BoxOps,
15 binding::{
16 content::{build_invocation, digest_literal, digest_text},
17 def::dialect::{RegisterOptions, def_macro, def_register, is_defined},
18 },
19 common::{
20 arena::{self, SymHashMap as HashMap, SymStr},
21 cleaners::{clean_id, clean_label, roman_aux},
22 error::*,
23 number::Number,
24 numeric_ops::NumericOps,
25 },
26 definition::{Definition, ExpansionBody, expandable::ExpandableOptions},
27 mouth, state,
28 state::*,
29 stomach,
30 token::*,
31 tokens::{TeXString, Tokens},
32 whatsit::Whatsit,
33};
34
35#[derive(Default)]
37pub struct NewCounterOptions<'ct> {
38 pub idprefix: &'ct str,
41 pub idwithin: &'ct str,
44 pub nested: Vec<&'ct str>,
50}
51
52pub fn new_counter(ctr: &str, within: &str, options_opt: Option<NewCounterOptions>) -> Result<()> {
55 let unctr = s!("UN{ctr}"); if !within.is_empty()
57 && within != "document"
58 && lookup_definition(&T_CS!(s!("\\c@{within}")))?.is_none()
59 {
60 new_counter(within, "", None)?;
61 }
62 let cctr = s!("\\c@{ctr}");
63 let clctr = s!("\\cl@{ctr}");
64 let cunctr = s!("\\c@{unctr}");
65 let clunctr = s!("\\cl@{unctr}");
66 let cs_cctr = T_CS!(&cctr);
69 let prev_defn = lookup_definition(&cs_cctr)?;
70 if let Some(ref defn) = prev_defn {
71 if defn.is_register() {
72 } else {
74 let relax_meaning = lookup_meaning(&T_RELAX!());
76 let prev_meaning = lookup_meaning(&cs_cctr);
77 if prev_meaning != relax_meaning {
78 Warn!(
79 "unexpected",
80 &cctr,
81 s!("Counter {} was already defined; redefining", cctr)
82 );
83 }
84 def_register(
85 cs_cctr,
86 None,
87 Number::new(0),
88 Some(RegisterOptions {
89 allocate: Some(String::from("\\count")),
90 ..RegisterOptions::default()
91 }),
92 )?;
93 }
94 } else {
95 def_register(
96 cs_cctr,
97 None,
98 Number::new(0),
99 Some(RegisterOptions {
100 allocate: Some(String::from("\\count")),
101 ..RegisterOptions::default()
102 }),
103 )?;
104 }
105 after_assignment();
106 if !has_value(&clctr) {
107 assign_value(&clctr, Tokens!(), Some(Scope::Global));
108 }
109 def_register(T_CS!(&cunctr), None, Number::new(0), None)?;
110 if !has_value(&clunctr) {
111 assign_value(&clunctr, Tokens!(), Some(Scope::Global));
112 }
113
114 if !within.is_empty() {
115 let clwithin = s!("\\cl@{within}");
116 let clunwithin = s!("\\cl@UN{within}");
117 let x = if let Some(cl) = lookup_tokens(&clwithin) {
118 cl.unlist()
119 } else {
120 Vec::new()
121 };
122 let mut clwithin_tokens = vec![T_CS!(ctr), T_CS!(&unctr)];
123 clwithin_tokens.extend(x);
124 assign_value(
125 &clwithin,
126 Stored::Tokens(Tokens::new(clwithin_tokens)),
127 Some(Scope::Global),
128 );
129
130 let mut unx = if let Some(clun) = lookup_tokens(&clunwithin) {
131 clun.unlist()
132 } else {
133 Vec::new()
134 };
135 let mut clunwithin_tokens = vec![T_CS!(unctr)];
136 clunwithin_tokens.append(&mut unx);
137
138 assign_value(
139 &clunwithin,
140 Stored::Tokens(Tokens::new(clunwithin_tokens)),
141 Some(Scope::Global),
142 )
143 }
144
145 if let Some(ref options) = options_opt
146 && !options.nested.is_empty()
147 {
148 assign_value(
149 &s!("nested_counters_{}", ctr),
150 options.nested.clone(),
151 Some(Scope::Global),
152 )
153 }
154
155 let ctr_string = ctr.to_string();
157 def_macro(
158 T_CS!(s!("\\the{}", ctr)),
159 None,
160 Some(ExpansionBody::Closure(Rc::new(move |_args| {
161 let counter_value = counter_value(&ctr_string)?.value_of();
162 Ok(Tokens::new(ExplodeText!(counter_value)))
163 }))),
164 Some(ExpandableOptions {
165 scope: Some(Scope::Global),
166 ..ExpandableOptions::default()
167 }),
168 )?;
169 let p_ctr_cs = T_CS!(&s!("\\p@{}", ctr));
170 if lookup_definition(&p_ctr_cs)?.is_none() {
171 def_macro(
172 p_ctr_cs,
173 None,
174 Tokens::default(),
175 Some(ExpandableOptions {
176 scope: Some(Scope::Global),
177 ..ExpandableOptions::default()
178 }),
179 )?;
180 }
181
182 let mut prefix = match options_opt {
183 None => String::new(),
184 Some(ref opt) => opt.idprefix.to_string(),
185 };
186 if !prefix.is_empty() {
187 assign_value(
188 &s!("@ID@prefix@{}", ctr),
189 prefix.clone(),
190 Some(Scope::Global),
191 );
192 } else {
193 prefix = lookup_string(&s!("@ID@prefix@{}", ctr));
194 if prefix.is_empty() {
195 prefix = ctr.to_string();
196 }
197 }
198 prefix = clean_id(&prefix);
199
200 if !prefix.is_empty() {
201 let idwithin = match options_opt {
202 Some(ref opts) => {
203 if opts.idwithin.is_empty() {
204 within
205 } else {
206 opts.idwithin
207 }
208 },
209 None => within,
210 }
211 .to_string();
212
213 let ctr_string = ctr.to_string();
214 let thectrid = s!("\\the{}@ID", ctr);
215 if !idwithin.is_empty() {
216 def_macro(
217 T_CS!(thectrid),
218 None,
219 Some(ExpansionBody::Closure(Rc::new(move |_args| {
220 Ok(mouth::tokenize_internal(TeXString::assembled(s!(
234 "\\expandafter\\ifx\\csname the{}@ID\\endcsname\\lx@empty\\else\\csname the{}@ID\\endcsname.\\fi {}\\csname @{}@ID\\endcsname",
235 idwithin,
236 idwithin,
237 prefix,
238 ctr_string
239 ))))
240 }))),
241 Some(ExpandableOptions {
242 scope: Some(Scope::Global),
243 ..ExpandableOptions::default()
244 }),
245 )?;
246 } else {
247 def_macro(
248 T_CS!(thectrid),
249 None,
250 Some(ExpansionBody::Closure(Rc::new(move |_args| {
251 Ok(mouth::tokenize_internal(TeXString::assembled(s!(
252 "{prefix}\\csname @{ctr_string}@ID\\endcsname",
253 ))))
254 }))),
255 Some(ExpandableOptions {
256 scope: Some(Scope::Global),
257 ..ExpandableOptions::default()
258 }),
259 )?;
260 }
261 def_macro(
262 T_CS!(s!("\\@{}@ID", ctr)),
263 None,
264 Some(ExpansionBody::Tokens(Tokens!(T_OTHER!("0")))),
265 Some(ExpandableOptions {
266 scope: Some(Scope::Global),
267 ..ExpandableOptions::default()
268 }),
269 )?;
270 }
271
272 Ok(())
273}
274pub fn counter_value(ctr: &str) -> Result<Number> {
276 match lookup_register(&s!("\\c@{ctr}"), Vec::new())? {
277 None => {
278 let message = s!("Counter '{}' was not defined; assuming 0", ctr);
280 Warn!("undefined", ctr, message);
281 Ok(Number::new(0))
282 },
283 Some(value) => Ok(Number::new(value.value_of())),
284 }
285}
286pub fn add_to_counter(ctr: &str, value: Number) -> Result<()> {
288 let v = counter_value(ctr)?.add(value);
289 assign_register(&s!("\\c@{ctr}"), v.into(), Some(Scope::Global), Vec::new())?;
290 after_assignment();
291 let id_cs = T_CS!(s!("\\@{ctr}@ID"));
292 def_macro(
293 id_cs,
294 None,
295 Tokens::new(Explode!(v.value_of())),
296 Some(ExpandableOptions {
297 scope: Some(Scope::Global),
298 ..ExpandableOptions::default()
299 }),
300 )
301}
302
303pub fn step_counter(ctr: &str, noreset: bool) -> Result<()> {
306 let value = counter_value(ctr)?;
307 let newvalue = value.add(Number::new(1));
308 let c_ctr = s!("\\c@{ctr}");
309 assign_register(&c_ctr, newvalue.into(), Some(Scope::Global), Vec::new())?;
310 after_assignment();
311 let token_value = Tokens::new(Explode!(newvalue.value_of()));
312 def_macro(
313 T_CS!(s!("\\@{ctr}@ID")),
314 None,
315 token_value,
316 Some(ExpandableOptions {
317 scope: Some(Scope::Global),
318 ..ExpandableOptions::default()
319 }),
320 )?;
321
322 if !noreset && let Some(nested) = lookup_tokens(&s!("\\cl@{ctr}")) {
324 for c in nested.unlist() {
325 reset_counter(&c)?;
326 }
327 }
328 Ok(())
329}
330
331pub fn ref_step_counter(ctype: &str, noreset: bool) -> Result<HashMap<Stored>> {
340 let ctype = {
347 let mut s = ctype;
348 for tail in ["\\par", "\\@startsection@hook", "\\relax"] {
349 if let Some(stripped) = s.strip_suffix(tail) {
350 s = stripped;
351 break;
352 }
353 }
354 s
355 };
356 let ctr = with_mapping("counter_for_type", ctype, |meaning| match meaning {
357 Some(Stored::String(ctr)) => arena::to_string(*ctr),
358 _ => ctype.to_string(),
359 });
360 step_counter(&ctr, noreset)?;
361 maybe_preempt_refnum(&ctr, false);
362
363 let the_ctr_id = s!("\\the{ctr}@ID");
364 let the_ctr = s!("\\the{ctr}");
365
366 let has_id: bool = match lookup_definition(&T_CS!(&the_ctr_id))? {
367 Some(iddef) => {
368 if let Some(params) = iddef.get_parameters() {
369 params.get_num_args() == 0
370 } else {
371 true
372 }
373 },
374 _ => false,
375 };
376
377 let the_ctr_cs = T_CS!(&the_ctr);
378 let the_ctr_id_cs = T_CS!(&the_ctr_id);
379 def_macro(
380 T_CS!("\\@currentlabel"),
381 None,
382 the_ctr_cs,
383 Some(ExpandableOptions {
384 scope: Some(Scope::Global),
385 ..ExpandableOptions::default()
386 }),
387 )?;
388 if has_id {
389 def_macro(
390 T_CS!("\\@currentID"),
391 None,
392 the_ctr_id_cs,
393 Some(ExpandableOptions {
394 scope: Some(Scope::Global),
395 ..ExpandableOptions::default()
396 }),
397 )?;
398 }
399
400 let id = if has_id {
401 digest_literal(Tokens!(T_CS!(&the_ctr_id)))?.to_string()
402 } else {
403 String::new()
404 };
405
406 let refnum = digest_text(Tokens!(T_CS!(&the_ctr)))?;
407 let invocation;
408 {
409 invocation = build_invocation(T_CS!("\\lx@make@tags"), vec![Some(Tokens!(T_OTHER!(
410 ctype
411 )))])?;
412 }
413
414 let tags = stomach::digest(invocation)?;
415
416 deactivate_counter_scope(arena::pin(&ctr));
419
420 assign_value("current_counter", ctr.clone(), Some(Scope::Local));
422
423 let scope = arena::pin(format!("{ctr}:{refnum}"));
424 let mut receiver = VecDeque::new();
425 receiver.push_front(Stored::String(scope));
426 assign_value(
427 &s!("scopes_for_counter:{ctr}"),
428 receiver,
429 Some(Scope::Local),
430 );
431 activate_scope(scope);
432
433 Ok(stored_map!(
434 "tags" => Stored::Digested(tags),
435 "id" => Stored::String(arena::pin(id))
436 ))
437}
438
439fn maybe_preempt_refnum(ctr: &str, norefnum: bool) {
448 if let Some(mapper) = get_label_mapping_hook() {
449 let hj_refnum = T_CS!(s!("\\_PREEMPTED_REFNUM_{ctr}"));
450 let hj_id = T_CS!(s!("\\_PREEMPTED_ID_{ctr}"));
451 if !norefnum && has_meaning(&hj_refnum) {
453 let_i(&T_CS!(s!("\\the{ctr}")), &hj_refnum, Some(Scope::Global));
454 }
455 if has_meaning(&hj_id) {
456 let_i(&T_CS!(s!("\\the{ctr}@ID")), &hj_id, Some(Scope::Global));
457 }
458 let label = lookup_string("PEEKED_LABEL");
459 let (fixedrefnum, fixedid) = mapper(&label, ctr, norefnum);
460 if let Some(refnum) = fixedrefnum
461 && !norefnum
462 {
463 if !has_meaning(&hj_refnum) {
464 let_i(&hj_refnum, &T_CS!(s!("\\the{ctr}")), Some(Scope::Global));
466 }
467 let _ = def_macro(
468 T_CS!(s!("\\the{ctr}")),
469 None,
470 ExpansionBody::Tokens(Tokens::new(Explode!(&refnum))),
471 Some(ExpandableOptions {
472 scope: Some(Scope::Global),
473 ..Default::default()
474 }),
475 );
476 }
477 if let Some(id) = fixedid {
478 if !has_meaning(&hj_id) {
479 let_i(&hj_id, &T_CS!(s!("\\the{ctr}@ID")), Some(Scope::Global));
481 }
482 let _ = def_macro(
483 T_CS!(s!("\\the{ctr}@ID")),
484 None,
485 ExpansionBody::Tokens(Tokens::new(Explode!(&id))),
486 Some(ExpandableOptions {
487 scope: Some(Scope::Global),
488 ..Default::default()
489 }),
490 );
491 }
492 remove_value("PEEKED_LABEL"); assign_value(
494 "PROCESSED_LABEL",
495 Stored::String(arena::pin(label)),
496 Some(Scope::Global),
497 );
498 }
499}
500
501pub fn maybe_peek_label() -> Result<()> {
504 if get_label_mapping_hook().is_some() {
505 let peek = crate::gullet::read_non_space()?;
506 if let Some(ref token) = peek {
507 if x_equals(token, &T_CS!("\\label")) {
508 begin_semiverbatim(None);
509 let arg = crate::gullet::read_arg(crate::gullet::ExpansionLevel::Off)?;
510 end_semiverbatim()?;
511 let arg_str = arg.to_string();
512 let label = clean_label(&arg_str, Some("")).into_owned();
513 assign_value(
514 "PEEKED_LABEL",
515 Stored::String(arena::pin(&label)),
516 Some(Scope::Global),
517 );
518 crate::gullet::unread(Tokens!(T_BEGIN!(), arg, T_END!()));
520 } else {
521 remove_value("PROCESSED_LABEL");
522 remove_value("PEEKED_LABEL");
523 }
524 }
525 if let Some(token) = peek {
526 crate::gullet::unread_one(token);
527 }
528 }
529 Ok(())
530}
531
532pub fn maybe_note_label(label: &str) {
536 if get_label_mapping_hook().is_some() {
537 let label = clean_label(label, Some(""));
538 let processed = lookup_string("PROCESSED_LABEL");
539 if processed.is_empty() || processed != label {
540 remove_value("PROCESSED_LABEL");
542 assign_value(
543 "PEEKED_LABEL",
544 Stored::String(arena::pin(label)),
545 Some(Scope::Global),
546 );
547 }
548 }
549}
550
551fn deactivate_counter_scope(ctr: SymStr) {
552 let (scopes_for_counter, nested_counters) = arena::with(ctr, |cstr| {
553 (
554 s!("scopes_for_counter:{cstr}"),
555 s!("nested_counters_{cstr}"),
556 )
557 });
558 let scope_syms: Vec<SymStr> = with_value(&scopes_for_counter, |v| match v {
562 Some(Stored::VecDequeStored(stored_scopes)) => stored_scopes
563 .iter()
564 .map(|s| match s {
565 Stored::String(scope) => *scope,
566 _ => panic!("assignment scopes should be stored as strings, got: {s:?}"),
567 })
568 .collect(),
569 _ => Vec::new(),
570 });
571 for scope in scope_syms {
572 deactivate_scope(scope);
573 }
574
575 let inner_ctrs: Vec<SymStr> = with_value(&nested_counters, |v| match v {
578 Some(Stored::Strings(stored_counters)) => stored_counters.iter().copied().collect(),
579 _ => Vec::new(),
580 });
581 for inner_ctr in inner_ctrs {
582 deactivate_counter_scope(inner_ctr);
583 }
584}
585
586pub fn ref_step_id(ctype: &str) -> Result<HashMap<Stored>> {
591 let ctr = with_mapping("counter_for_type", ctype, |mapping| match mapping {
592 Some(map) => map.to_string(),
593 None => ctype.to_string(),
594 });
595 let unctr = s!("UN{ctr}");
596 let unctr_cmd = s!("\\c@{unctr}");
603 let unctr_defined = lookup_register(&unctr_cmd, Vec::new())
604 .ok()
605 .flatten()
606 .is_some();
607 if !unctr_defined {
608 let _ = new_counter(&ctr, "document", None);
609 }
610 step_counter(&unctr, false)?;
611 maybe_preempt_refnum(&ctr, true);
612 let cunctr_val = lookup_number(&s!("\\c@{unctr}"))
613 .unwrap_or_default()
614 .value_of();
615 def_macro(
616 T_CS!(s!("\\@{ctr}@ID")),
617 None,
618 Tokens!(T_OTHER!("x"), Explode!(cunctr_val)),
619 Some(ExpandableOptions {
620 scope: Some(Scope::Global),
621 ..ExpandableOptions::default()
622 }),
623 )?;
624
625 let the_ctr_id = s!("\\the{ctr}@ID");
626 def_macro(T_CS!("\\@currentID"), None, T_CS!(&the_ctr_id), None)?;
627 Ok(stored_map!("id" =>
628 clean_id(&digest_literal(T_CS!(the_ctr_id))?.to_string())))
629}
630
631pub fn ref_current_id(ctype: &str) -> Result<HashMap<Stored>> {
634 let ctr = with_mapping("counter_for_type", ctype, |mapping| match mapping {
635 Some(map) => map.to_string(),
636 None => ctype.to_string(),
637 });
638 let the_ctr_id = s!("\\the{ctr}@ID");
639 let id = clean_id(&digest_literal(T_CS!(the_ctr_id))?.to_string());
640 Ok(stored_map!("id" => id))
641}
642
643pub fn reset_counter(ctr: &Token) -> Result<()> {
645 let (c_ctr, c_un_ctr, ctr_id) =
646 ctr.with_str(|ctr| (s!("\\c@{ctr}"), s!("\\c@UN{ctr}"), s!("\\@{ctr}@ID")));
647 assign_register(
648 &c_ctr,
649 Number::new(0).into(),
650 Some(Scope::Global),
651 Vec::new(),
652 )?;
653 if !ctr.with_str(|cstr| cstr.starts_with("UN")) {
654 assign_register(
656 &c_un_ctr,
657 Number::new(0).into(),
658 Some(Scope::Global),
659 Vec::new(),
660 )?;
661 }
662 def_macro(
663 T_CS!(ctr_id),
664 None,
665 Tokens!(T_OTHER!("0")),
666 Some(ExpandableOptions {
667 scope: Some(Scope::Global),
668 ..ExpandableOptions::default()
669 }),
670 )?;
671 if let Some(nested) = lookup_tokens(&s!("\\cl@{ctr}")) {
673 for c in nested.unlist() {
674 reset_counter(&c)?;
675 }
676 }
677 Ok(())
678}
679
680pub fn ref_step_item_counter(tag_opt: Option<&Tokens>) -> Result<HashMap<Stored>> {
682 let counter = lookup_string("itemcounter");
683 let n = lookup_int("itemization_items");
684 assign_value("itemization_items", n + 1, None);
685 let mut attr: HashMap<Stored> = HashMap::default();
686 if n > 0
687 && let Some(sep) = lookup_dimension("\\itemsep")
688 {
689 let default_opt = lookup_dimension("\\lx@default@itemsep");
690 if default_opt.is_none() || sep.value_of() != default_opt.unwrap().value_of() {
691 attr.insert("itemsep", sep.into());
692 }
693 }
694
695 let mut result = if let Some(tag) = tag_opt {
696 let mut props = ref_step_id(&counter)?;
697 if tag.is_empty() {
698 return Ok(props);
699 }
700 let formatter = if counter.starts_with("@desc") {
701 T_CS!("\\descriptionlabel")
702 } else {
703 T_CS!("\\makelabel")
704 };
705 let counter_name = s!("\\{counter}name");
706 let typename = if is_defined(&counter_name) {
707 T_CS!(counter_name)
708 } else {
709 T_CS!("\\itemtyperefname")
710 };
711
712 let mut tag_tokens = vec![
713 T_BEGIN!(),
714 T_CS!("\\let"),
715 T_CS!(s!("\\the{counter}")),
716 T_CS!("\\@empty"),
717 T_CS!("\\def"),
718 T_CS!(s!("\\fnum@{counter}")),
719 T_BEGIN!(),
720 formatter,
721 T_BEGIN!(),
722 ];
723 let reverted_tag = (*tag).clone().revert();
725 tag_tokens.extend(reverted_tag.clone());
726 tag_tokens.extend(vec![
727 T_END!(),
728 T_END!(),
729 T_CS!("\\def"),
730 T_CS!(s!("\\typerefnum@{counter}")),
731 T_BEGIN!(),
732 typename,
733 T_SPACE!(),
734 ]);
735 tag_tokens.extend(reverted_tag);
736 tag_tokens.push(T_END!());
737 tag_tokens.extend(
738 build_invocation(T_CS!("\\lx@make@tags"), vec![Some(Tokens!(T_OTHER!(
739 counter
740 )))])?
741 .unlist(),
742 );
743 tag_tokens.push(T_END!());
744
745 let tags = stomach::digest(tag_tokens)?;
746 if !tags.is_empty()? {
747 props.insert("tags", tags.into());
748 }
749 props
750 } else {
751 ref_step_counter(&counter, false)?
752 };
753 for (k, v) in attr.into_iter() {
754 result.insert_sym(k, v);
755 }
756 Ok(result)
757}
758
759#[derive(Debug, Default, Clone)]
761pub struct BeginItemizeOptions {
762 pub nolevel: bool,
764 pub series: Option<Tokens>,
766 pub start: Option<Number>,
768 pub resume: Option<String>,
770 pub resume_star: Option<String>,
772}
773
774pub fn begin_itemize(
778 itype: &str,
779 counter: Option<&str>,
780 options: BeginItemizeOptions,
781) -> Result<HashMap<Stored>> {
782 let outercounter = lookup_string("itemcounter");
784 let outerlevel = if !outercounter.is_empty() {
785 lookup_int(&s!("{outercounter}level"))
786 } else {
787 0
788 };
789 let counter = counter.unwrap_or("@item");
790 let listlevel = lookup_int("itemization_level") + 1; let level = lookup_int(&s!("{counter}level")) + (if options.nolevel { 0 } else { 1 });
793 AssignRegister!(
794 "\\itemsep",
795 lookup_dimension("\\lx@default@itemsep")
796 .unwrap_or_default()
797 .into()
798 );
799 assign_value("itemization_level", listlevel, None);
800 assign_value(&s!("{counter}level"), level, None);
801 assign_value("itemization_items", 0, None);
802 let listpostfix = roman!(listlevel).to_string();
803 let postfix = roman!(level).to_string();
804 let mut usecounter = counter.to_string();
805 if !options.nolevel && !postfix.is_empty() {
806 usecounter.push_str(&postfix);
807 }
808 if !itype.is_empty() {
809 let itype_cs = T_CS!(s!("\\{itype}@item"));
810 let_i(&T_CS!("\\item"), &itype_cs, None);
811 }
812 let_i(&T_CS!("\\par"), &T_CS!("\\lx@normal@par"), None);
814 def_macro(
815 T_CS!("\\@listctr"),
816 None,
817 Tokens!(Explode!(usecounter)),
818 None,
819 )?;
820 assign_value("itemcounter", Stored::String(arena::pin(&usecounter)), None);
823 let listcounter = s!("@itemize{listpostfix}");
824 if lookup_definition(&T_CS!(s!("\\c@{listcounter}")))?.is_none() {
825 new_counter(&listcounter, "", None)?;
827 }
828 if !outercounter.is_empty() {
829 let outerusecounter = s!("{outercounter}{}", roman!(outerlevel).to_string());
831 let thectr = s!("\\the{listcounter}@ID");
832 let theexpansion = s!("\\the{outerusecounter}@ID.I\\arabic{{{listcounter}}}");
833 def_macro(
834 T_CS!(thectr),
835 None,
836 mouth::tokenize_internal(TeXString::assembled(theexpansion)),
837 None,
838 )?;
839
840 let mut cl_toks = vec![T_CS!(&listcounter)];
842 let cl_name = s!("\\cl@{outerusecounter}");
843 let existing = with_value(&cl_name, |v| match v {
844 Some(Stored::Tokens(tks)) => tks.clone().unlist(),
845 _ => Vec::new(),
846 });
847 cl_toks.extend(existing);
848 assign_value(
849 &cl_name,
850 Stored::Tokens(Tokens::new(cl_toks)),
851 Some(Scope::Global),
852 );
853 }
854 let useexp = Tokens::new(vec![
865 T_CS!(s!("\\the{listcounter}@ID")),
866 T_OTHER!(".i"),
867 T_CS!(s!("\\@{usecounter}@ID")),
868 ]);
869 def_macro(T_CS!(s!("\\the{usecounter}@ID")), None, useexp, None)?;
870
871 let mut series = if let Some(s) = options.series {
872 s.to_string()
873 } else {
874 String::new()
875 };
876 if let Some(start) = options.start {
877 SetCounter!(usecounter, start);
878 add_to_counter(&usecounter, Number(-1))?;
879 } else if let Some(s) = match options.resume {
880 Some(s) => Some(s),
881 None => options.resume_star,
882 } {
883 if s != "noseries" {
884 series = s.clone();
885 let last_val = lookup_int(&s!("enumitem_series_{s}_last"));
886 if last_val != 0 {
887 SetCounter!(usecounter, Number(last_val));
888 }
889 }
890 } else {
891 reset_counter(&T_OTHER!(&usecounter))?;
892 }
893
894 let mut rsc = ref_step_counter(&s!("@itemize{listpostfix}"), false)?;
895 rsc.insert("counter", usecounter.into());
896 rsc.insert("series", series.into());
897 let pad = lookup_dimension_cs("\\topsep", false)
903 .unwrap_or_default()
904 .add(lookup_dimension_cs("\\parskip", false).unwrap_or_default())
905 .add(lookup_dimension_cs("\\partopsep", false).unwrap_or_default());
906 rsc.insert("padtop", Stored::Dimension(pad));
907 rsc.insert("padbottom", Stored::Dimension(pad));
908 Ok(rsc)
909}
910
911pub fn set_itemization_style(stuff: Option<&Tokens>, level: Option<i32>) -> Result<()> {
916 if let Some(stuff) = stuff {
917 if stuff.is_empty() {
918 return Ok(());
919 }
920 let level = level.unwrap_or_else(|| lookup_int("@itemlevel").max(0) as i32);
921 let level_str = roman_aux(level);
922 let cs_name = s!("\\labelitem{level_str}");
923 def_macro(T_CS!(&cs_name), None, stuff.clone(), None)?;
924 }
925 Ok(())
926}
927
928pub fn set_enumeration_style(stuff: Option<&Tokens>, level: Option<i32>) -> Result<()> {
933 if let Some(stuff) = stuff {
934 if stuff.is_empty() {
935 return Ok(());
936 }
937 let level = level.unwrap_or_else(|| lookup_int("enumlevel").max(0) as i32);
938 let level_str = roman_aux(level);
939 let tokens = stuff.unlist_ref();
941 let mut out: Vec<Token> = Vec::with_capacity(tokens.len());
942 let ctr = T_OTHER!(s!("enum{level_str}"));
943 let mut i = 0;
944 while i < tokens.len() {
945 let t = tokens[i];
946 if t.get_catcode() == Catcode::BEGIN {
947 out.push(t);
949 let mut brlevel = 1i32;
950 i += 1;
951 while brlevel > 0 && i < tokens.len() {
952 let tt = tokens[i];
953 if tt.get_catcode() == Catcode::BEGIN {
954 brlevel += 1;
955 } else if tt.get_catcode() == Catcode::END {
956 brlevel -= 1;
957 }
958 out.push(tt);
959 i += 1;
960 }
961 } else {
962 let ch = char::from_u32(t.get_charcode()).unwrap_or('\0');
963 let cat = t.get_catcode();
964 match (ch, cat) {
965 ('A', Catcode::LETTER) => {
966 def_macro(
968 T_CS!(s!("\\theenum{level_str}")),
969 None,
970 Tokens::new(vec![T_CS!("\\Alph"), T_BEGIN!(), ctr, T_END!()]),
971 None,
972 )?;
973 out.push(T_CS!(s!("\\theenum{level_str}")));
974 },
975 ('a', Catcode::LETTER) => {
976 def_macro(
978 T_CS!(s!("\\theenum{level_str}")),
979 None,
980 Tokens::new(vec![T_CS!("\\alph"), T_BEGIN!(), ctr, T_END!()]),
981 None,
982 )?;
983 out.push(T_CS!(s!("\\theenum{level_str}")));
984 },
985 ('I', Catcode::LETTER) => {
986 def_macro(
988 T_CS!(s!("\\theenum{level_str}")),
989 None,
990 Tokens::new(vec![T_CS!("\\Roman"), T_BEGIN!(), ctr, T_END!()]),
991 None,
992 )?;
993 out.push(T_CS!(s!("\\theenum{level_str}")));
994 },
995 ('i', Catcode::LETTER) => {
996 def_macro(
998 T_CS!(s!("\\theenum{level_str}")),
999 None,
1000 Tokens::new(vec![T_CS!("\\roman"), T_BEGIN!(), ctr, T_END!()]),
1001 None,
1002 )?;
1003 out.push(T_CS!(s!("\\theenum{level_str}")));
1004 },
1005 ('1', Catcode::OTHER) => {
1006 def_macro(
1008 T_CS!(s!("\\theenum{level_str}")),
1009 None,
1010 Tokens::new(vec![T_CS!("\\arabic"), T_BEGIN!(), ctr, T_END!()]),
1011 None,
1012 )?;
1013 out.push(T_CS!(s!("\\theenum{level_str}")));
1014 },
1015 _ => {
1016 out.push(t);
1017 },
1018 }
1019 i += 1;
1020 }
1021 }
1022 let mut label_tokens = vec![T_BEGIN!()];
1024 label_tokens.extend(out);
1025 label_tokens.push(T_END!());
1026 def_macro(
1027 T_CS!(s!("\\labelenum{level_str}")),
1028 None,
1029 Tokens::new(label_tokens),
1030 None,
1031 )?;
1032 }
1033 Ok(())
1034}
1035
1036pub fn rescue_caption_counters(captype: &str, whatsit: &mut Whatsit) {
1039 let tagskey = &s!("{captype}_tags");
1040 if let Some(tags) = remove_value(tagskey) {
1041 whatsit.set_property("tags", tags);
1042 }
1043 let idkey = s!("{captype}_id");
1044 if let Some(id) = remove_value(&idkey) {
1045 whatsit.set_property("id", id);
1046 }
1047 let inlistkey = s!("{captype}_inlist");
1048 if let Some(inlist) = remove_value(&inlistkey) {
1049 whatsit.set_property("inlist", inlist);
1050 }
1051}