Skip to main content

ftml/render/text/
context.rs

1/*
2 * render/text/context.rs
3 *
4 * ftml - Library to parse Wikidot text
5 * Copyright (C) 2019-2026 Wikijump Team
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21use crate::data::PageInfo;
22use crate::non_empty_vec::NonEmptyVec;
23use crate::render::Handle;
24use crate::settings::WikitextSettings;
25use crate::tree::{Bibliography, BibliographyList, Element, VariableScopes};
26use std::fmt::{self, Write};
27use std::num::NonZeroUsize;
28
29#[derive(Debug)]
30pub struct TextContext<'i, 'h, 'e, 't>
31where
32    'e: 't,
33{
34    output: String,
35    info: &'i PageInfo<'i>,
36    handle: &'h Handle,
37    settings: &'e WikitextSettings,
38
39    //
40    // Included page scopes
41    //
42    variables: VariableScopes,
43
44    //
45    // Elements from the syntax tree
46    //
47    table_of_contents: &'e [Element<'t>],
48    footnotes: &'e [Vec<Element<'t>>],
49    bibliographies: &'e BibliographyList<'t>,
50
51    //
52    // Other fields to track
53    //
54    /// Strings to prepended to each new line.
55    prefixes: Vec<&'static str>,
56
57    /// How deep we currently are in the list.
58    list_depths: NonEmptyVec<usize>,
59
60    /// Whether we're in "invisible mode".
61    /// When this is non-zero, all non-newline characters
62    /// added are instead replaced with spaces.
63    invisible: usize,
64
65    /// The current equation index, for rendering.
66    equation_index: NonZeroUsize,
67
68    /// The current footnote index, for rendering.
69    footnote_index: NonZeroUsize,
70}
71
72impl<'i, 'h, 'e, 't> TextContext<'i, 'h, 'e, 't>
73where
74    'e: 't,
75{
76    #[inline]
77    pub fn new(
78        info: &'i PageInfo<'i>,
79        handle: &'h Handle,
80        settings: &'e WikitextSettings,
81        table_of_contents: &'e [Element<'t>],
82        footnotes: &'e [Vec<Element<'t>>],
83        bibliographies: &'e BibliographyList<'t>,
84        wikitext_len: usize,
85    ) -> Self {
86        TextContext {
87            output: String::with_capacity(wikitext_len),
88            info,
89            handle,
90            settings,
91            variables: VariableScopes::new(),
92            table_of_contents,
93            footnotes,
94            bibliographies,
95            prefixes: Vec::new(),
96            list_depths: NonEmptyVec::new(1),
97            invisible: 0,
98            equation_index: NonZeroUsize::new(1).unwrap(),
99            footnote_index: NonZeroUsize::new(1).unwrap(),
100        }
101    }
102
103    // Getters
104    #[inline]
105    pub fn buffer(&mut self) -> &mut String {
106        &mut self.output
107    }
108
109    #[inline]
110    pub fn info(&self) -> &'i PageInfo<'i> {
111        self.info
112    }
113
114    #[inline]
115    pub fn settings(&self) -> &WikitextSettings {
116        self.settings
117    }
118
119    #[inline]
120    pub fn language(&self) -> &str {
121        &self.info.language
122    }
123
124    #[inline]
125    pub fn handle(&self) -> &'h Handle {
126        self.handle
127    }
128
129    #[inline]
130    pub fn variables(&self) -> &VariableScopes {
131        &self.variables
132    }
133
134    #[inline]
135    pub fn variables_mut(&mut self) -> &mut VariableScopes {
136        &mut self.variables
137    }
138
139    #[inline]
140    pub fn table_of_contents(&self) -> &'e [Element<'t>] {
141        self.table_of_contents
142    }
143
144    #[inline]
145    pub fn footnotes(&self) -> &'e [Vec<Element<'t>>] {
146        self.footnotes
147    }
148
149    #[inline]
150    pub fn get_bibliography(&self, index: usize) -> &'e Bibliography<'t> {
151        self.bibliographies.get_bibliography(index)
152    }
153
154    pub fn get_bibliography_ref(
155        &self,
156        label: &str,
157    ) -> Option<(usize, &'e [Element<'t>])> {
158        self.bibliographies.get_reference(label)
159    }
160
161    pub fn next_equation_index(&mut self) -> NonZeroUsize {
162        let index = self.equation_index;
163        self.equation_index = NonZeroUsize::new(index.get() + 1).unwrap();
164        index
165    }
166
167    pub fn next_footnote_index(&mut self) -> NonZeroUsize {
168        let index = self.footnote_index;
169        self.footnote_index = NonZeroUsize::new(index.get() + 1).unwrap();
170        index
171    }
172
173    // Prefixes
174    #[inline]
175    pub fn push_prefix(&mut self, prefix: &'static str) {
176        self.prefixes.push(prefix);
177    }
178
179    #[inline]
180    pub fn pop_prefix(&mut self) {
181        self.prefixes.pop();
182    }
183
184    // List depth
185    #[inline]
186    pub fn list_depth(&self) -> usize {
187        self.list_depths.len()
188    }
189
190    #[inline]
191    pub fn incr_list_depth(&mut self) {
192        self.list_depths.push(1);
193    }
194
195    #[inline]
196    pub fn decr_list_depth(&mut self) {
197        self.list_depths.pop();
198    }
199
200    pub fn next_list_index(&mut self) -> usize {
201        let index = *self.list_depths.last();
202        *self.list_depths.last_mut() += 1;
203        index
204    }
205
206    // Invisible mode
207    #[inline]
208    fn invisible(&self) -> bool {
209        self.invisible > 0
210    }
211
212    #[inline]
213    pub fn enable_invisible(&mut self) {
214        self.invisible += 1;
215    }
216
217    #[inline]
218    pub fn disable_invisible(&mut self) {
219        self.invisible -= 1;
220    }
221
222    // Buffer management
223    pub fn push(&mut self, ch: char) {
224        if self.invisible() {
225            self.output.push(' ');
226        } else {
227            self.output.push(ch);
228        }
229    }
230
231    pub fn push_str(&mut self, s: &str) {
232        if self.invisible() {
233            let chars = s.chars().count();
234            for _ in 0..chars {
235                self.output.push(' ');
236            }
237        } else {
238            self.output.push_str(s);
239        }
240    }
241
242    pub fn add_newline(&mut self) {
243        self.output.push('\n');
244
245        for prefix in &self.prefixes {
246            self.output.push_str(prefix);
247        }
248    }
249
250    #[inline]
251    pub fn ends_with_newline(&self) -> bool {
252        self.output.ends_with('\n')
253    }
254}
255
256impl<'i, 'h, 'e, 't> From<TextContext<'i, 'h, 'e, 't>> for String {
257    #[inline]
258    fn from(ctx: TextContext<'i, 'h, 'e, 't>) -> String {
259        ctx.output
260    }
261}
262
263impl<'e, 't> Write for TextContext<'_, '_, 'e, 't>
264where
265    'e: 't,
266{
267    #[inline]
268    fn write_str(&mut self, s: &str) -> fmt::Result {
269        self.buffer().write_str(s)
270    }
271}