use crate::core::{Bounds, Corners, Pixels, Point, Rgba, Size};
use crate::gpu::atlas::{AtlasTile, TextureAtlas};
use crate::gpu::scene::{MonochromeSprite, Quad, Scene, Underline};
use crate::gpu::text::{CODE_FLAG, STRIKE_FLAG, ShapedText, TextSystem, UNDERLINE_FLAG};
use crate::gpu::text_crisp;
use cosmic_text::{CacheKey, SwashContent};
#[derive(Clone, Copy)]
pub(crate) struct GlyphTile {
tile: AtlasTile,
left: i32,
top: i32,
is_color: bool,
}
#[derive(Clone, Copy)]
pub struct TextPlacement {
pub origin: Point,
pub color: Rgba,
pub clip: Bounds,
pub scale: f32,
}
pub struct GlyphTarget<'a> {
pub queue: Option<&'a wgpu::Queue>,
pub monochrome_atlas: &'a mut TextureAtlas,
pub polychrome_atlas: &'a mut TextureAtlas,
}
fn glyph_color(color: Option<cosmic_text::Color>, fallback: Rgba) -> Rgba {
match color {
Some(color) => {
let [red, green, blue, alpha] = color.as_rgba();
Rgba::new(red as f32 / 255.0, green as f32 / 255.0, blue as f32 / 255.0, alpha as f32 / 255.0 * fallback.a)
}
None => fallback,
}
}
impl TextSystem {
pub fn paint(
&mut self,
shaped: &ShapedText,
placement: &TextPlacement,
target: &mut GlyphTarget,
scene: &mut Scene,
) {
let TextPlacement { origin, color, clip, scale } = *placement;
for run in shaped.buffer.layout_runs() {
paint_code_chips(&run, placement, scene);
let offset = (origin.x.0 * scale, (origin.y.0 + run.line_y) * scale);
for glyph in run.glyphs {
let tint = glyph_color(glyph.color_opt, color);
if glyph.metadata & UNDERLINE_FLAG != 0 {
let underline = Bounds::new(
Point::new(Pixels(origin.x.0 + glyph.x), Pixels(origin.y.0 + run.line_y + 1.0)),
Size::new(Pixels(glyph.w), Pixels(2.0)),
);
scene.insert_underline(Underline::new(underline, clip, tint, 1.0, false));
}
if glyph.metadata & STRIKE_FLAG != 0 {
let middle = origin.y.0 + run.line_y - glyph.font_size * 0.3;
let strike = Bounds::new(
Point::new(Pixels(origin.x.0 + glyph.x), Pixels(middle)),
Size::new(Pixels(glyph.w), Pixels(1.0)),
);
scene.insert_quad(Quad::new(strike, clip, tint));
}
let mut physical = glyph.physical(offset, scale);
let crisp = text_crisp::is_crisp(scale);
if crisp {
physical.x += text_crisp::snap_x(&mut physical.cache_key.x_bin);
}
let Some(entry) = self.glyph_tile(physical.cache_key, crisp, target) else { continue };
let bounds = Bounds::new(
Point::new(
Pixels((physical.x + entry.left) as f32 / scale),
Pixels((physical.y - entry.top) as f32 / scale),
),
Size::new(Pixels(entry.tile.width as f32 / scale), Pixels(entry.tile.height as f32 / scale)),
);
if entry.is_color {
let sprite = MonochromeSprite::new(bounds, clip, entry.tile.uv, Rgba::new(1.0, 1.0, 1.0, tint.a));
scene.insert_polychrome(sprite);
} else {
scene.insert_monochrome(MonochromeSprite::new(bounds, clip, entry.tile.uv, tint));
}
}
}
}
fn glyph_tile(&mut self, key: CacheKey, crisp: bool, target: &mut GlyphTarget) -> Option<GlyphTile> {
if let Some(cached) = self.tiles.get(&(key, crisp)) {
return *cached;
}
let image = self.swash_cache.get_image_uncached(&mut self.font_system, key);
let entry = image.and_then(|image| {
let (width, height) = (image.placement.width, image.placement.height);
if width == 0 || height == 0 {
return None;
}
let (tile, is_color) = match image.content {
SwashContent::Mask => {
let mut mask = image.data;
if crisp {
text_crisp::boost(&mut mask);
}
(target.monochrome_atlas.insert(target.queue, width, height, &mask)?, false)
}
SwashContent::Color => {
let premultiplied = premultiply_rgba(&image.data);
(target.polychrome_atlas.insert(target.queue, width, height, &premultiplied)?, true)
}
SwashContent::SubpixelMask => return None,
};
Some(GlyphTile { tile, left: image.placement.left, top: image.placement.top, is_color })
});
self.tiles.insert((key, crisp), entry);
entry
}
}
fn paint_code_chips(run: &cosmic_text::LayoutRun, placement: &TextPlacement, scene: &mut Scene) {
const PAD: f32 = 2.0;
let mut chip: Option<(f32, f32)> = None;
let flush = |chip: Option<(f32, f32)>, scene: &mut Scene| {
let Some((start, end)) = chip else { return };
let bounds = Bounds::new(
Point::new(Pixels(placement.origin.x.0 + start - PAD), Pixels(placement.origin.y.0 + run.line_top + 1.0)),
Size::new(Pixels(end - start + 2.0 * PAD), Pixels((run.line_height - 2.0).max(1.0))),
);
let quad =
Quad::new(bounds, placement.clip, placement.color.with_alpha(0.1)).corners(Corners::all(Pixels(3.0)));
scene.insert_quad(quad);
};
for glyph in run.glyphs {
if glyph.metadata & CODE_FLAG == 0 {
flush(chip.take(), scene);
continue;
}
chip = Some(chip.map_or((glyph.x, glyph.x + glyph.w), |(start, _)| (start, glyph.x + glyph.w)));
}
flush(chip, scene);
}
pub(crate) fn premultiply_rgba(straight: &[u8]) -> Vec<u8> {
straight
.as_chunks::<4>()
.0
.iter()
.flat_map(|pixel| {
let alpha = pixel[3] as u16;
[
((pixel[0] as u16 * alpha) / 255) as u8,
((pixel[1] as u16 * alpha) / 255) as u8,
((pixel[2] as u16 * alpha) / 255) as u8,
pixel[3],
]
})
.collect()
}