1use core::{
7 cell::UnsafeCell,
8 hash::{Hash, Hasher},
9 marker::PhantomData,
10 ptr::addr_of,
11};
12use embedded_graphics::{
13 draw_target::DrawTarget,
14 prelude::PixelColor,
15 primitives::Rectangle,
16 text::renderer::{CharacterStyle, TextRenderer},
17};
18
19use crate::{
20 parser::{Parser, Token},
21 rendering::{cursor::Cursor, TextBoxProperties},
22};
23
24#[cfg(feature = "plugin")]
25pub mod private;
26#[cfg(feature = "plugin")]
27pub use private::Plugin;
28
29#[cfg(not(feature = "plugin"))]
30mod private;
31#[cfg(not(feature = "plugin"))]
32use private::Plugin;
33
34#[cfg(feature = "ansi")]
35pub mod ansi;
36pub mod tail;
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
39pub(crate) enum ProcessingState {
40 Measure,
41 Render,
42}
43
44#[derive(Clone, Copy, Default)]
46pub struct NoPlugin<C>(PhantomData<C>)
47where
48 C: PixelColor;
49
50impl<C> NoPlugin<C>
51where
52 C: PixelColor,
53{
54 pub(crate) const fn new() -> Self {
55 Self(PhantomData)
56 }
57}
58
59#[cfg_attr(
63 feature = "plugin",
64 doc = "If you wish to implement a plugin, see [Plugin]."
65)]
66pub trait PluginMarker<'a, C: PixelColor>: Plugin<'a, C> {}
68
69impl<'a, C, T> PluginMarker<'a, C> for T
70where
71 T: Plugin<'a, C>,
72 C: PixelColor,
73{
74}
75
76#[derive(Clone, Debug)]
77pub(crate) struct PluginInner<'a, M, C> {
78 plugin: M,
79 state: ProcessingState,
80 peeked_token: Option<Token<'a, C>>,
81}
82
83#[derive(Debug)]
84pub(crate) struct PluginWrapper<'a, M, C> {
85 inner: UnsafeCell<PluginInner<'a, M, C>>,
86}
87
88impl<'a, M: Clone, C: Clone> Clone for PluginWrapper<'a, M, C> {
89 fn clone(&self) -> Self {
90 Self {
91 inner: UnsafeCell::new(self.with(|this| PluginInner {
92 plugin: this.plugin.clone(),
93 state: this.state,
94 peeked_token: unsafe { addr_of!(this.peeked_token).read() },
95 })),
96 }
97 }
98}
99
100impl<'a, M, C> Hash for PluginWrapper<'a, M, C>
101where
102 C: PixelColor,
103{
104 fn hash<H: Hasher>(&self, state: &mut H) {
105 self.with(|this| this.state.hash(state))
106 }
107}
108
109impl<'a, M, C> PluginWrapper<'a, M, C> {
110 pub fn new(plugin: M) -> Self {
111 Self {
112 inner: UnsafeCell::new(PluginInner {
113 plugin,
114 state: ProcessingState::Measure,
115 peeked_token: None,
116 }),
117 }
118 }
119
120 pub fn into_inner(self) -> M {
121 self.inner.into_inner().plugin
122 }
123
124 fn with<R>(&self, cb: impl FnOnce(&PluginInner<'a, M, C>) -> R) -> R {
125 let inner = unsafe {
126 self.inner.get().as_ref().unwrap_unchecked()
128 };
129
130 cb(inner)
131 }
132
133 fn with_mut<R>(&self, cb: impl FnOnce(&mut PluginInner<'a, M, C>) -> R) -> R {
134 let inner = unsafe {
135 self.inner.get().as_mut().unwrap_unchecked()
137 };
138
139 cb(inner)
140 }
141}
142
143impl<'a, M, C> PluginWrapper<'a, M, C>
144where
145 C: PixelColor,
146 M: private::Plugin<'a, C>,
147{
148 pub fn new_line(&self) {
149 self.with_mut(|this| this.plugin.new_line());
150 }
151
152 pub fn set_state(&self, state: ProcessingState) {
153 self.with_mut(|this| this.state = state);
154 }
155
156 #[inline]
157 pub fn render_token(&self, token: Token<'a, C>) -> Option<Token<'a, C>> {
158 self.with_mut(|this| match this.state {
159 ProcessingState::Measure => Some(token),
160 ProcessingState::Render => this.plugin.render_token(token),
161 })
162 }
163
164 pub fn peek_token(&self, source: &mut Parser<'a, C>) -> Option<Token<'a, C>> {
165 self.with_mut(|this| {
166 if this.peeked_token.is_none() {
167 this.peeked_token = this.plugin.next_token(|| source.next());
168 }
169
170 this.peeked_token.clone()
171 })
172 }
173
174 pub fn consume_peeked_token(&self) {
175 self.with_mut(|this| this.peeked_token = None);
176 }
177
178 pub fn consume_partial(&self, len: usize) {
179 self.with_mut(|this| {
180 debug_assert!(matches!(
182 this.peeked_token,
183 Some(Token::Whitespace(_, _)) | Some(Token::Word(_))
184 ));
185
186 let skip_chars = |str: &'a str, n| {
187 let mut chars = str.chars();
188 for _ in 0..n {
189 chars.next();
190 }
191 chars.as_str()
192 };
193
194 if let Some(token) = this.peeked_token.take() {
195 let token = match token {
196 Token::Whitespace(count, seq) => {
197 Token::Whitespace(count - len as u32, skip_chars(seq, len))
198 }
199 Token::Word(w) => Token::Word(skip_chars(w, len)),
200 _ => return,
201 };
202
203 this.peeked_token.replace(token);
204 }
205 })
206 }
207
208 pub fn on_start_render<S: CharacterStyle + TextRenderer>(
209 &self,
210 cursor: &mut Cursor,
211 props: TextBoxProperties<'_, S>,
212 ) {
213 self.with_mut(|this| {
214 this.peeked_token = None;
215
216 this.plugin.on_start_render(cursor, &props);
217 });
218 }
219
220 pub fn on_rendering_finished(&self) {
221 self.with_mut(|this| this.plugin.on_rendering_finished());
222 }
223
224 pub fn post_render<T, D>(
225 &self,
226 draw_target: &mut D,
227 character_style: &T,
228 text: Option<&str>,
229 bounds: Rectangle,
230 ) -> Result<(), D::Error>
231 where
232 T: TextRenderer<Color = C>,
233 D: DrawTarget<Color = C>,
234 {
235 self.with_mut(|this| {
236 this.plugin
237 .post_render(draw_target, character_style, text, bounds)
238 })
239 }
240}