1use std::fmt::Write;
2use std::{rc::Rc, cell::RefCell};
3
4use crate::parser::*;
5
6fn escape_html(text: &str) -> String {
8 let mut result = String::with_capacity(text.len());
9 for c in text.chars() {
10 match c {
11 '&' => result.push_str("&"),
12 '<' => result.push_str("<"),
13 '>' => result.push_str(">"),
14 '"' => result.push_str("""),
15 '\'' => result.push_str("'"),
16 _ => result.push(c),
17 }
18 }
19 result
20}
21
22fn escape_href(href: &str) -> String {
24 let href = href.trim();
26
27 if !href.starts_with("http://") && !href.starts_with("https://") {
29 format!("https://{}", href)
30 } else {
31 href.to_string()
32 }
33}
34
35#[derive(Debug, Clone)]
37struct Link(Rc<RefCell<Option<LinkData>>>);
38
39#[derive(Debug)]
41struct LinkData {
42 href: String,
43 replacer: String,
44}
45
46impl Link {
47
48 fn new(id: u32) -> Self {
50 let data = LinkData {
51 href: String::new(),
52 replacer: format!("§§HREF{id}§§"),
53 };
54
55 Self(Rc::new(RefCell::new(Some(data))))
56 }
57
58 fn append(&self, text: &str) {
60 let mut reference = self.0.borrow_mut();
61 let data = reference.as_mut().expect("link already taken");
62 data.href.push_str(text)
63 }
64
65 fn replacer(&self) -> String {
67 let reference = self.0.borrow();
68 let data = reference.as_ref().expect("link already taken");
69 data.replacer.clone()
70 }
71
72 fn take(&self) -> LinkData {
74 self.0.take().expect("link already taken")
75 }
76
77}
78
79impl PartialEq<Link> for Link {
80
81 fn eq(&self, other: &Link) -> bool {
83 Rc::ptr_eq(&self.0, &other.0)
84 }
85
86}
87
88#[derive(Debug, Clone, PartialEq)]
90enum Element {
91 Strong,
92 Em,
93 Ins,
94 Del,
95 Span { color: Color },
96 A { link: Link },
97}
98
99#[derive(Default, Debug)]
101struct Renderer {
102 html: String,
104
105 elements: Vec<Element>,
107
108 link_counter: u32,
110 link_list: Vec<Link>,
112
113 is_editor: bool,
115}
116
117macro_rules! write_html {
119 ($self:ident, $($arg:tt)*) => {
120 write!(&mut $self.html, $($arg)*).unwrap()
121 }
122}
123
124macro_rules! write_meta {
127 ($self:ident, $($arg:tt)*) => {
128 if $self.is_editor {
129 write_html!($self, "<span class=\"sillycode-meta\">");
130 write_html!($self, $($arg)*);
131 write_html!($self, "</span>");
132 }
133 };
134}
135
136impl Renderer {
137
138 fn new() -> Self {
140 Self::default()
141 }
142
143 fn open(&mut self, element: &Element) {
145 match element {
146 Element::Strong => write_html!(self, "<strong>"),
147 Element::Em => write_html!(self, "<em>"),
148 Element::Ins => write_html!(self, "<ins>"),
149 Element::Del => write_html!(self, "<del>"),
150 Element::Span { color } => {
151 write_html!(self, "<span style=\"color: {color}\">");
152 }
153 Element::A { link } => {
154 write_html!(self, "<a href=\"{}\">", link.replacer());
155 }
156 }
157 }
158
159 fn close(&mut self, element: &Element) {
161 match element {
162 Element::Strong => write_html!(self, "</strong>"),
163 Element::Em => write_html!(self, "</em>"),
164 Element::Ins => write_html!(self, "</ins>"),
165 Element::Del => write_html!(self, "</del>"),
166 Element::Span { color: _ } => write_html!(self, "</span>"),
167 Element::A { link: _ } => write_html!(self, "</a>"),
168 }
169 }
170
171 fn open_all(&mut self, elements: &[Element]) {
173 for element in elements.iter() {
174 self.open(element);
175 }
176 }
177
178 fn close_all(&mut self, elements: &[Element]) {
180 for element in elements.iter().rev() {
181 self.close(element);
182 }
183 }
184
185 fn push(&mut self, element: Element) {
187 self.open(&element);
188 self.elements.push(element);
189 }
190
191 fn contains(&self, element: &Element) -> bool {
193 self.elements.contains(element)
194 }
195
196 fn remove(&mut self, predicate: impl Fn(&Element) -> bool) -> bool {
198 for i in (0..self.elements.len()).rev() {
199 if predicate(&self.elements[i]) {
200 let removed: Element = self.elements.remove(i);
202 let preserved: Vec<Element> = self.elements[i..self.elements.len()].into();
204
205 self.close_all(&preserved);
207
208 self.close(&removed);
210
211 self.open_all(&preserved);
213
214 return true;
215 }
216 }
217
218 false
219 }
220
221 fn apply(&mut self, element: Element, enable: bool) {
224 if enable {
225 if !self.contains(&element) {
226 self.push(element);
227 }
228 } else {
229 self.remove(|e| e == &element);
230 }
231 }
232
233 fn push_link(&mut self) {
236 let link = Link::new(self.link_counter);
237 self.link_counter += 1;
238 self.link_list.push(link.clone());
239 self.push(Element::A { link });
240 }
241
242 fn append_link(&mut self, text: &str) {
244 for element in self.elements.iter() {
245 if let Element::A { link } = element {
246 link.append(text);
247 }
248 }
249 }
250
251 fn on_text(&mut self, text: &str) {
253 let text = escape_html(text);
255
256 write_html!(self, "{text}");
258
259 self.append_link(text.as_str());
261 }
262
263 fn on_escape(&mut self) {
265 write_meta!(self, "\\");
268 }
269
270 fn on_newline(&mut self) {
272 let elements = self.elements.clone();
274
275 self.close_all(&elements);
277
278 write_html!(self, "</div><div>");
280
281 self.open_all(&elements);
283 }
284
285 fn on_style(&mut self, style: StyleKind, enable: bool) {
287 if style == StyleKind::Link {
289 if enable {
290 write_meta!(self, "[url]");
291 self.push_link();
292 } else {
293 self.remove(|e| matches!(e, Element::A { .. }));
294 write_meta!(self, "[/url]");
295 }
296 } else {
298 if enable {
299 write_meta!(self, "[{}]", style.to_tag());
300 }
301
302 match style {
303 StyleKind::Bold => self.apply(Element::Strong, enable),
304 StyleKind::Italic => self.apply(Element::Em, enable),
305 StyleKind::Underline => self.apply(Element::Ins, enable),
306 StyleKind::Strikethrough => self.apply(Element::Del, enable),
307 _ => unreachable!(),
308 }
309
310 if !enable {
311 write_meta!(self, "[/{}]", style.to_tag());
312 }
313 }
314 }
315
316 fn on_color(&mut self, color: Color, enable: bool) {
318 if enable {
319 write_meta!(self, "[color={color}]");
320 self.push(Element::Span { color });
321 } else {
322 self.remove(|e| matches!(e, Element::Span { .. }));
323 write_meta!(self, "[/color]");
324 }
325 }
326
327 fn on_emote(&mut self, emote: EmoteKind) {
329 let tag = emote.to_tag();
330 let name = emote.to_name();
331 let path = format!("/static/emoticons/{}.png", name);
332 if self.is_editor {
333 write_html!(self, "<span class=\"sillycode-emote\" style=\"background-image: url({path})\">[{tag}]</span>");
334 } else {
335 write_html!(self, "<img class=\"sillycode-emote\" src=\"{path}\" alt=\"{name}\">");
336 }
337 }
338
339 fn render(mut self, parts: impl IntoIterator<Item = Part>) -> String {
341 write_html!(self, "<div>");
343
344 for part in parts {
346 match part {
347 Part::Text(text) => self.on_text(&text),
348 Part::Escape => self.on_escape(),
349 Part::Newline => self.on_newline(),
350 Part::Style(style, enable) => self.on_style(style, enable),
351 Part::Color(color, enable) => self.on_color(color, enable),
352 Part::Emote(emote) => self.on_emote(emote),
353 }
354 }
355
356 self.close_all(&self.elements.clone());
358
359 write_html!(self, "</div>");
361
362 for link in self.link_list.iter().map(|link| link.take()) {
364 self.html = self.html.replace(&link.replacer, &escape_href(&link.href));
365 }
366
367 self.html = self.html
369 .replace("<div> ", "<div> ")
370 .replace(" </div>", " <br></div>")
371 .replace("<div></div>", "<div><br></div>");
372
373 self.html
375 }
376
377}
378
379pub fn render(parts: impl IntoIterator<Item = Part>, is_editor: bool) -> String {
383 let mut renderer = Renderer::new();
384 renderer.is_editor = is_editor;
385 renderer.render(parts)
386}