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 {
103 Self {
104 background: Color::rgb(0, 0, 0),
105 text: Color::rgb(0, 0, 0),
106 accent: Color::rgb(0, 0, 0),
107 muted: Color::rgb(0, 0, 0),
108 code_bg: Color::rgb(0, 0, 0),
109 code_text: Color::rgb(0, 0, 0),
110 highlight: Color::rgb(0, 0, 0),
111 border: Color::rgb(0, 0, 0),
112 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,
117 line_height: 1.5,
118 heading_scale: [2.0, 1.6, 1.35, 1.15, 1.0, 0.9],
119 }
120 }
121}
122
123impl Default for Theme {
124 fn default() -> Self {
125 Self::light()
126 }
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131pub enum OutputFormat {
132 Png,
134 PngFast,
136 Webp,
138 WebpOrPng,
141}
142
143#[derive(Clone)]
145pub struct RenderOptions {
146 pub width: f32,
148 pub padding: Insets,
150 pub scale: f32,
152 pub theme: Theme,
154 pub fonts: FontHandle,
156 pub format: OutputFormat,
158 pub images: HashMap<String, Vec<u8>>,
160 pub header: Option<PageChrome>,
162 pub footer: Option<PageChrome>,
164}
165
166#[derive(Clone, Debug)]
170pub struct PageChrome {
171 pub inlines: Vec<Inline>,
173 pub trailing: Option<Vec<Inline>>,
176 pub align: Align,
178 pub color: Option<Color>,
180 pub size: f32,
182 pub rule: bool,
184 pub band: Option<Color>,
187}
188
189impl PageChrome {
190 pub fn new(text: impl Into<String>) -> Self {
192 Self::from_inlines(vec![Inline::Text { text: text.into(), style: TextStyle::default() }])
193 }
194
195 pub fn rich<R>(f: impl FnOnce(&mut ParaBuilder) -> R) -> Self {
198 let mut pb = ParaBuilder::new();
199 let _ = f(&mut pb);
200 Self::from_inlines(pb.into_inlines())
201 }
202
203 fn from_inlines(inlines: Vec<Inline>) -> Self {
204 Self {
205 inlines,
206 trailing: None,
207 align: Align::Left,
208 color: None,
209 size: 0.72,
210 rule: true,
211 band: None,
212 }
213 }
214
215 pub fn trailing<R>(mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> Self {
217 let mut pb = ParaBuilder::new();
218 let _ = f(&mut pb);
219 self.trailing = Some(pb.into_inlines());
220 self
221 }
222
223 pub fn band(mut self, hex: &str) -> Self {
225 if let Some(c) = Color::hex(hex) {
226 self.band = Some(c);
227 }
228 self
229 }
230 pub fn align(mut self, a: Align) -> Self {
232 self.align = a;
233 self
234 }
235 pub fn color(mut self, hex: &str) -> Self {
237 if let Some(c) = Color::hex(hex) {
238 self.color = Some(c);
239 }
240 self
241 }
242 pub fn size(mut self, mult: f32) -> Self {
244 if mult.is_finite() && mult > 0.0 {
245 self.size = mult;
246 }
247 self
248 }
249 pub fn no_rule(mut self) -> Self {
251 self.rule = false;
252 self
253 }
254}
255
256impl Default for RenderOptions {
257 fn default() -> Self {
258 Self {
259 width: 960.0,
260 padding: Insets::symmetric(32.0, 40.0),
261 scale: 2.0,
262 theme: Theme::light(),
263 fonts: FontHandle::shared_default(),
264 format: OutputFormat::Png,
265 images: HashMap::new(),
266 header: None,
267 footer: None,
268 }
269 }
270}
271
272impl RenderOptions {
273 pub fn with_width(mut self, w: f32) -> Self {
275 self.width = w;
276 self
277 }
278 pub fn with_padding(mut self, p: Insets) -> Self {
280 self.padding = p;
281 self
282 }
283 pub fn with_theme(mut self, t: Theme) -> Self {
285 self.theme = t;
286 self
287 }
288 pub fn with_fonts(mut self, f: FontHandle) -> Self {
290 self.fonts = f;
291 self
292 }
293 pub fn with_scale(mut self, s: f32) -> Self {
295 self.scale = s.clamp(0.25, 8.0);
296 self
297 }
298 pub fn fast(self) -> Self {
300 self.with_scale(1.0)
301 }
302 pub fn standard(self) -> Self {
304 self.with_scale(1.5)
305 }
306 pub fn sharp(self) -> Self {
308 self.with_scale(2.0)
309 }
310 pub fn ultra(self) -> Self {
312 self.with_scale(3.0)
313 }
314 pub fn with_header(self, text: impl Into<String>) -> Self {
316 self.with_header_chrome(PageChrome::new(text))
317 }
318 pub fn with_header_chrome(mut self, c: PageChrome) -> Self {
320 self.header = Some(c);
321 self
322 }
323 pub fn with_footer(self, text: impl Into<String>) -> Self {
325 self.with_footer_chrome(PageChrome::new(text).align(Align::Center))
326 }
327 pub fn with_footer_chrome(mut self, c: PageChrome) -> Self {
329 self.footer = Some(c);
330 self
331 }
332 pub fn with_format(mut self, f: OutputFormat) -> Self {
334 self.format = f;
335 self
336 }
337 pub fn png(self) -> Self {
339 self.with_format(OutputFormat::Png)
340 }
341 pub fn png_fast(self) -> Self {
343 self.with_format(OutputFormat::PngFast)
344 }
345 pub fn webp(self) -> Self {
347 self.with_format(OutputFormat::Webp)
348 }
349 pub fn webp_or_png(self) -> Self {
351 self.with_format(OutputFormat::WebpOrPng)
352 }
353}