Skip to main content

citum_engine/render/
plain.rs

1/*
2SPDX-License-Identifier: MIT OR Apache-2.0
3SPDX-FileCopyrightText: © 2023-2026 Bruce D'Arcus and Citum contributors
4*/
5
6//! Plain text output format.
7
8use super::format::OutputFormat;
9use citum_schema::template::WrapPunctuation;
10
11#[derive(Default, Clone)]
12/// Renders processed citations and bibliography entries as plain text.
13pub struct PlainText;
14
15impl OutputFormat for PlainText {
16    type Output = String;
17
18    fn text(&self, s: &str) -> Self::Output {
19        s.to_string()
20    }
21
22    fn join(&self, items: Vec<Self::Output>, delimiter: &str) -> Self::Output {
23        items.join(delimiter)
24    }
25
26    fn finish(&self, output: Self::Output) -> String {
27        output
28    }
29
30    fn emph(&self, content: Self::Output) -> Self::Output {
31        if content.is_empty() {
32            return content;
33        }
34        format!("_{content}_")
35    }
36
37    fn strong(&self, content: Self::Output) -> Self::Output {
38        if content.is_empty() {
39            return content;
40        }
41        format!("**{content}**")
42    }
43
44    fn small_caps(&self, content: Self::Output) -> Self::Output {
45        // Plain text fallback for small caps could be capitals, but usually we just keep it as is
46        // or use span-like markers if we wanted, but let's stick to plain text.
47        content
48    }
49
50    fn superscript(&self, content: Self::Output) -> Self::Output {
51        if content.is_empty() {
52            return content;
53        }
54        format!("^{content}^")
55    }
56
57    fn quote(&self, content: Self::Output) -> Self::Output {
58        if content.is_empty() {
59            return content;
60        }
61        format!("\u{201C}{content}\u{201D}")
62    }
63
64    fn affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
65        format!("{prefix}{content}{suffix}")
66    }
67
68    fn inner_affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
69        format!("{prefix}{content}{suffix}")
70    }
71
72    fn wrap_punctuation(&self, wrap: &WrapPunctuation, content: Self::Output) -> Self::Output {
73        match wrap {
74            WrapPunctuation::Parentheses => format!("({content})"),
75            WrapPunctuation::Brackets => format!("[{content}]"),
76            WrapPunctuation::Quotes => format!("\u{201C}{content}\u{201D}"),
77        }
78    }
79
80    fn semantic(&self, _class: &str, content: Self::Output) -> Self::Output {
81        // Plain text ignores semantic classes
82        content
83    }
84
85    fn annotation(&self, content: Self::Output) -> Self::Output {
86        if content.is_empty() {
87            return content;
88        }
89
90        format!("\n\n{content}")
91    }
92
93    fn link(&self, _url: &str, content: Self::Output) -> Self::Output {
94        // Plain text just renders the text content of the link
95        content
96    }
97
98    fn entry(
99        &self,
100        _id: &str,
101        content: Self::Output,
102        _url: Option<&str>,
103        _metadata: &super::format::ProcEntryMetadata,
104    ) -> Self::Output {
105        content
106    }
107}