use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use agg_rust::basics::{is_vertex, FillingRule};
use agg_rust::color::Gray8;
use agg_rust::conv_curve::ConvCurve;
use agg_rust::conv_transform::ConvTransform;
use agg_rust::path_storage::PathStorage;
use agg_rust::pixfmt_gray::PixfmtGray8;
use agg_rust::rasterizer_scanline_aa::RasterizerScanlineAa;
use agg_rust::renderer_base::RendererBase;
use agg_rust::renderer_scanline::render_scanlines_aa_solid;
use agg_rust::rendering_buffer::RowAccessor;
use agg_rust::scanline_u::ScanlineU8;
use agg_rust::trans_affine::TransAffine;
use super::filter::{apply_5_tap_filter, apply_gray_collapse};
use crate::color::Color;
use crate::draw_ctx::FillRule;
use crate::text::{measure_text_metrics, shape_text, Font};
pub fn identity_xform() -> TransAffine {
TransAffine::new()
}
pub struct CachedLcdText {
pub pixels: Arc<Vec<u8>>,
pub width: u32,
pub height: u32,
pub baseline_x_in_mask: f64,
pub baseline_y_in_mask: f64,
}
const MASK_PAD: f64 = 2.0;
#[derive(Clone, PartialEq, Eq, Hash)]
struct LcdMaskKey {
text: String,
font_ptr: usize,
size_bits: u64,
width_bits: u64,
italic_bits: u64,
interval_bits: u64,
hint_y: bool,
faux_weight_bits: u64,
primary_weight_bits: u64,
gamma_bits: u64,
gray: bool,
}
struct LcdMaskEntry {
pixels: Arc<Vec<u8>>,
width: u32,
height: u32,
baseline_x_in_mask: f64,
baseline_y_in_mask: f64,
}
thread_local! {
static MASK_CACHE: RefCell<HashMap<LcdMaskKey, LcdMaskEntry>>
= RefCell::new(HashMap::new());
static MASK_LRU: RefCell<VecDeque<LcdMaskKey>>
= RefCell::new(VecDeque::new());
}
const MASK_CACHE_MAX: usize = 1024;
pub fn rasterize_text_lcd_cached(font: &Arc<Font>, text: &str, size: f64) -> CachedLcdText {
rasterize_text_mask_cached(font, text, size, false)
}
pub fn rasterize_text_gray_cached(font: &Arc<Font>, text: &str, size: f64) -> CachedLcdText {
rasterize_text_mask_cached(font, text, size, true)
}
fn rasterize_text_mask_cached(
font: &Arc<Font>,
text: &str,
size: f64,
gray: bool,
) -> CachedLcdText {
let width_now = crate::font_settings::current_width();
let italic_now = crate::font_settings::current_faux_italic();
let interval_now = crate::font_settings::current_interval();
let hint_y_now = crate::font_settings::hinting_enabled();
let fweight_now = crate::font_settings::current_faux_weight();
let pweight_now = crate::font_settings::current_primary_weight();
let gamma_now = crate::font_settings::current_gamma();
let key = LcdMaskKey {
text: text.to_string(),
font_ptr: Arc::as_ptr(font) as *const () as usize,
size_bits: size.to_bits(),
width_bits: width_now.to_bits(),
italic_bits: italic_now.to_bits(),
interval_bits: interval_now.to_bits(),
hint_y: hint_y_now,
faux_weight_bits: fweight_now.to_bits(),
primary_weight_bits: pweight_now.to_bits(),
gamma_bits: gamma_now.to_bits(),
gray,
};
let hit = MASK_CACHE.with(|m| {
m.borrow().get(&key).map(|e| CachedLcdText {
pixels: Arc::clone(&e.pixels),
width: e.width,
height: e.height,
baseline_x_in_mask: e.baseline_x_in_mask,
baseline_y_in_mask: e.baseline_y_in_mask,
})
});
if let Some(got) = hit {
MASK_LRU.with(|lru| {
let mut lru = lru.borrow_mut();
if let Some(pos) = lru.iter().position(|k| k == &key) {
lru.remove(pos);
}
lru.push_back(key);
});
return got;
}
let m = measure_text_metrics(font, text, size);
let width_slack = (width_now - 1.0).abs() * size;
let italic_slack = (italic_now.abs() / 3.0) * (m.ascent + m.descent);
let extra_pad = (width_slack + italic_slack).ceil();
let pad_x = MASK_PAD + extra_pad;
let mut ink_ascent = m.ascent;
let mut ink_descent = m.descent;
for ch in text.chars() {
if let Some((y_min, y_max)) = font.glyph_visual_bounds(ch, size) {
ink_ascent = ink_ascent.max(y_max);
ink_descent = ink_descent.max(-y_min);
}
}
let bw = (m.width + pad_x * 2.0).ceil().max(1.0) as u32;
let bh = (ink_ascent + ink_descent + MASK_PAD * 2.0).ceil().max(1.0) as u32;
let bx = pad_x;
let by_unhinted = MASK_PAD + ink_descent;
let by = if hint_y_now {
by_unhinted.round()
} else {
by_unhinted
};
let mask = if gray {
rasterize_gray_mask(font, text, size, bx, by, bw, bh, &TransAffine::new())
} else {
rasterize_lcd_mask(font, text, size, bx, by, bw, bh, &TransAffine::new())
};
let pixels = Arc::new(mask.data);
let entry = LcdMaskEntry {
pixels: Arc::clone(&pixels),
width: bw,
height: bh,
baseline_x_in_mask: bx,
baseline_y_in_mask: by,
};
MASK_CACHE.with(|m| m.borrow_mut().insert(key.clone(), entry));
MASK_LRU.with(|lru| {
let mut lru = lru.borrow_mut();
lru.push_back(key.clone());
while lru.len() > MASK_CACHE_MAX {
if let Some(old) = lru.pop_front() {
MASK_CACHE.with(|m| m.borrow_mut().remove(&old));
}
}
});
CachedLcdText {
pixels,
width: bw,
height: bh,
baseline_x_in_mask: bx,
baseline_y_in_mask: by,
}
}
pub struct LcdMask {
pub data: Vec<u8>, pub width: u32,
pub height: u32,
}
pub fn rasterize_lcd_mask(
font: &Font,
text: &str,
size: f64,
x: f64,
y: f64,
mask_w: u32,
mask_h: u32,
transform: &TransAffine,
) -> LcdMask {
rasterize_lcd_mask_multi(font, &[(text, x, y)], size, mask_w, mask_h, transform)
}
pub fn rasterize_lcd_mask_multi(
font: &Font,
spans: &[(&str, f64, f64)],
size: f64,
mask_w: u32,
mask_h: u32,
transform: &TransAffine,
) -> LcdMask {
let mut builder = LcdMaskBuilder::new(mask_w, mask_h);
builder.with_paths(transform, |add| {
for (text, x, y) in spans {
if text.is_empty() {
continue;
}
let (mut paths, _) = shape_text(font, text, size, *x, *y);
for path in paths.iter_mut() {
add(path);
}
}
});
builder.finalize()
}
#[allow(clippy::too_many_arguments)]
pub fn rasterize_gray_mask(
font: &Font,
text: &str,
size: f64,
x: f64,
y: f64,
mask_w: u32,
mask_h: u32,
transform: &TransAffine,
) -> LcdMask {
let mut builder = LcdMaskBuilder::new(mask_w, mask_h);
builder.with_paths(transform, |add| {
if !text.is_empty() {
let (mut paths, _) = shape_text(font, text, size, x, y);
for path in paths.iter_mut() {
add(path);
}
}
});
builder.finalize_gray()
}
pub fn rect_to_pixel_clip(rect: (f64, f64, f64, f64)) -> (i32, i32, i32, i32) {
let (x, y, w, h) = rect;
(
x.floor() as i32,
y.floor() as i32,
(x + w).ceil() as i32,
(y + h).ceil() as i32,
)
}
fn transformed_path_bbox(
path: &PathStorage,
transform: &TransAffine,
) -> Option<(f64, f64, f64, f64)> {
let mut min_x = f64::INFINITY;
let mut min_y = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut max_y = f64::NEG_INFINITY;
let mut any = false;
for i in 0..path.total_vertices() {
let mut x = 0.0;
let mut y = 0.0;
let cmd = path.vertex_idx(i, &mut x, &mut y);
if !is_vertex(cmd) {
continue;
}
transform.transform(&mut x, &mut y);
min_x = min_x.min(x);
min_y = min_y.min(y);
max_x = max_x.max(x);
max_y = max_y.max(y);
any = true;
}
if any {
Some((min_x, min_y, max_x, max_y))
} else {
None
}
}
pub fn build_bounded_mask(
buffer_w: u32,
buffer_h: u32,
path: &mut PathStorage,
transform: &TransAffine,
clip: Option<(f64, f64, f64, f64)>,
fill_rule: FillRule,
) -> Option<(LcdMask, i32, i32)> {
if buffer_w == 0 || buffer_h == 0 {
return None;
}
let (min_x, min_y, max_x, max_y) = transformed_path_bbox(path, transform)?;
const PAD: f64 = 2.0;
let mut x1 = (min_x - PAD).floor() as i32;
let mut y1 = (min_y - PAD).floor() as i32;
let mut x2 = (max_x + PAD).ceil() as i32;
let mut y2 = (max_y + PAD).ceil() as i32;
if let Some(c) = clip {
let (cx1, cy1, cx2, cy2) = rect_to_pixel_clip(c);
x1 = x1.max(cx1);
y1 = y1.max(cy1);
x2 = x2.min(cx2);
y2 = y2.min(cy2);
}
x1 = x1.max(0);
y1 = y1.max(0);
x2 = x2.min(buffer_w as i32);
y2 = y2.min(buffer_h as i32);
if x1 >= x2 || y1 >= y2 {
return None;
}
let bbox_x = x1;
let bbox_y = y1;
let bbox_w = (x2 - x1) as u32;
let bbox_h = (y2 - y1) as u32;
let mut local = *transform;
local.multiply(&TransAffine::new_translation(-bbox_x as f64, -bbox_y as f64));
let local_clip = clip.map(|(cx, cy, cw, ch)| (cx - bbox_x as f64, cy - bbox_y as f64, cw, ch));
let mut builder = LcdMaskBuilder::new(bbox_w, bbox_h)
.with_clip(local_clip)
.with_fill_rule(fill_rule);
builder.with_paths(&local, |add| {
add(path);
});
Some((builder.finalize(), bbox_x, bbox_y))
}
pub struct LcdMaskBuilder {
gray: Vec<u8>,
gray_w: u32,
gray_h: u32,
mask_w: u32,
mask_h: u32,
clip: Option<(f64, f64, f64, f64)>,
fill_rule: FillRule,
}
impl LcdMaskBuilder {
pub fn new(mask_w: u32, mask_h: u32) -> Self {
let gray_w = mask_w.saturating_mul(3);
let gray_h = mask_h;
let gray = vec![0u8; (gray_w as usize) * (gray_h as usize)];
Self {
gray,
gray_w,
gray_h,
mask_w,
mask_h,
clip: None,
fill_rule: FillRule::NonZero,
}
}
pub fn with_clip(mut self, clip: Option<(f64, f64, f64, f64)>) -> Self {
self.clip = clip;
self
}
pub fn with_fill_rule(mut self, fill_rule: FillRule) -> Self {
self.fill_rule = fill_rule;
self
}
pub fn with_paths<F>(&mut self, transform: &TransAffine, f: F)
where
F: FnOnce(&mut dyn FnMut(&mut PathStorage)),
{
rasterize_paths_into_gray(
&mut self.gray,
self.gray_w,
self.gray_h,
transform,
self.clip,
self.fill_rule,
f,
);
}
pub fn finalize(self) -> LcdMask {
if self.mask_w == 0 || self.mask_h == 0 {
return LcdMask {
data: Vec::new(),
width: self.mask_w,
height: self.mask_h,
};
}
let data = apply_5_tap_filter(&self.gray, self.gray_w, self.mask_w, self.mask_h);
LcdMask {
data,
width: self.mask_w,
height: self.mask_h,
}
}
pub fn finalize_gray(self) -> LcdMask {
if self.mask_w == 0 || self.mask_h == 0 {
return LcdMask {
data: Vec::new(),
width: self.mask_w,
height: self.mask_h,
};
}
let data = apply_gray_collapse(&self.gray, self.gray_w, self.mask_w, self.mask_h);
LcdMask {
data,
width: self.mask_w,
height: self.mask_h,
}
}
}
fn rasterize_paths_into_gray<F>(
gray: &mut [u8],
gray_w: u32,
gray_h: u32,
transform: &TransAffine,
clip: Option<(f64, f64, f64, f64)>,
fill_rule: FillRule,
f: F,
) where
F: FnOnce(&mut dyn FnMut(&mut PathStorage)),
{
if gray_w == 0 || gray_h == 0 {
return;
}
let stride = gray_w as i32;
let mut ra = RowAccessor::new();
unsafe {
ra.attach(gray.as_mut_ptr(), gray_w, gray_h, stride);
}
let pf = PixfmtGray8::new(&mut ra);
let mut rb = RendererBase::new(pf);
if let Some((cx, cy, cw, ch)) = clip {
let x1 = (cx.floor() as i32).saturating_mul(3);
let y1 = cy.floor() as i32;
let x2 = ((cx + cw).ceil() as i32).saturating_mul(3) - 1;
let y2 = (cy + ch).ceil() as i32 - 1;
rb.clip_box_i(x1, y1, x2, y2);
}
let mut ras = RasterizerScanlineAa::new();
ras.filling_rule(to_agg_fill_rule(fill_rule));
let mut sl = ScanlineU8::new();
let cov_color = Gray8::new_opaque(255);
let mut xform = *transform;
xform.sx *= 3.0;
xform.shx *= 3.0;
xform.tx *= 3.0;
let mut add = |path: &mut PathStorage| {
let mut curves = ConvCurve::new(path);
let mut tx = ConvTransform::new(&mut curves, xform);
ras.reset();
ras.add_path(&mut tx, 0);
render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, &cov_color);
};
f(&mut add);
}
fn to_agg_fill_rule(rule: FillRule) -> FillingRule {
match rule {
FillRule::NonZero => FillingRule::NonZero,
FillRule::EvenOdd => FillingRule::EvenOdd,
}
}
pub fn composite_lcd_mask(
dst_rgba: &mut [u8],
dst_w: u32,
dst_h: u32,
mask: &LcdMask,
src: Color,
dst_x: i32,
dst_y: i32,
) {
if mask.width == 0 || mask.height == 0 {
return;
}
let sa = src.a.clamp(0.0, 1.0);
let sr = src.r.clamp(0.0, 1.0);
let sg = src.g.clamp(0.0, 1.0);
let sb = src.b.clamp(0.0, 1.0);
let dst_w_i = dst_w as i32;
let dst_h_i = dst_h as i32;
let mw = mask.width as i32;
let mh = mask.height as i32;
for my in 0..mh {
let dy = dst_y + my;
if dy < 0 || dy >= dst_h_i {
continue;
}
for mx in 0..mw {
let dx = dst_x + mx;
if dx < 0 || dx >= dst_w_i {
continue;
}
let mi = ((my * mw + mx) * 3) as usize;
let cr = (mask.data[mi] as f32 / 255.0) * sa;
let cg = (mask.data[mi + 1] as f32 / 255.0) * sa;
let cb = (mask.data[mi + 2] as f32 / 255.0) * sa;
if cr == 0.0 && cg == 0.0 && cb == 0.0 {
continue;
}
let di = ((dy * dst_w_i + dx) * 4) as usize;
let dr = dst_rgba[di] as f32 / 255.0;
let dg = dst_rgba[di + 1] as f32 / 255.0;
let db = dst_rgba[di + 2] as f32 / 255.0;
let rr = sr * cr + dr * (1.0 - cr);
let rg = sg * cg + dg * (1.0 - cg);
let rbb = sb * cb + db * (1.0 - cb);
dst_rgba[di] = (rr * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
dst_rgba[di + 1] = (rg * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
dst_rgba[di + 2] = (rbb * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
}
}
}