use std::cell::RefCell;
use std::rc::Rc;
use cosmic_text::{CacheKey, FontSystem};
use guillotiere::AllocId;
use crate::color::{Premultiplied, sRGB32};
use crate::graphics::{GlyphCache, GlyphRegion};
use crate::render::atlas::{Atlas, Size};
use crate::render::compositor::{CompositorView, DataFlags};
use crate::{Error, PxRect};
use swash::scale::{Render, ScaleContext, Source, StrikeWith};
use swash::zeno::{Format, Vector};
pub use swash::scale::image::{Content, Image};
pub use swash::zeno::{Angle, Command, Placement, Transform};
pub struct Instance {
pub text_buffer: Rc<RefCell<cosmic_text::Buffer>>,
pub padding: std::cell::Cell<crate::PxPerimeter>,
}
impl Instance {
pub fn get_glyph(key: CacheKey, glyphs: &GlyphCache) -> Option<&GlyphRegion> {
glyphs.get(&key)
}
pub fn draw_glyph(
font_system: &mut FontSystem,
context: &mut ScaleContext,
cache_key: CacheKey,
) -> Option<Image> {
let font = match font_system.get_font(cache_key.font_id, cache_key.font_weight) {
Some(some) => some,
None => {
debug_assert!(false, "did not find font {:?}", cache_key.font_id);
return None;
}
};
let mut scaler = context
.builder(font.as_swash())
.size(f32::from_bits(cache_key.font_size_bits))
.hint(true)
.build();
let offset = Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
Render::new(&[
Source::ColorOutline(0),
Source::ColorBitmap(StrikeWith::BestFit),
Source::Outline,
])
.format(Format::Alpha)
.offset(offset)
.render(&mut scaler, cache_key.glyph_id)
}
pub fn write_glyph(
key: CacheKey,
font_system: &mut FontSystem,
glyphs: &mut GlyphCache,
device: &wgpu::Device,
queue: &wgpu::Queue,
atlas: &mut Atlas,
cache: &mut ScaleContext,
) -> Result<(), Error> {
if glyphs.get(&key).is_some() {
return Ok(());
}
let Some(mut image) = Self::draw_glyph(font_system, cache, key) else {
return Err(Error::GlyphRenderFailure);
};
let region = if image.data.is_empty() {
super::atlas::Region {
id: AllocId::deserialize(u32::MAX),
uv: guillotiere::euclid::Box2D::zero(),
index: 0,
}
} else {
atlas.reserve(
device,
Size::new(image.placement.width as i32, image.placement.height as i32),
None,
None,
)?
};
if !image.data.is_empty() {
match image.content {
Content::Mask => {
let mask = image.data;
image.data = mask
.iter()
.flat_map(|x| sRGB32::new(255, 255, 255, *x).as_f32().srgb_pre().as_bgra())
.collect();
}
Content::Color => {
for c in image.data.as_mut_slice().chunks_exact_mut(4) {
c.copy_from_slice(
&sRGB32::new(c[0], c[1], c[2], c[3])
.as_f32()
.srgb_pre()
.as_bgra(),
);
}
}
Content::SubpixelMask => {
let len = image.data.len() / 4;
let slice = image.data.as_mut_slice();
for i in 0..len {
let idx = i * 4;
slice.swap(idx, idx + 2);
}
}
}
atlas.queue_data(
&image.data,
®ion,
queue,
image.placement.width,
image.placement.height,
);
}
if let Some(mut old) = glyphs.insert(
key,
GlyphRegion {
offset: [image.placement.left, image.placement.top],
region,
},
) {
atlas.destroy(&mut old.region);
}
Ok(())
}
pub fn prepare_glyph(
x: i32,
y: i32,
line_y: f32,
scale_factor: f32,
color: cosmic_text::Color,
bounds_min_x: i32,
bounds_min_y: i32,
bounds_max_x: i32,
bounds_max_y: i32,
glyph: &GlyphRegion,
) -> Result<Option<super::compositor::Data>, Error> {
if glyph.region.uv.area() == 0 {
return Ok(None);
}
let mut x = x + glyph.offset[0];
let mut y = (line_y * scale_factor).round() as i32 + y - glyph.offset[1];
let mut u = glyph.region.uv.min.x;
let mut v = glyph.region.uv.min.y;
let mut width = glyph.region.uv.width();
let mut height = glyph.region.uv.height();
let max_x = x + width;
if x > bounds_max_x || max_x < bounds_min_x {
return Ok(None);
}
let max_y = y + height;
if y > bounds_max_y || max_y < bounds_min_y {
return Ok(None);
}
if x < bounds_min_x {
let right_shift = bounds_min_x - x;
x = bounds_min_x;
width = max_x - bounds_min_x;
u += right_shift;
}
if x + width > bounds_max_x {
width = bounds_max_x - x;
}
if y < bounds_min_y {
let bottom_shift = bounds_min_y - y;
y = bounds_min_y;
height = max_y - bounds_min_y;
v += bottom_shift;
}
if y + height > bounds_max_y {
height = bounds_max_y - y;
}
Ok(Some(super::compositor::Data {
pos: [x as f32, y as f32].into(),
dim: [width as f32, height as f32].into(),
uv: [u as f32, v as f32].into(),
uvdim: [width as f32, height as f32].into(),
color: u32::from_be_bytes(color.as_rgba()),
rotation: 0.0,
flags: DataFlags::new().with_tex(glyph.region.index).into(),
..Default::default()
}))
}
fn evaluate(
buffer: &cosmic_text::Buffer,
pos: crate::PxPoint,
scale: f32,
mut bounds: PxRect,
color: cosmic_text::Color,
compositor: &mut super::compositor::CompositorView<'_>,
font_system: &mut FontSystem,
glyphs: &mut GlyphCache,
device: &wgpu::Device,
queue: &wgpu::Queue,
atlas: &mut Atlas,
cache: &mut ScaleContext,
) -> Result<(), Error> {
bounds = bounds.intersect(compositor.current_clip());
let bounds_top = bounds.topleft().y as i32;
let bounds_bottom = bounds.bottomright().y as i32;
let bounds_min_x = (bounds.topleft().x as i32).max(0);
let bounds_min_y = bounds_top.max(0);
let bounds_max_x = bounds.bottomright().x as i32;
let bounds_max_y = bounds_bottom;
let is_run_visible = |run: &cosmic_text::LayoutRun| {
let start_y_physical = (pos.y + (run.line_top * scale)) as i32;
let end_y_physical = start_y_physical + (run.line_height * scale) as i32;
start_y_physical <= bounds_max_y && bounds_top <= end_y_physical
};
let layout_runs = buffer
.layout_runs()
.skip_while(|run| !is_run_visible(run))
.take_while(is_run_visible);
for run in layout_runs {
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((pos.x, pos.y), scale);
let glyphcolor = match glyph.color_opt {
Some(some) => some,
None => color,
};
Self::write_glyph(
physical_glyph.cache_key,
font_system,
glyphs,
device,
queue,
atlas,
cache,
)?;
if let Some(data) = Self::prepare_glyph(
physical_glyph.x,
physical_glyph.y,
run.line_y,
scale,
glyphcolor,
bounds_min_x,
bounds_min_y,
bounds_max_x,
bounds_max_y,
Self::get_glyph(physical_glyph.cache_key, glyphs)
.ok_or(Error::GlyphCacheFailure)?,
)? {
compositor.preprocessed(data);
}
}
}
Ok(())
}
}
impl super::Renderable for Instance {
fn render(
&self,
area: PxRect,
driver: &crate::graphics::Driver,
compositor: &mut CompositorView<'_>,
) -> Result<(), Error> {
let padding = self.padding.get();
Self::evaluate(
&self.text_buffer.borrow(),
area.topleft().add_size(&padding.topleft()),
1.0,
area,
cosmic_text::Color::rgb(255, 255, 255),
compositor,
&mut driver.font_system.write(),
&mut driver.glyphs.write(),
&driver.device,
&driver.queue,
&mut driver.atlas.write(),
&mut driver.swash_cache.write(),
)
}
}