ftml_viewer_components/components/
counters.rs1use flams_ontology::{
2 narration::{paragraphs::ParagraphKind, sections::SectionLevel},
3 uris::{DocumentURI, Name},
4};
5use flams_utils::{
6 impossible, unwrap,
7 vecmap::{VecMap, VecSet},
8};
9use leptos::prelude::*;
10use smallvec::SmallVec;
11
12use crate::extractor::DOMExtractor;
13
14use super::{TOCElem, TOCIter};
15
16#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
17pub enum LogicalLevel {
18 None,
19 Section(SectionLevel),
20 Paragraph,
21 BeamerSlide,
22}
23
24trait CounterTrait:
25 Copy
26 + PartialEq
27 + std::ops::Add<Self, Output = Self>
28 + std::ops::AddAssign<Self>
29 + Default
30 + Clone
31 + Send
32 + Sync
33 + std::fmt::Debug
34 + std::fmt::Display
35 + 'static
36{
37 fn one() -> Self;
38}
39impl CounterTrait for u16 {
40 fn one() -> Self {
41 1
42 }
43}
44impl CounterTrait for u32 {
45 fn one() -> Self {
46 1
47 }
48}
49
50#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
51struct AllSections(pub [u16; 7]);
52impl std::fmt::Display for AllSections {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 write!(
55 f,
56 "[{} {} {} {} {} {} {}]",
57 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6]
58 )
59 }
60}
61
62impl std::ops::Add<SectionLevel> for AllSections {
63 type Output = Self;
64 fn add(self, rhs: SectionLevel) -> Self::Output {
65 let idx: u8 = rhs.into();
66 let mut s = AllSections::default();
67 s.0[idx as usize] = 1;
68 self + s
69 }
70}
71
72impl std::ops::Add<Self> for AllSections {
73 type Output = Self;
74 fn add(self, rhs: Self) -> Self::Output {
75 let mut changed = false;
76 let r = AllSections([
77 {
78 if rhs.0[0] > 0 {
79 changed = true
80 };
81 self.0[0] + rhs.0[0]
82 },
83 {
84 if rhs.0[1] > 0 {
85 changed = true
86 };
87 self.0[1] + rhs.0[1]
88 },
89 {
90 if changed {
91 0
92 } else {
93 if rhs.0[2] > 0 {
94 changed = true
95 }
96 self.0[2] + rhs.0[2]
97 }
98 },
99 {
100 if changed {
101 0
102 } else {
103 if rhs.0[3] > 0 {
104 changed = true
105 }
106 self.0[3] + rhs.0[3]
107 }
108 },
109 {
110 if changed {
111 0
112 } else {
113 if rhs.0[4] > 0 {
114 changed = true
115 }
116 self.0[4] + rhs.0[4]
117 }
118 },
119 {
120 if changed {
121 0
122 } else {
123 if rhs.0[5] > 0 {
124 changed = true
125 }
126 self.0[5] + rhs.0[5]
127 }
128 },
129 {
130 if changed {
131 0
132 } else {
133 self.0[6] + rhs.0[6]
134 }
135 },
136 ]);
137 r
139 }
140}
141
142impl std::ops::AddAssign<Self> for AllSections {
143 fn add_assign(&mut self, rhs: Self) {
144 let mut changed = false;
146 if rhs.0[0] > 0 {
147 changed = true
148 };
149 self.0[0] += rhs.0[0];
150 if rhs.0[1] > 0 {
151 changed = true;
152 }
153 self.0[1] += rhs.0[1];
154 if changed {
155 self.0[2] = 0
156 } else {
157 if rhs.0[2] > 0 {
158 changed = true;
159 }
160 self.0[2] += rhs.0[2];
161 }
162 if changed {
163 self.0[3] = 0
164 } else {
165 if rhs.0[3] > 0 {
166 changed = true;
167 }
168 self.0[3] += rhs.0[3];
169 }
170 if changed {
171 self.0[4] = 0
172 } else {
173 if rhs.0[4] > 0 {
174 changed = true;
175 }
176 self.0[4] += rhs.0[4];
177 }
178 if changed {
179 self.0[5] = 0
180 } else {
181 if rhs.0[5] > 0 {
182 changed = true;
183 }
184 self.0[5] += rhs.0[5];
185 }
186 if changed {
187 self.0[6] = 0
188 } else {
189 self.0[6] += rhs.0[6];
190 }
191 }
193}
194
195impl CounterTrait for AllSections {
196 fn one() -> Self {
197 panic!("That's not how sectioning works")
198 }
199}
200
201impl SmartCounter<AllSections> {
202 fn inc_at(&self, lvl: SectionLevel) {
203 let idx: u8 = lvl.into();
204 let mut s = AllSections::default();
205 s.0[idx as usize] = 1;
206 self.0
207 .update_untracked(|SmartCounterI { since, .. }| *since += s);
208 }
209}
210
211#[derive(Debug, Clone, Default)]
212struct SmartCounterI<N: CounterTrait> {
213 cutoff: Option<Cutoff<N>>,
214 since: N,
215}
216
217#[derive(Clone)]
218struct Cutoff<N: CounterTrait> {
219 previous: Option<std::sync::Arc<Cutoff<N>>>,
220 since: N,
221 set: RwSignal<N>,
222}
223impl<N: CounterTrait> Cutoff<N> {
224 fn get(&self) -> N {
225 if let Some(p) = self.previous.as_ref() {
226 p.get() + self.since + self.set.get()
228 } else {
229 self.since + self.set.get()
231 }
232 }
233 fn depth(&self) -> u16 {
234 if let Some(p) = self.previous.as_ref() {
235 p.depth() + 1
236 } else {
237 1
238 }
239 }
240 fn get_untracked(&self) -> N {
241 if let Some(p) = self.previous.as_ref() {
242 p.get_untracked() + self.since + self.set.get_untracked()
243 } else {
244 self.since + self.set.get_untracked()
245 }
246 }
247}
248impl<N: CounterTrait> std::fmt::Debug for Cutoff<N> {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 f.debug_struct("Cutoff")
251 .field("depth", &self.depth())
252 .field(
253 "previous",
254 &self.previous.as_ref().map(|p| p.get_untracked()),
255 )
256 .field("since", &self.since)
257 .field("set", &self.set.get_untracked())
258 .finish()
259 }
260}
261
262#[derive(Clone, Default, Copy)]
263struct SmartCounter<N: CounterTrait>(RwSignal<SmartCounterI<N>>);
264
265impl<N: CounterTrait> SmartCounterI<N> {
266 fn get(&self) -> N {
267 if let Some(cutoff) = &self.cutoff {
268 cutoff.get() + self.since
269 } else {
270 self.since
271 }
272 }
273}
274
275impl<N: CounterTrait> std::fmt::Debug for SmartCounter<N> {
276 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277 self.0
278 .with_untracked(|s| f.debug_struct("SmartCounter").field("inner", s).finish())
279 }
280}
281
282impl<N: CounterTrait> SmartCounter<N> {
283 fn inc_memo<T: Send + Sync + 'static + PartialEq>(
284 &self,
285 f: impl Fn(N) -> T + Send + Sync + 'static,
286 ) -> Memo<T> {
287 self.0.update_untracked(|SmartCounterI { cutoff, since }| {
288 *since += N::one();
289 let since = *since;
290 if let Some(cutoff) = cutoff {
291 let cutoff = cutoff.clone();
292 Memo::new(move |_| f(cutoff.get() + since))
293 } else {
294 Memo::new(move |_| f(since))
295 }
296 })
297 }
298 fn get_untracked(&self) -> N {
299 self.0.with_untracked(|SmartCounterI { cutoff, since }| {
300 if let Some(cutoff) = cutoff {
301 cutoff.get() + *since
302 } else {
303 *since
304 }
305 })
306 }
307
308 fn inc(&self) {
309 self.0
310 .update_untracked(|SmartCounterI { since, .. }| *since += N::one());
311 }
312 fn memo<T: Send + Sync + 'static + PartialEq>(
313 &self,
314 f: impl Fn(N) -> T + Send + Sync + 'static,
315 ) -> Memo<T> {
316 self.0.with_untracked(|SmartCounterI { cutoff, since }| {
317 let since = *since;
318 if let Some(cutoff) = cutoff {
319 let cutoff = cutoff.clone();
320 Memo::new(move |_| f(cutoff.get() + since))
321 } else {
322 Memo::new(move |_| f(since))
323 }
324 })
325 }
326 fn reset(&self) {
327 self.0.update_untracked(|x| *x = SmartCounterI::default());
328 }
329
330 fn set_cutoff(&self, v: N) {
331 self.0.update_untracked(|SmartCounterI { cutoff, .. }| {
332 if let Some(c) = cutoff.as_ref() {
333 c.set.set(v);
334 }
335 });
336 }
337
338 fn split(&self) -> Self {
339 let SmartCounterI { cutoff, since } = self.0.get_untracked();
340 let ret = Self(RwSignal::new(SmartCounterI {
341 cutoff: cutoff.clone(),
342 since,
343 }));
344
345 let previous = cutoff.map(std::sync::Arc::new);
346 let new_cutoff = Cutoff {
347 previous,
348 since,
349 set: RwSignal::new(N::default()),
350 };
351 self.0.update_untracked(
352 |SmartCounterI {
353 cutoff: nctf,
354 since: snc,
355 }| {
356 *nctf = Some(new_cutoff);
357 *snc = N::default();
358 },
359 );
360 ret
361 }
362}
363
364#[derive(Debug, Clone)]
365pub struct SectionCounters {
366 pub current: LogicalLevel,
367 pub max: SectionLevel,
368 sections: SmartCounter<AllSections>,
369 initialized: RwSignal<bool>,
370 counters: RwSignal<VecMap<Name, SmartCounter<u16>>>,
371 resets: RwSignal<VecMap<SectionLevel, VecSet<Name>>>,
372 for_paras: RwSignal<VecMap<(ParagraphKind, Option<Name>), Option<Name>>>,
373 slides: SmartCounter<u32>,
374}
375impl Default for SectionCounters {
376 #[inline]
377 fn default() -> Self {
378 Self {
379 current: LogicalLevel::None,
380 max: SectionLevel::Part,
381 sections: SmartCounter::default(),
382 counters: RwSignal::new(VecMap::default()),
383 resets: RwSignal::new(VecMap::default()),
384 for_paras: RwSignal::new(VecMap::default()),
385 initialized: RwSignal::new(false),
386 slides: SmartCounter::default(),
387 }
388 }
389}
390impl SectionCounters {
391 fn init_paras(&self) {
392 if self.initialized.get_untracked() {
393 return;
394 }
395 self.initialized.update_untracked(|b| *b = true);
396 let mut counters = VecMap::default();
397 let mut resets = VecMap::default();
398 let mut for_paras = VecMap::default();
399 let sig = expect_context::<RwSignal<DOMExtractor>>();
400 sig.with_untracked(|e| {
401 for c in &e.styles.counters {
402 counters.insert(c.name.clone(), SmartCounter::default());
404 if let Some(p) = c.parent {
405 resets
406 .get_or_insert_mut(p, || VecSet::new())
407 .insert(c.name.clone());
408 }
409 }
410 for stl in &e.styles.styles {
411 for_paras.insert((stl.kind, stl.name.clone()), stl.counter.clone());
412 }
413 });
414 self.counters.update_untracked(|p| *p = counters);
415 self.resets.update_untracked(|p| *p = resets);
416 self.for_paras.update_untracked(|p| *p = for_paras);
417 }
418
419 pub fn current_level(&self) -> LogicalLevel {
420 self.current
421 }
422
423 pub fn next_section(&mut self) -> (Option<Memo<String>>, Option<&'static str>) {
424 self.init_paras();
425 let lvl = if let LogicalLevel::Section(s) = self.current {
426 s.inc()
427 } else if self.current == LogicalLevel::None {
428 self.max
429 } else {
430 return (Some(Memo::new(|_| "display:content;".into())), None);
431 };
432 self.set_section(lvl);
434 let sections = self.sections.0.get_untracked();
435 match lvl {
436 SectionLevel::Part => (
437 Some(Memo::new(move |_| {
438 let sects = sections.get().0;
439 format!("counter-set:ftml-part {};", sects[0])
440 })),
441 Some("ftml-part"),
442 ),
443 SectionLevel::Chapter => (
444 Some(Memo::new(move |_| {
445 let sects = sections.get().0;
446 format!(
447 "counter-set:ftml-part {} ftml-chapter {}",
448 sects[0], sects[1]
449 )
450 })),
451 Some("ftml-chapter"),
452 ),
453 SectionLevel::Section => (
454 Some(Memo::new(move |_| {
455 let sects = sections.get().0;
456 format!(
457 "counter-set:ftml-part {} ftml-chapter {} ftml-section {}",
458 sects[0], sects[1], sects[2]
459 )
460 })),
461 Some("ftml-section"),
462 ),
463 SectionLevel::Subsection => (
464 Some(Memo::new(move |_| {
465 let sects = sections.get().0;
466 format!("counter-set:ftml-part {} ftml-chapter {} ftml-section {} ftml-subsection {}",
467 sects[0],
468 sects[1],
469 sects[2],
470 sects[3],
471 )
472 })),
473 Some("ftml-subsection"),
474 ),
475 SectionLevel::Subsubsection => (
476 Some(Memo::new(move |_| {
477 let sects = sections.get().0;
478 format!("counter-set:ftml-part {} ftml-chapter {} ftml-section {} ftml-subsection {} ftml-subsubsection {}",
479 sects[0],
480 sects[1],
481 sects[2],
482 sects[3],
483 sects[4],
484 )
485 })),
486 Some("ftml-subsubsection"),
487 ),
488 SectionLevel::Paragraph => (None, Some("ftml-paragraph")),
489 SectionLevel::Subparagraph => (None, Some("ftml-subparagraph")),
490 }
491 }
492
493 pub fn set_section(&mut self, lvl: SectionLevel) {
494 self.init_paras();
495 self.sections.inc_at(lvl);
496 self.resets.with_untracked(|rs| {
498 for (l, r) in &rs.0 {
500 if *l >= lvl {
501 for n in r.iter() {
502 self.counters.with_untracked(|c| {
503 if let Some(c) = c.get(n) {
504 c.reset();
506 }
507 });
508 }
509 }
510 }
511 });
512 self.current = LogicalLevel::Section(lvl);
513 }
514
515 fn get_counter(
516 all: &VecMap<(ParagraphKind, Option<Name>), Option<Name>>,
517 kind: ParagraphKind,
518 styles: &[Name],
519 ) -> Option<Name> {
520 styles
521 .iter()
522 .rev()
523 .find_map(|s| {
524 all.0.iter().find_map(|((k, n), v)| {
525 if *k == kind && n.as_ref().is_some_and(|n| *n == *s) {
526 Some(v.as_ref())
527 } else {
528 None
529 }
530 })
531 })
532 .unwrap_or_else(|| all.get(&(kind, None)).map(|o| o.as_ref()).flatten())
533 .cloned()
534 }
535
536 pub fn get_para(&mut self, kind: ParagraphKind, styles: &[Name]) -> Memo<String> {
537 self.init_paras();
538 self.current = LogicalLevel::Paragraph;
539 let cnt = self
540 .for_paras
541 .with_untracked(|all_styles| Self::get_counter(all_styles, kind, styles));
542 if let Some(cntname) = cnt {
543 let Some(cnt) = self
544 .counters
545 .with_untracked(|cntrs| cntrs.get(&cntname).copied())
546 else {
547 impossible!()
548 };
549 cnt.inc_memo(move |i| format!("counter-set:ftml-{cntname} {i};"))
550 } else {
551 Memo::new(|_| String::new())
552 }
553 }
554
555 pub fn get_problem(&mut self, _styles: &[Name]) -> Memo<String> {
556 self.init_paras();
557 self.current = LogicalLevel::Paragraph;
558 Memo::new(|_| String::new())
559 }
560
561 pub fn get_slide() -> Memo<u32> {
562 let counters: Self = expect_context();
563 counters.init_paras();
564 counters.slides.memo(|i| i)
565 }
566 pub fn slide_inc() -> Self {
567 let mut counters: Self = expect_context();
568 counters.init_paras();
569 counters.slides.inc();
570 counters.current = LogicalLevel::BeamerSlide;
571 counters
572 }
573
574 pub fn inputref(uri: DocumentURI, id: String) -> Self {
575 let mut counters: Self = expect_context();
576 counters.init_paras();
577
578 let old_slides = counters.slides; counters.slides = counters.slides.split();
582 let old_slides = unwrap!(old_slides.0.get_untracked().cutoff).set;
583
584 let old_sections = counters.sections;
585 counters.sections = counters.sections.split();
586 let old_sections = unwrap!(old_sections.0.get_untracked().cutoff).set;
587
588 let mut new_paras = VecMap::default();
589
590 let old_paras = counters.counters.with_untracked(|v| {
591 v.0.iter()
592 .map(|(n, e)| {
593 let r = *e;
595 let since = r.0.update_untracked(|e| {
596 let r = e.since;
597 e.since = 0;
598 r
599 });
600 new_paras.insert(n.clone(), e.split());
601 (n.clone(), (unwrap!(r.0.get_untracked().cutoff).set, since))
602 })
603 .collect::<VecMap<_, _>>()
604 });
605 counters.counters = RwSignal::new(new_paras);
606
607 let ctw = expect_context::<RwSignal<Option<Vec<TOCElem>>>>();
608 let uricl = uri.clone();
609 let idcl = id.clone();
610 let children = Memo::new(move |_| {
611 let uri = &uricl;
612 let id = &idcl;
613 ctw.with(|v| {
614 if let Some(v) = v.as_ref() {
615 for e in v.iter_elems() {
616 if let TOCElem::Inputref {
617 uri: u,
618 id: i,
619 children: chs,
620 ..
621 } = e
622 {
623 if u == uri && i == id {
624 return Some(chs.clone());
625 }
626 }
627 }
628 None
629 } else {
630 None
631 }
632 })
633 });
634
635 let current = counters.current;
636 let max = counters.max;
637 let para_map = counters.for_paras;
638
639 Effect::new(move || {
640 children.with(|ch| {
641 if let Some(ch) = ch.as_ref() {
642 para_map.with_untracked(|m| {
644 update(ch, current, max, &old_slides, &old_sections, &old_paras, m)
645 })
646 }
647 })
648 });
649
650 counters
653 }
654}
655
656fn update(
657 ch: &[TOCElem],
658 mut current: LogicalLevel,
659 max: SectionLevel,
660 old_slides: &RwSignal<u32>,
661 old_sections: &RwSignal<AllSections>,
662 old_paras: &VecMap<Name, (RwSignal<u16>, u16)>,
663 para_map: &VecMap<(ParagraphKind, Option<Name>), Option<Name>>,
664) {
665 let mut curr = ch.iter();
666 let mut stack = SmallVec::<_, 4>::new();
667
668 let mut n_slides = 0;
669 let mut n_sections = AllSections::default();
670 let mut n_counters = old_paras
671 .0
672 .iter()
673 .map(|(n, (_, i))| (n.clone(), *i))
674 .collect::<VecMap<_, _>>();
675
676 loop {
679 if let Some(c) = curr.next() {
680 match c {
681 TOCElem::Slide => n_slides += 1,
682 TOCElem::SkippedSection { children } => {
683 let lvl = if let LogicalLevel::Section(s) = current {
684 s.inc()
685 } else if current == LogicalLevel::None {
686 max
687 } else {
688 continue;
689 };
690 let old = std::mem::replace(&mut current, LogicalLevel::Section(lvl));
691 stack.push((std::mem::replace(&mut curr, children.iter()), old));
692 }
693 TOCElem::Section { children, .. } => {
694 let lvl = if let LogicalLevel::Section(s) = current {
695 s.inc()
696 } else if current == LogicalLevel::None {
697 max
698 } else {
699 continue;
700 };
701 n_sections = n_sections + lvl;
702 let old = std::mem::replace(&mut current, LogicalLevel::Section(lvl));
703 stack.push((std::mem::replace(&mut curr, children.iter()), old));
704 }
705 TOCElem::Inputref { children, .. } => {
706 stack.push((std::mem::replace(&mut curr, children.iter()), current));
708 }
709 TOCElem::Paragraph { styles, kind, .. } => {
710 if let Some(n) = SectionCounters::get_counter(para_map, *kind, styles) {
711 *n_counters.get_or_insert_mut(n, || 0) += 1;
713 }
714 }
715 }
716 } else if let Some((next, lvl)) = stack.pop() {
717 curr = next;
718 current = lvl;
719 } else {
720 break;
721 }
722 }
723
724 old_slides.set(n_slides);
727 old_sections.set(n_sections);
728 for (n, v) in n_counters {
729 if let Some((s, _)) = old_paras.get(&n) {
731 s.set(v);
732 }
733 }
734}