ferroui_base/media/text_formatting/
text_run_cache.rs1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use crate::media::text_formatting::{ShapedTextRun, TextRun};
6use crate::media::FlowDirection;
7
8#[derive(Default)]
18pub struct TextRunCache {
19 state: RefCell<State>,
20}
21
22#[derive(Default)]
23struct State {
24 single: Option<(i32, CachedShapingResult)>,
26 entries: Option<HashMap<i32, CachedShapingResult>>,
28}
29
30impl TextRunCache {
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn invalidate(&self) {
37 let mut state = self.state.borrow_mut();
38
39 if let Some((_, value)) = state.single.take() {
40 Self::dispose_cached_runs(&value);
41 return;
42 }
43
44 let Some(entries) = &mut state.entries else {
45 return;
46 };
47
48 for entry in entries.values() {
49 Self::dispose_cached_runs(entry);
50 }
51
52 entries.clear();
53 }
54
55 pub fn invalidate_from(&self, text_source_index: i32) {
57 let mut state = self.state.borrow_mut();
58
59 if let Some((key, value)) = &state.single {
60 if *key >= text_source_index {
61 Self::dispose_cached_runs(value);
62 state.single = None;
63 }
64
65 return;
66 }
67
68 let Some(entries) = &mut state.entries else {
69 return;
70 };
71
72 entries.retain(|key, result| {
73 if *key >= text_source_index {
74 Self::dispose_cached_runs(result);
75 false
76 } else {
77 true
78 }
79 });
80 }
81
82 pub(crate) fn try_get_shaped_runs(&self, first_text_source_index: i32) -> Option<CachedShapingResult> {
84 let state = self.state.borrow();
85
86 if let Some((key, value)) = &state.single {
87 if *key == first_text_source_index {
88 return Some(value.clone());
89 }
90 }
91
92 state.entries.as_ref().and_then(|entries| entries.get(&first_text_source_index).cloned())
93 }
94
95 pub(crate) fn add(&self, first_text_source_index: i32, result: CachedShapingResult) {
99 Self::add_ref_shaped_runs(&result.shaped_runs);
100
101 let mut state = self.state.borrow_mut();
102
103 if let Some(entries) = &mut state.entries {
104 if let Some(existing) = entries.insert(first_text_source_index, result) {
105 Self::dispose_cached_runs(&existing);
106 }
107
108 return;
109 }
110
111 match state.single.take() {
112 None => {
113 state.single = Some((first_text_source_index, result));
114 }
115 Some((single_key, single_value)) if single_key == first_text_source_index => {
116 Self::dispose_cached_runs(&single_value);
117 state.single = Some((first_text_source_index, result));
118 }
119 Some((single_key, single_value)) => {
120 let mut entries = HashMap::new();
122 entries.insert(single_key, single_value);
123 entries.insert(first_text_source_index, result);
124 state.entries = Some(entries);
125 }
126 }
127 }
128
129 fn add_ref_shaped_runs(runs: &[Rc<dyn TextRun>]) {
130 for run in runs {
131 if let Some(shaped) = run.downcast_ref::<ShapedTextRun>() {
132 shaped.add_reference();
133 }
134 }
135 }
136
137 pub fn dispose(&self) {
139 self.invalidate();
140 self.state.borrow_mut().entries = None;
141 }
142
143 fn dispose_cached_runs(result: &CachedShapingResult) {
144 for run in result.shaped_runs.iter() {
145 if let Some(shaped) = run.downcast_ref::<ShapedTextRun>() {
146 shaped.dispose();
147 }
148 }
149 }
150
151 #[cfg(test)]
153 pub(crate) fn has_entries_map(&self) -> bool {
154 self.state.borrow().entries.is_some()
155 }
156}
157
158#[derive(Clone)]
161pub(crate) struct CachedShapingResult {
162 pub shaped_runs: Rc<[Rc<dyn TextRun>]>,
164 pub resolved_flow_direction: FlowDirection,
166 pub text_end_of_line: Option<Rc<dyn TextRun>>,
168 pub text_source_length: i32,
170}
171
172impl CachedShapingResult {
173 pub fn new(
174 shaped_runs: Rc<[Rc<dyn TextRun>]>,
175 resolved_flow_direction: FlowDirection,
176 text_end_of_line: Option<Rc<dyn TextRun>>,
177 text_source_length: i32,
178 ) -> Self {
179 Self { shaped_runs, resolved_flow_direction, text_end_of_line, text_source_length }
180 }
181}