use std::collections::HashMap;
use crate::build::ParaBuilder;
use crate::font::FontHandle;
use crate::model::{Align, Color, Inline, TextStyle};
#[derive(Clone, Copy, Debug)]
pub struct Insets {
pub top: f32,
pub right: f32,
pub bottom: f32,
pub left: f32,
}
impl Insets {
pub const fn all(v: f32) -> Self {
Self { top: v, right: v, bottom: v, left: v }
}
pub const fn symmetric(v: f32, h: f32) -> Self {
Self { top: v, right: h, bottom: v, left: h }
}
}
#[derive(Clone, Debug)]
pub struct Theme {
pub background: Color,
pub text: Color,
pub accent: Color,
pub muted: Color,
pub code_bg: Color,
pub code_text: Color,
pub highlight: Color,
pub border: Color,
pub font_sans: String,
pub font_serif: String,
pub font_mono: String,
pub font_kai: String,
pub font_emoji: String,
pub base_size: f32,
pub line_height: f32,
pub heading_scale: [f32; 6],
}
impl Theme {
pub fn light() -> Self {
Self {
background: Color::rgb(0xff, 0xff, 0xff),
text: Color::rgb(0x1f, 0x23, 0x28),
accent: Color::rgb(0x25, 0x63, 0xeb),
muted: Color::rgb(0x6e, 0x77, 0x81),
code_bg: Color::rgb(0xf3, 0xf4, 0xf6),
code_text: Color::rgb(0x1f, 0x23, 0x28),
highlight: Color::rgb(0xff, 0xf1, 0xa8),
border: Color::rgb(0xe5, 0xe7, 0xeb),
..Self::common()
}
}
pub fn dark() -> Self {
Self {
background: Color::rgb(0x0d, 0x11, 0x17),
text: Color::rgb(0xe6, 0xed, 0xf3),
accent: Color::rgb(0x58, 0xa6, 0xff),
muted: Color::rgb(0x8b, 0x94, 0x9e),
code_bg: Color::rgb(0x16, 0x1b, 0x22),
code_text: Color::rgb(0xe6, 0xed, 0xf3),
highlight: Color::rgb(0x57, 0x4a, 0x1a),
border: Color::rgb(0x30, 0x36, 0x3d),
..Self::common()
}
}
fn common() -> Self {
Self {
background: Color::rgb(0, 0, 0),
text: Color::rgb(0, 0, 0),
accent: Color::rgb(0, 0, 0),
muted: Color::rgb(0, 0, 0),
code_bg: Color::rgb(0, 0, 0),
code_text: Color::rgb(0, 0, 0),
highlight: Color::rgb(0, 0, 0),
border: Color::rgb(0, 0, 0),
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,
line_height: 1.5,
heading_scale: [2.0, 1.6, 1.35, 1.15, 1.0, 0.9],
}
}
}
impl Default for Theme {
fn default() -> Self {
Self::light()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OutputFormat {
Png,
PngFast,
Webp,
WebpOrPng,
}
#[derive(Clone)]
pub struct RenderOptions {
pub width: f32,
pub padding: Insets,
pub scale: f32,
pub theme: Theme,
pub fonts: FontHandle,
pub format: OutputFormat,
pub images: HashMap<String, Vec<u8>>,
pub header: Option<PageChrome>,
pub footer: Option<PageChrome>,
}
#[derive(Clone, Debug)]
pub struct PageChrome {
pub inlines: Vec<Inline>,
pub trailing: Option<Vec<Inline>>,
pub align: Align,
pub color: Option<Color>,
pub size: f32,
pub rule: bool,
pub band: Option<Color>,
}
impl PageChrome {
pub fn new(text: impl Into<String>) -> Self {
Self::from_inlines(vec![Inline::Text { text: text.into(), style: TextStyle::default() }])
}
pub fn rich<R>(f: impl FnOnce(&mut ParaBuilder) -> R) -> Self {
let mut pb = ParaBuilder::new();
let _ = f(&mut pb);
Self::from_inlines(pb.into_inlines())
}
fn from_inlines(inlines: Vec<Inline>) -> Self {
Self {
inlines,
trailing: None,
align: Align::Left,
color: None,
size: 0.72,
rule: true,
band: None,
}
}
pub fn trailing<R>(mut self, f: impl FnOnce(&mut ParaBuilder) -> R) -> Self {
let mut pb = ParaBuilder::new();
let _ = f(&mut pb);
self.trailing = Some(pb.into_inlines());
self
}
pub fn band(mut self, hex: &str) -> Self {
if let Some(c) = Color::hex(hex) {
self.band = Some(c);
}
self
}
pub fn align(mut self, a: Align) -> Self {
self.align = a;
self
}
pub fn color(mut self, hex: &str) -> Self {
if let Some(c) = Color::hex(hex) {
self.color = Some(c);
}
self
}
pub fn size(mut self, mult: f32) -> Self {
if mult.is_finite() && mult > 0.0 {
self.size = mult;
}
self
}
pub fn no_rule(mut self) -> Self {
self.rule = false;
self
}
}
impl Default for RenderOptions {
fn default() -> Self {
Self {
width: 960.0,
padding: Insets::symmetric(32.0, 40.0),
scale: 2.0,
theme: Theme::light(),
fonts: FontHandle::shared_default(),
format: OutputFormat::Png,
images: HashMap::new(),
header: None,
footer: None,
}
}
}
impl RenderOptions {
pub fn with_width(mut self, w: f32) -> Self {
self.width = w;
self
}
pub fn with_padding(mut self, p: Insets) -> Self {
self.padding = p;
self
}
pub fn with_theme(mut self, t: Theme) -> Self {
self.theme = t;
self
}
pub fn with_fonts(mut self, f: FontHandle) -> Self {
self.fonts = f;
self
}
pub fn with_scale(mut self, s: f32) -> Self {
self.scale = s.clamp(0.25, 8.0);
self
}
pub fn fast(self) -> Self {
self.with_scale(1.0)
}
pub fn standard(self) -> Self {
self.with_scale(1.5)
}
pub fn sharp(self) -> Self {
self.with_scale(2.0)
}
pub fn ultra(self) -> Self {
self.with_scale(3.0)
}
pub fn with_header(self, text: impl Into<String>) -> Self {
self.with_header_chrome(PageChrome::new(text))
}
pub fn with_header_chrome(mut self, c: PageChrome) -> Self {
self.header = Some(c);
self
}
pub fn with_footer(self, text: impl Into<String>) -> Self {
self.with_footer_chrome(PageChrome::new(text).align(Align::Center))
}
pub fn with_footer_chrome(mut self, c: PageChrome) -> Self {
self.footer = Some(c);
self
}
pub fn with_format(mut self, f: OutputFormat) -> Self {
self.format = f;
self
}
pub fn png(self) -> Self {
self.with_format(OutputFormat::Png)
}
pub fn png_fast(self) -> Self {
self.with_format(OutputFormat::PngFast)
}
pub fn webp(self) -> Self {
self.with_format(OutputFormat::Webp)
}
pub fn webp_or_png(self) -> Self {
self.with_format(OutputFormat::WebpOrPng)
}
}