1use std::collections::HashMap;
5
6use crate::build::ParaBuilder;
7use crate::font::FontHandle;
8use crate::model::{Align, Color, Inline, TextStyle};
9
10#[derive(Clone, Copy, Debug)]
12pub struct Insets {
13 pub top: f32,
15 pub right: f32,
17 pub bottom: f32,
19 pub left: f32,
21}
22
23impl Insets {
24 pub const fn all(v: f32) -> Self {
26 Self { top: v, right: v, bottom: v, left: v }
27 }
28 pub const fn symmetric(v: f32, h: f32) -> Self {
30 Self { top: v, right: h, bottom: v, left: h }
31 }
32}
33
34#[derive(Clone, Debug)]
36pub struct Theme {
37 pub background: Color,
39 pub text: Color,
41 pub accent: Color,
43 pub muted: Color,
45 pub code_bg: Color,
47 pub code_text: Color,
49 pub highlight: Color,
51 pub border: Color,
53 pub font_sans: String,
55 pub font_serif: String,
57 pub font_mono: String,
59 pub font_kai: String,
61 pub base_size: f32,
63 pub line_height: f32,
65 pub heading_scale: [f32; 6],
67}
68
69impl Theme {
70 pub fn light() -> Self {
72 Self {
73 background: Color::rgb(0xff, 0xff, 0xff),
74 text: Color::rgb(0x1f, 0x23, 0x28),
75 accent: Color::rgb(0x25, 0x63, 0xeb),
76 muted: Color::rgb(0x6e, 0x77, 0x81),
77 code_bg: Color::rgb(0xf3, 0xf4, 0xf6),
78 code_text: Color::rgb(0x1f, 0x23, 0x28),
79 highlight: Color::rgb(0xff, 0xf1, 0xa8),
80 border: Color::rgb(0xe5, 0xe7, 0xeb),
81 ..Self::common()
82 }
83 }
84
85 pub fn dark() -> Self {
87 Self {
88 background: Color::rgb(0x0d, 0x11, 0x17),
89 text: Color::rgb(0xe6, 0xed, 0xf3),
90 accent: Color::rgb(0x58, 0xa6, 0xff),
91 muted: Color::rgb(0x8b, 0x94, 0x9e),
92 code_bg: Color::rgb(0x16, 0x1b, 0x22),
93 code_text: Color::rgb(0xe6, 0xed, 0xf3),
94 highlight: Color::rgb(0x57, 0x4a, 0x1a),
95 border: Color::rgb(0x30, 0x36, 0x3d),
96 ..Self::common()
97 }
98 }
99
100 fn common() -> Self {
102 Self {
103 background: Color::rgb(0, 0, 0),
104 text: Color::rgb(0, 0, 0),
105 accent: Color::rgb(0, 0, 0),
106 muted: Color::rgb(0, 0, 0),
107 code_bg: Color::rgb(0, 0, 0),
108 code_text: Color::rgb(0, 0, 0),
109 highlight: Color::rgb(0, 0, 0),
110 border: Color::rgb(0, 0, 0),
111 font_sans: "Noto Sans SC".to_string(), font_serif: "Noto Serif SC".to_string(), font_mono: "JetBrains Mono".to_string(), font_kai: "LXGW WenKai GB".to_string(), base_size: 30.0,
116 line_height: 1.5,
117 heading_scale: [2.0, 1.6, 1.35, 1.15, 1.0, 0.9],
118 }
119 }
120}
121
122impl Default for Theme {
123 fn default() -> Self {
124 Self::light()
125 }
126}
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130pub enum OutputFormat {
131 Png,
133 PngFast,
135 Webp,
137 WebpOrPng,
140}
141
142#[derive(Clone)]
144pub struct RenderOptions {
145 pub width: f32,
147 pub padding: Insets,
149 pub scale: f32,
151 pub theme: Theme,
153 pub fonts: FontHandle,
155 pub format: OutputFormat,
157 pub images: HashMap<String, Vec<u8>>,
159 pub header: Option<PageChrome>,
161 pub footer: Option<PageChrome>,
163}
164
165#[derive(Clone, Debug)]
169pub struct PageChrome {
170 pub inlines: Vec<Inline>,
172 pub trailing: Option<Vec<Inline>>,
175 pub align: Align,
177 pub color: Option<Color>,
179 pub size: f32,
181 pub rule: bool,
183 pub band: Option<Color>,
186}
187
188impl PageChrome {
189 pub fn new(text: impl Into<String>) -> Self {
191 Self::from_inlines(vec![Inline::Text { text: text.into(), style: TextStyle::default() }])
192 }
193
194 pub fn rich<R>(f: impl FnOnce(&mut ParaBuilder) -> R) -> Self {
197 let mut pb = ParaBuilder::new();
198 let _ = f(&mut pb);
199 Self::from_inlines(pb.into_inlines())
200 }
201
202 fn from_inlines(inlines: Vec<Inline>) -> Self {
203 Self {
204 inlines,
205 trailing: None,
206 align: Align::Left,
207 color: None,
208 size: 0.72,
209 rule: true,
210 band: None,
211 }
212 }
213
214 pub fn trailing<R>(mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> Self {
216 let mut pb = ParaBuilder::new();
217 let _ = f(&mut pb);
218 self.trailing = Some(pb.into_inlines());
219 self
220 }
221
222 pub fn band(mut self, hex: &str) -> Self {
224 if let Some(c) = Color::hex(hex) {
225 self.band = Some(c);
226 }
227 self
228 }
229 pub fn align(mut self, a: Align) -> Self {
231 self.align = a;
232 self
233 }
234 pub fn color(mut self, hex: &str) -> Self {
236 if let Some(c) = Color::hex(hex) {
237 self.color = Some(c);
238 }
239 self
240 }
241 pub fn size(mut self, mult: f32) -> Self {
243 if mult.is_finite() && mult > 0.0 {
244 self.size = mult;
245 }
246 self
247 }
248 pub fn no_rule(mut self) -> Self {
250 self.rule = false;
251 self
252 }
253}
254
255impl Default for RenderOptions {
256 fn default() -> Self {
257 Self {
258 width: 960.0,
259 padding: Insets::symmetric(32.0, 40.0),
260 scale: 2.0,
261 theme: Theme::light(),
262 fonts: FontHandle::shared_default(),
263 format: OutputFormat::Png,
264 images: HashMap::new(),
265 header: None,
266 footer: None,
267 }
268 }
269}
270
271impl RenderOptions {
272 pub fn with_width(mut self, w: f32) -> Self {
274 self.width = w;
275 self
276 }
277 pub fn with_padding(mut self, p: Insets) -> Self {
279 self.padding = p;
280 self
281 }
282 pub fn with_theme(mut self, t: Theme) -> Self {
284 self.theme = t;
285 self
286 }
287 pub fn with_fonts(mut self, f: FontHandle) -> Self {
289 self.fonts = f;
290 self
291 }
292 pub fn with_scale(mut self, s: f32) -> Self {
294 self.scale = s.clamp(0.25, 8.0);
295 self
296 }
297 pub fn fast(self) -> Self {
299 self.with_scale(1.0)
300 }
301 pub fn standard(self) -> Self {
303 self.with_scale(1.5)
304 }
305 pub fn sharp(self) -> Self {
307 self.with_scale(2.0)
308 }
309 pub fn ultra(self) -> Self {
311 self.with_scale(3.0)
312 }
313 pub fn with_header(self, text: impl Into<String>) -> Self {
315 self.with_header_chrome(PageChrome::new(text))
316 }
317 pub fn with_header_chrome(mut self, c: PageChrome) -> Self {
319 self.header = Some(c);
320 self
321 }
322 pub fn with_footer(self, text: impl Into<String>) -> Self {
324 self.with_footer_chrome(PageChrome::new(text).align(Align::Center))
325 }
326 pub fn with_footer_chrome(mut self, c: PageChrome) -> Self {
328 self.footer = Some(c);
329 self
330 }
331 pub fn with_format(mut self, f: OutputFormat) -> Self {
333 self.format = f;
334 self
335 }
336 pub fn png(self) -> Self {
338 self.with_format(OutputFormat::Png)
339 }
340 pub fn png_fast(self) -> Self {
342 self.with_format(OutputFormat::PngFast)
343 }
344 pub fn webp(self) -> Self {
346 self.with_format(OutputFormat::Webp)
347 }
348 pub fn webp_or_png(self) -> Self {
350 self.with_format(OutputFormat::WebpOrPng)
351 }
352}