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 font_emoji: String,
64 pub base_size: f32,
66 pub line_height: f32,
68 pub heading_scale: [f32; 6],
70}
71
72impl Theme {
73 pub fn light() -> Self {
75 Self {
76 background: Color::rgb(0xff, 0xff, 0xff),
77 text: Color::rgb(0x1f, 0x23, 0x28),
78 accent: Color::rgb(0x25, 0x63, 0xeb),
79 muted: Color::rgb(0x6e, 0x77, 0x81),
80 code_bg: Color::rgb(0xf3, 0xf4, 0xf6),
81 code_text: Color::rgb(0x1f, 0x23, 0x28),
82 highlight: Color::rgb(0xff, 0xf1, 0xa8),
83 border: Color::rgb(0xe5, 0xe7, 0xeb),
84 ..Self::common()
85 }
86 }
87
88 pub fn dark() -> Self {
90 Self {
91 background: Color::rgb(0x0d, 0x11, 0x17),
92 text: Color::rgb(0xe6, 0xed, 0xf3),
93 accent: Color::rgb(0x58, 0xa6, 0xff),
94 muted: Color::rgb(0x8b, 0x94, 0x9e),
95 code_bg: Color::rgb(0x16, 0x1b, 0x22),
96 code_text: Color::rgb(0xe6, 0xed, 0xf3),
97 highlight: Color::rgb(0x57, 0x4a, 0x1a),
98 border: Color::rgb(0x30, 0x36, 0x3d),
99 ..Self::common()
100 }
101 }
102
103 fn common() -> Self {
106 Self {
107 background: Color::rgb(0, 0, 0),
108 text: Color::rgb(0, 0, 0),
109 accent: Color::rgb(0, 0, 0),
110 muted: Color::rgb(0, 0, 0),
111 code_bg: Color::rgb(0, 0, 0),
112 code_text: Color::rgb(0, 0, 0),
113 highlight: Color::rgb(0, 0, 0),
114 border: Color::rgb(0, 0, 0),
115 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(), font_emoji: "Noto Color Emoji".to_string(), base_size: 30.0,
121 line_height: 1.5,
122 heading_scale: [2.0, 1.6, 1.35, 1.15, 1.0, 0.9],
123 }
124 }
125}
126
127impl Default for Theme {
128 fn default() -> Self {
129 Self::light()
130 }
131}
132
133#[derive(Clone, Copy, Debug, PartialEq, Eq)]
135pub enum OutputFormat {
136 Png,
138 PngFast,
140 Webp,
142 WebpOrPng,
145}
146
147#[derive(Clone)]
149pub struct RenderOptions {
150 pub width: f32,
152 pub padding: Insets,
154 pub scale: f32,
156 pub theme: Theme,
158 pub fonts: FontHandle,
160 pub format: OutputFormat,
162 pub images: HashMap<String, Vec<u8>>,
164 pub header: Option<PageChrome>,
166 pub footer: Option<PageChrome>,
168}
169
170#[derive(Clone, Debug)]
174pub struct PageChrome {
175 pub inlines: Vec<Inline>,
177 pub trailing: Option<Vec<Inline>>,
180 pub align: Align,
182 pub color: Option<Color>,
184 pub size: f32,
186 pub rule: bool,
188 pub band: Option<Color>,
191}
192
193impl PageChrome {
194 pub fn new(text: impl Into<String>) -> Self {
196 Self::from_inlines(vec![Inline::Text { text: text.into(), style: TextStyle::default() }])
197 }
198
199 pub fn rich<R>(f: impl FnOnce(&mut ParaBuilder) -> R) -> Self {
202 let mut pb = ParaBuilder::new();
203 let _ = f(&mut pb);
204 Self::from_inlines(pb.into_inlines())
205 }
206
207 fn from_inlines(inlines: Vec<Inline>) -> Self {
208 Self {
209 inlines,
210 trailing: None,
211 align: Align::Left,
212 color: None,
213 size: 0.72,
214 rule: true,
215 band: None,
216 }
217 }
218
219 pub fn trailing<R>(mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> Self {
221 let mut pb = ParaBuilder::new();
222 let _ = f(&mut pb);
223 self.trailing = Some(pb.into_inlines());
224 self
225 }
226
227 pub fn band(mut self, hex: &str) -> Self {
229 if let Some(c) = Color::hex(hex) {
230 self.band = Some(c);
231 }
232 self
233 }
234 pub fn align(mut self, a: Align) -> Self {
236 self.align = a;
237 self
238 }
239 pub fn color(mut self, hex: &str) -> Self {
241 if let Some(c) = Color::hex(hex) {
242 self.color = Some(c);
243 }
244 self
245 }
246 pub fn size(mut self, mult: f32) -> Self {
248 if mult.is_finite() && mult > 0.0 {
249 self.size = mult;
250 }
251 self
252 }
253 pub fn no_rule(mut self) -> Self {
255 self.rule = false;
256 self
257 }
258}
259
260impl Default for RenderOptions {
261 fn default() -> Self {
262 Self {
263 width: 960.0,
264 padding: Insets::symmetric(32.0, 40.0),
265 scale: 2.0,
266 theme: Theme::light(),
267 fonts: FontHandle::shared_default(),
268 format: OutputFormat::Png,
269 images: HashMap::new(),
270 header: None,
271 footer: None,
272 }
273 }
274}
275
276impl RenderOptions {
277 pub fn with_width(mut self, w: f32) -> Self {
279 self.width = w;
280 self
281 }
282 pub fn with_padding(mut self, p: Insets) -> Self {
284 self.padding = p;
285 self
286 }
287 pub fn with_theme(mut self, t: Theme) -> Self {
289 self.theme = t;
290 self
291 }
292 pub fn with_fonts(mut self, f: FontHandle) -> Self {
294 self.fonts = f;
295 self
296 }
297 pub fn with_scale(mut self, s: f32) -> Self {
299 self.scale = s.clamp(0.25, 8.0);
300 self
301 }
302 pub fn fast(self) -> Self {
304 self.with_scale(1.0)
305 }
306 pub fn standard(self) -> Self {
308 self.with_scale(1.5)
309 }
310 pub fn sharp(self) -> Self {
312 self.with_scale(2.0)
313 }
314 pub fn ultra(self) -> Self {
316 self.with_scale(3.0)
317 }
318 pub fn with_header(self, text: impl Into<String>) -> Self {
320 self.with_header_chrome(PageChrome::new(text))
321 }
322 pub fn with_header_chrome(mut self, c: PageChrome) -> Self {
324 self.header = Some(c);
325 self
326 }
327 pub fn with_footer(self, text: impl Into<String>) -> Self {
329 self.with_footer_chrome(PageChrome::new(text).align(Align::Center))
330 }
331 pub fn with_footer_chrome(mut self, c: PageChrome) -> Self {
333 self.footer = Some(c);
334 self
335 }
336 pub fn with_format(mut self, f: OutputFormat) -> Self {
338 self.format = f;
339 self
340 }
341 pub fn png(self) -> Self {
343 self.with_format(OutputFormat::Png)
344 }
345 pub fn png_fast(self) -> Self {
347 self.with_format(OutputFormat::PngFast)
348 }
349 pub fn webp(self) -> Self {
351 self.with_format(OutputFormat::Webp)
352 }
353 pub fn webp_or_png(self) -> Self {
355 self.with_format(OutputFormat::WebpOrPng)
356 }
357}