use eframe::egui::{Align, Color32, FontFamily, FontId, Stroke, TextFormat};
use odox_core::{Color, Position, TextProperties};
use crate::fonts::{Variant, family_of};
pub const DEFAULT_SIZE: f32 = 12.0;
const SCRIPT_SCALE: f32 = 0.58;
#[derive(Debug, Clone, Copy)]
pub struct Palette {
pub paper: Color32,
pub ink: Color32,
pub link: Color32,
}
impl Default for Palette {
fn default() -> Self {
Self {
paper: Color32::from_rgb(0xff, 0xff, 0xff),
ink: Color32::from_rgb(0x00, 0x00, 0x00),
link: Color32::from_rgb(0x1a, 0x5f, 0xb4),
}
}
}
impl Palette {
pub fn for_theme(dark_mode: bool) -> Self {
if dark_mode {
Self {
paper: Color32::from_rgb(0x1e, 0x1e, 0x1e),
ink: Color32::from_rgb(0xd4, 0xd4, 0xd4),
link: Color32::from_rgb(0x37, 0x94, 0xff),
}
} else {
Self::default()
}
}
}
pub fn text_format(
properties: &TextProperties,
inherited: f32,
zoom: f32,
palette: Palette,
) -> TextFormat {
let size = match properties.size {
Some(measure) => measure.resolve(inherited),
None => inherited,
};
let variant = Variant {
bold: properties.bold.unwrap_or(false),
italic: properties.italic.unwrap_or(false),
};
let family = match &properties.font_family {
Some(name) if !name.is_empty() => family_of(name, variant),
_ => FontFamily::Proportional,
};
let (scale, valign) = match properties.position {
Some(Position::Super) => (SCRIPT_SCALE, Align::TOP),
Some(Position::Sub) => (SCRIPT_SCALE, Align::BOTTOM),
_ => (1.0, Align::BOTTOM),
};
let color = properties.color.map_or(palette.ink, color32);
let line = Stroke::new((size * zoom * 0.06).max(1.0), color);
TextFormat {
font_id: FontId::new(size * zoom * scale, family),
color,
background: properties.background.map_or(Color32::TRANSPARENT, color32),
underline: if properties.underline.unwrap_or(false) {
line
} else {
Stroke::NONE
},
strikethrough: if properties.strike.unwrap_or(false) {
line
} else {
Stroke::NONE
},
valign,
italics: false,
..TextFormat::default()
}
}
pub fn link_format(
properties: &TextProperties,
inherited: f32,
zoom: f32,
palette: Palette,
) -> TextFormat {
let mut format = text_format(properties, inherited, zoom, palette);
format.color = palette.link;
format.underline = Stroke::new(format.underline.width.max(1.0), format.color);
format
}
pub fn size_of(properties: &TextProperties, inherited: f32) -> f32 {
properties
.size
.map_or(inherited, |measure| measure.resolve(inherited))
}
pub fn color32(color: Color) -> Color32 {
Color32::from_rgb(color.r, color.g, color.b)
}
pub fn match_fill(current: bool) -> Color32 {
if current {
Color32::from_rgb(255, 150, 40)
} else {
Color32::from_rgb(250, 220, 90)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn navy() -> Color {
Color {
r: 0x00,
g: 0x00,
b: 0x80,
}
}
#[test]
fn for_theme_light_is_the_default_palette() {
let light = Palette::for_theme(false);
let default = Palette::default();
assert_eq!(light.paper, default.paper);
assert_eq!(light.ink, default.ink);
assert_eq!(light.link, default.link);
}
#[test]
fn for_theme_dark_moves_paper_ink_and_link_together() {
let dark = Palette::for_theme(true);
let light = Palette::for_theme(false);
assert_ne!(dark.paper, light.paper);
assert_ne!(dark.ink, light.ink);
assert_ne!(dark.link, light.link);
}
#[test]
fn text_format_keeps_a_documents_own_colour_in_either_theme() {
let properties = TextProperties {
color: Some(navy()),
..TextProperties::default()
};
for dark_mode in [false, true] {
let format = text_format(
&properties,
DEFAULT_SIZE,
1.0,
Palette::for_theme(dark_mode),
);
assert_eq!(format.color, color32(navy()));
}
}
#[test]
fn text_format_falls_back_to_the_palettes_ink_when_unset() {
let properties = TextProperties::default();
let palette = Palette::for_theme(true);
let format = text_format(&properties, DEFAULT_SIZE, 1.0, palette);
assert_eq!(format.color, palette.ink);
}
#[test]
fn link_format_overrides_a_documents_own_resolved_colour() {
let properties = TextProperties {
color: Some(navy()),
..TextProperties::default()
};
let palette = Palette::for_theme(true);
let format = link_format(&properties, DEFAULT_SIZE, 1.0, palette);
assert_eq!(format.color, palette.link);
assert_ne!(format.color, color32(navy()));
}
#[test]
fn link_format_underline_follows_the_link_colour_not_the_old_text_colour() {
let properties = TextProperties {
color: Some(navy()),
underline: Some(true),
..TextProperties::default()
};
let palette = Palette::for_theme(true);
let format = link_format(&properties, DEFAULT_SIZE, 1.0, palette);
assert_eq!(format.underline.color, palette.link);
}
#[test]
fn link_format_underlines_a_link_with_no_styling_at_all() {
let properties = TextProperties::default();
let palette = Palette::for_theme(false);
let format = link_format(&properties, DEFAULT_SIZE, 1.0, palette);
assert_eq!(format.color, palette.link);
assert!(format.underline.width >= 1.0);
}
}