use cranpose_text::{SynthesizedStyle, TextLayout};
use cranpose_ui::text::{RangeStyle, Shadow, SpanStyle, TextDrawStyle, TextStyle};
use cranpose_ui_graphics::{Rect, whole};
use crate::{
geometry::blur_reach_for_minimum_scale,
glyph_mask::STROKE_MITER_LIMIT,
text_glyphs::{FAKE_BOLD_MAX_RATIO, FAKE_ITALIC_SKEW},
};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct InkSpread {
around: f32,
skew: f32,
}
impl InkSpread {
pub fn of(stroke: f32, synthesized: SynthesizedStyle) -> Self {
let embolden = synthesized
.bold_size
.map_or(0.0, |size| size * FAKE_BOLD_MAX_RATIO);
Self {
around: stroke.max(embolden) * 0.5 * STROKE_MITER_LIMIT,
skew: if synthesized.italic {
FAKE_ITALIC_SKEW
} else {
0.0
},
}
}
pub fn of_text(
style: &TextStyle,
spans: &[RangeStyle<SpanStyle>],
layout: &TextLayout,
) -> Self {
let styles =
|| std::iter::once(&style.span_style).chain(spans.iter().map(|span| &span.item));
let stroke = styles().map(stroke_width).fold(0.0, f32::max);
let shadow = styles()
.filter_map(|span| span.shadow)
.map(shadow_reach)
.fold(0.0, f32::max);
let glyphs = Self::of(stroke, layout.synthesized_style());
Self {
around: glyphs.around + shadow,
..glyphs
}
}
}
fn shadow_reach(shadow: Shadow) -> f32 {
let blur = if shadow.blur_radius > 0.0 {
blur_reach_for_minimum_scale(shadow.blur_radius, 1.0)
} else {
0.0
};
shadow.offset.x.abs().max(shadow.offset.y.abs()) + blur
}
fn stroke_width(style: &SpanStyle) -> f32 {
match style.draw_style {
Some(TextDrawStyle::Stroke { width }) if width.is_finite() && width > 0.0 => width,
_ => 0.0,
}
}
#[inline]
pub fn glyph_part(rect: Rect, layout: &TextLayout, scale: f32, spread: InkSpread) -> Rect {
let reach = reach_from_corner((rect, layout, scale), spread);
let left = whole::floor(reach.x);
Rect {
x: rect.x + left,
y: rect.y + reach.y,
width: whole::ceil(reach.x + reach.width) - left,
height: reach.height,
}
}
pub(crate) fn glyph_bounds(rect: Rect, layout: &TextLayout, spread: InkSpread) -> Rect {
let reach = reach_from_corner((rect, layout, 1.0), spread);
rect.union(Rect {
x: rect.x + reach.x,
y: rect.y + reach.y,
..reach
})
}
#[inline]
fn reach_from_corner((rect, layout, scale): (Rect, &TextLayout, f32), spread: InkSpread) -> Rect {
let InkSpread { around, skew } = spread;
let ink = layout.glyph_reach();
let around = around * scale;
let top = ink.y * scale;
let bottom = ((ink.y + ink.height) * scale).max(rect.height);
let across = around + skew * (bottom - top);
Rect {
x: ink.x * scale - across,
y: top - around,
width: ink.width * scale + 2.0 * across,
height: bottom - top + 2.0 * around,
}
}