use crate::sugarloaf::primitives::SugarCursor;
use crate::Sugar;
use crate::SugarDecoration;
use crate::SugarStyle;
use swash::text::cluster::CharInfo;
use swash::Setting;
use swash::{Stretch, Style, Weight};
#[derive(Copy, Debug, Clone)]
pub struct FragmentData {
pub span: usize,
pub break_shaping: bool,
pub start: usize,
pub end: usize,
pub vars: FontSettingKey,
}
#[derive(Copy, Debug, Clone)]
pub struct ItemData {
pub start: usize,
pub end: usize,
pub vars: FontSettingKey,
}
#[derive(Default)]
pub struct BuilderLineText {
pub content: Vec<char>,
pub frags: Vec<u32>,
pub spans: Vec<usize>,
pub info: Vec<CharInfo>,
pub offsets: Vec<u32>,
}
#[derive(Default)]
pub struct BuilderLine {
pub text: BuilderLineText,
pub fragments: Vec<FragmentData>,
pub items: Vec<ItemData>,
pub styles: Vec<FragmentStyle>,
pub hash: Option<u64>,
}
#[derive(Default)]
pub struct BuilderState {
pub lines: Vec<BuilderLine>,
pub vars: FontSettingCache<f32>,
pub scale: f32,
}
impl BuilderState {
pub fn new() -> Self {
let mut lines = vec![BuilderLine::default()];
lines[0].styles.push(FragmentStyle::default());
Self {
lines,
..BuilderState::default()
}
}
#[inline]
pub fn new_line(&mut self) {
self.lines.push(BuilderLine::default());
let last = self.lines.len() - 1;
self.lines[last]
.styles
.push(FragmentStyle::scaled_default(self.scale));
}
#[inline]
pub fn current_line(&self) -> usize {
let size = self.lines.len();
if size == 0 {
0
} else {
size - 1
}
}
#[inline]
pub fn clear(&mut self) {
self.lines.clear();
self.vars.clear();
}
#[inline]
pub fn begin(&mut self) {
self.lines.push(BuilderLine::default());
}
}
pub type FontSettingKey = u32;
#[derive(Default)]
pub struct FontSettingCache<T: Copy + PartialOrd + PartialEq> {
settings: Vec<Setting<T>>,
lists: Vec<FontSettingList>,
tmp: Vec<Setting<T>>,
}
impl<T: Copy + PartialOrd + PartialEq> FontSettingCache<T> {
pub fn get(&self, key: u32) -> &[Setting<T>] {
if key == !0 {
&[]
} else {
self.lists
.get(key as usize)
.map(|list| list.get(&self.settings))
.unwrap_or(&[])
}
}
pub fn clear(&mut self) {
self.settings.clear();
self.lists.clear();
self.tmp.clear();
}
}
pub const EMPTY_FONT_SETTINGS: FontSettingKey = !0;
#[derive(Copy, Clone)]
struct FontSettingList {
pub start: u32,
pub end: u32,
}
impl FontSettingList {
pub fn get<T>(self, elements: &[T]) -> &[T] {
elements
.get(self.start as usize..self.end as usize)
.unwrap_or(&[])
}
}
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Debug, Default)]
pub enum UnderlineShape {
#[default]
Regular = 0,
Dotted = 1,
Dashed = 2,
Curly = 3,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct UnderlineInfo {
pub offset: f32,
pub size: f32,
pub is_doubled: bool,
pub shape: UnderlineShape,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum FragmentStyleDecoration {
Underline(UnderlineInfo),
Strikethrough,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct FragmentStyle {
pub font: usize,
pub width: f32,
pub font_attrs: (Stretch, Weight, Style),
pub font_size: f32,
pub color: [f32; 4],
pub background_color: Option<[f32; 4]>,
pub font_vars: FontSettingKey,
pub letter_spacing: f32,
pub word_spacing: f32,
pub line_spacing: f32,
pub decoration: Option<FragmentStyleDecoration>,
pub decoration_color: Option<[f32; 4]>,
pub cursor: SugarCursor,
}
impl Default for FragmentStyle {
fn default() -> Self {
Self {
font: 0,
width: 1.0,
font_attrs: (Stretch::NORMAL, Weight::NORMAL, Style::Normal),
font_size: 16.,
font_vars: EMPTY_FONT_SETTINGS,
letter_spacing: 0.,
word_spacing: 0.,
line_spacing: 1.,
color: [1.0, 1.0, 1.0, 1.0],
background_color: None,
cursor: SugarCursor::Disabled,
decoration: None,
decoration_color: None,
}
}
}
impl FragmentStyle {
pub fn scaled_default(scale: f32) -> Self {
Self {
font: 0,
width: 1.0,
font_attrs: (Stretch::NORMAL, Weight::NORMAL, Style::Normal),
font_size: 16. * scale,
font_vars: EMPTY_FONT_SETTINGS,
letter_spacing: 0.,
word_spacing: 0.,
line_spacing: 1.,
color: [1.0, 1.0, 1.0, 1.0],
background_color: None,
cursor: SugarCursor::Disabled,
decoration: None,
decoration_color: None,
}
}
}
impl From<&Sugar> for FragmentStyle {
fn from(sugar: &Sugar) -> Self {
let mut style = FragmentStyle::default();
match sugar.style {
SugarStyle::BoldItalic => {
style.font_attrs.1 = Weight::BOLD;
style.font_attrs.2 = Style::Italic;
}
SugarStyle::Bold => {
style.font_attrs.1 = Weight::BOLD;
}
SugarStyle::Italic => {
style.font_attrs.2 = Style::Italic;
}
SugarStyle::Disabled => {}
}
let mut has_underline_cursor = false;
match sugar.cursor {
SugarCursor::Underline(cursor_color) => {
style.decoration =
Some(FragmentStyleDecoration::Underline(UnderlineInfo {
offset: -1.0,
size: -1.0,
is_doubled: false,
shape: UnderlineShape::Regular,
}));
style.decoration_color = Some(cursor_color);
has_underline_cursor = true;
}
SugarCursor::Block(cursor_color) => {
style.cursor = SugarCursor::Block(cursor_color);
}
SugarCursor::Caret(cursor_color) => {
style.cursor = SugarCursor::Caret(cursor_color);
}
SugarCursor::Disabled => {}
}
match &sugar.decoration {
SugarDecoration::Underline => {
if !has_underline_cursor {
style.decoration =
Some(FragmentStyleDecoration::Underline(UnderlineInfo {
offset: -2.0,
size: 2.0,
is_doubled: false,
shape: UnderlineShape::Regular,
}));
}
}
SugarDecoration::Strikethrough => {
style.decoration = Some(FragmentStyleDecoration::Strikethrough);
}
SugarDecoration::DoubleUnderline => {
style.decoration =
Some(FragmentStyleDecoration::Underline(UnderlineInfo {
offset: -4.0,
size: 1.0,
is_doubled: true,
shape: UnderlineShape::Regular,
}));
}
SugarDecoration::DottedUnderline => {
style.decoration =
Some(FragmentStyleDecoration::Underline(UnderlineInfo {
offset: -2.0,
size: 2.0,
is_doubled: false,
shape: UnderlineShape::Dotted,
}));
}
SugarDecoration::DashedUnderline => {
style.decoration =
Some(FragmentStyleDecoration::Underline(UnderlineInfo {
offset: -2.0,
size: 2.0,
is_doubled: false,
shape: UnderlineShape::Dashed,
}));
}
SugarDecoration::CurlyUnderline => {
style.decoration =
Some(FragmentStyleDecoration::Underline(UnderlineInfo {
offset: -2.0,
size: 1.0,
is_doubled: false,
shape: UnderlineShape::Curly,
}));
}
SugarDecoration::Disabled => {}
}
style.color = sugar.foreground_color;
style.background_color = sugar.background_color;
if let Some(decoration_color) = sugar.decoration_color {
style.decoration_color = Some(decoration_color);
}
style
}
}