use core::fmt;
use std::hash::{DefaultHasher, Hasher};
use glam::{Vec2, Vec4, u8vec2, vec2};
use parley::{Alignment, AlignmentOptions, Glyph, GlyphRun, Layout, PositionedLayoutItem};
use swash::scale::Scaler;
use swash::scale::image::Content;
use swash::zeno::Vector;
use crate::Color;
use crate::RRect;
use crate::common::upload_subtexture;
use crate::quadlas::Quadlas;
use crate::{DrawCommand, RawImage};
use super::Renderer;
use super::batch::glyph_quad::GlyphQuad;
use super::quadlas::{GlyphKey, InsertGlyphError};
#[derive(Clone)]
pub struct TextLayout {
layout: Layout<()>,
text: String,
alignment: Alignment,
color: Color,
real_size: Vec2,
}
impl fmt::Debug for TextLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TextLayout ({}x{})", self.real_size.x, self.real_size.y)
}
}
impl TextLayout {
pub fn new(layout: Layout<()>, text: String, alignment: Alignment, color: Color, scale_factor: f32) -> Self {
let mut layout = Self {
layout,
text,
alignment,
color,
real_size: Vec2::ZERO,
};
layout.set_max_advance(None, scale_factor);
layout
}
pub fn set_max_advance(&mut self, max_advance: Option<f32>, scale_factor: f32) {
let max_advance = max_advance.map(|x| x * scale_factor);
self.layout.break_all_lines(max_advance);
self.layout.align(
max_advance,
self.alignment,
AlignmentOptions {
align_when_overflowing: true,
},
);
self.update_size(scale_factor);
}
pub fn realign(&mut self, alignment: Alignment, scale_factor: f32) {
if self.alignment != alignment {
self.alignment = alignment;
self.layout.align(
None,
self.alignment,
AlignmentOptions {
align_when_overflowing: true,
},
);
self.update_size(scale_factor);
}
}
fn update_size(&mut self, scale_factor: f32) {
let width = self.layout.width();
let height = if let Some(last_line) = self.layout.lines().last() {
let metrics = last_line.metrics();
metrics.baseline + metrics.descent
} else {
0.0
};
self.real_size = vec2(width, height) / scale_factor;
}
pub fn color(&self) -> Color {
self.color
}
pub fn set_color(&mut self, color: Color) {
self.color = color;
}
pub fn size(&self) -> Vec2 {
self.real_size
}
pub fn width(&self) -> f32 {
self.real_size.x
}
pub fn height(&self) -> f32 {
self.real_size.y
}
pub fn layout(&self) -> &Layout<()> {
&self.layout
}
pub fn text(&self) -> &str {
&self.text
}
}
impl Renderer {
pub fn render_text(&mut self, text: &TextLayout, pos: Vec2, scale_factor: f32) {
self.render_text_raw(text.layout(), text.real_size, text.color, pos, scale_factor);
}
pub fn render_text_raw(&mut self, layout: &Layout<()>, real_size: Vec2, color: Color, pos: Vec2, scale_factor: f32) {
if (pos.x >= self.viewport.x || pos.x + real_size.x <= 0.0)
|| (pos.y >= self.viewport.y || pos.y + real_size.y <= 0.0)
{
return;
}
for line in layout.lines() {
for item in line.items() {
match item {
PositionedLayoutItem::GlyphRun(glyph_run) => {
self.render_glyph_run(&glyph_run, color, pos, scale_factor);
}
PositionedLayoutItem::InlineBox(inline_box) => {
self.push_draw_command(DrawCommand::RRect(RRect {
pos: pos + vec2(inline_box.x, inline_box.y),
size: vec2(inline_box.width, inline_box.height),
border_radius: Vec4::ZERO,
border_width: Vec4::ZERO,
fill_color: 0x000000ff,
stroke_color: 0x000000ff,
box_blur: 0.0,
}));
}
};
}
}
}
fn render_glyph_run(&mut self, glyph_run: &GlyphRun<'_, ()>, color: Color, offset_pos: Vec2, scale_factor: f32) {
let run_pos = vec2(glyph_run.offset(), glyph_run.baseline());
let run = glyph_run.run();
let run_metrics = run.metrics();
let font = run.font();
let font_size = run.font_size();
let normalized_coords = run.normalized_coords();
{
let pos = offset_pos + (run_pos - vec2(0.0, run_metrics.ascent)) / scale_factor;
let size = vec2(glyph_run.advance(), font_size) / scale_factor;
if pos.x >= self.viewport.x || pos.x + size.x <= 0.0 || pos.y >= self.viewport.y || pos.y + size.y <= 0.0 {
return;
}
}
let font_ref = swash::FontRef::from_index(font.data.as_ref(), font.index as usize).unwrap();
let style_hash = {
let (stretch, weight, style) = font_ref.attributes().parts();
let mut hasher = DefaultHasher::new();
hasher.write_u64(font.data.id());
hasher.write_u16(stretch.raw());
hasher.write_u16(weight.0);
hasher.write_u32(u32::from_le_bytes(style.to_degrees().to_le_bytes()));
hasher.write_u32(u32::from_ne_bytes(font_size.to_ne_bytes()));
(hasher.finish() % (u32::MAX as u64 + 1)) as u32
};
for attempt in 0..=10 {
if attempt == 10 {
panic!("There have been 10 failed attempts at drawing a single glyph run. What the fuck.");
}
let mut no_space_left = false;
let prev_command_count = self.command_buffer.len();
let mut scaler = (self.text.scale_cx)
.builder(font_ref)
.size(font_size)
.hint(false)
.normalized_coords(normalized_coords)
.build();
for glyph in glyph_run.positioned_glyphs() {
let glyph_pos = vec2(glyph.x, glyph.y);
let render_result = render_glyph(
&self.gl,
&mut self.text.quadlas,
self.text.quadlas_texture,
&mut self.text.scale_render,
&mut self.command_buffer,
&mut scaler,
self.current_frame,
color,
style_hash,
glyph,
glyph_pos,
offset_pos,
scale_factor,
);
match render_result {
Ok(()) => {}
Err(InsertGlyphError::NoSpaceLeft) => {
no_space_left = true;
break;
}
Err(InsertGlyphError::EmptyImage) => {}
Err(InsertGlyphError::TooBig) => {}
}
}
if no_space_left {
self.command_buffer.truncate(prev_command_count);
self.draw();
self.text.quadlas.grow();
self.reupload_glyph_atlas_texture();
} else {
break;
}
}
let style = glyph_run.style();
if let Some(decoration) = &style.underline {
let offset = (decoration.offset).unwrap_or(run_metrics.underline_offset);
let size = decoration.size.unwrap_or(run_metrics.underline_size);
render_decoration(self, glyph_run, color, offset, size, offset_pos, scale_factor);
}
if let Some(decoration) = &style.strikethrough {
let offset = (decoration.offset).unwrap_or(run_metrics.strikethrough_offset);
let size = decoration.size.unwrap_or(run_metrics.strikethrough_size);
render_decoration(self, glyph_run, color, offset, size, offset_pos, scale_factor);
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_glyph(
gl: &glow::Context,
quadlas: &mut Quadlas,
quadlas_texture: glow::Texture,
scale_render: &mut swash::scale::Render<'_>,
draw_commands: &mut Vec<DrawCommand>,
scaler: &mut Scaler<'_>,
current_frame: u64,
color: Color,
style_hash: u32,
glyph: Glyph,
glyph_pos: Vec2,
offset_pos: Vec2,
scale: f32,
) -> Result<(), InsertGlyphError> {
let quantize = u8vec2(2, 1);
let fquantize = quantize.as_vec2();
let offset_fract = (offset_pos.fract() * fquantize).floor() / fquantize;
let offset_pos = offset_pos.trunc();
let offset_fract = vec2(offset_fract.x, 1.0 - offset_fract.y);
let glyph_key = GlyphKey {
id: (glyph.id % (u16::MAX as u32)) as u16, quantization: (offset_fract * fquantize).floor().as_u8vec2(),
style_hash,
};
if let Some((land, gce)) = quadlas.get_cached_glyph(glyph_key, current_frame) {
let placement = gce.placement;
let glyph_size = vec2(placement.width as f32, placement.height as f32) / scale;
let glyph_pos = (glyph_pos + vec2(placement.left as f32, -placement.top as f32)) / scale;
let land_uv = quadlas.to_land_uv(land);
let color = match gce.is_colored {
true => 0xffffffff,
false => color,
};
draw_commands.push(DrawCommand::GlyphQuad(
GlyphQuad {
pos: offset_pos + glyph_pos,
size: glyph_size,
uv_pos: land_uv.pos,
uv_size: land_uv.size,
color,
},
quadlas_texture,
));
} else {
let fractional_offset = Vector::new(offset_fract.x, offset_fract.y);
let Some(rendered_glyph) = scale_render.offset(fractional_offset).render(scaler, glyph_key.id) else {
return Ok(());
};
let placement = rendered_glyph.placement;
let glyph_size = vec2(placement.width as f32, placement.height as f32) / scale;
let glyph_pos = (glyph_pos + vec2(placement.left as f32, -placement.top as f32)) / scale;
let (land, color) = match rendered_glyph.content {
Content::Color => {
let glyph_image = RawImage {
width: placement.width,
height: placement.height,
pixels: &rendered_glyph.data,
};
let (land, _) = quadlas.insert_glyph_unless_cached(
glyph_key,
rendered_glyph.placement,
true,
glyph_image,
current_frame,
)?;
unsafe {
upload_subtexture(
gl,
quadlas_texture,
land.pos.x,
land.pos.y,
land.size.x,
land.size.y,
Some(glyph_image.pixels),
);
}
(land, 0xffffffff)
}
Content::Mask => {
let rgba_data = (rendered_glyph.data.iter())
.flat_map(|&a| [0xff, 0xff, 0xff, a])
.collect::<Vec<_>>();
let glyph_image = RawImage {
width: placement.width,
height: placement.height,
pixels: &rgba_data,
};
let (land, _) = quadlas.insert_glyph_unless_cached(
glyph_key,
rendered_glyph.placement,
false,
RawImage {
width: placement.width,
height: placement.height,
pixels: &rgba_data,
},
current_frame,
)?;
unsafe {
upload_subtexture(
gl,
quadlas_texture,
land.pos.x,
land.pos.y,
land.size.x,
land.size.y,
Some(glyph_image.pixels),
);
}
(land, color)
}
Content::SubpixelMask => unimplemented!(),
};
let land_uv = quadlas.to_land_uv(land);
draw_commands.push(DrawCommand::GlyphQuad(
GlyphQuad {
pos: offset_pos + glyph_pos,
size: glyph_size,
uv_pos: land_uv.pos,
uv_size: land_uv.size,
color,
},
quadlas_texture,
));
}
Ok(())
}
fn render_decoration(
renderer: &mut Renderer,
glyph_run: &GlyphRun<'_, ()>,
color: Color,
offset: f32,
thickness: f32,
offset_pos: Vec2,
scale_factor: f32,
) {
let pos = offset_pos + vec2(glyph_run.offset(), glyph_run.baseline() - offset) / scale_factor;
let size = vec2(glyph_run.advance(), thickness * 2.0) / scale_factor;
if pos.x >= renderer.viewport.x || pos.x + size.x <= 0.0 || pos.y >= renderer.viewport.y || pos.y + size.y <= 0.0 {
return;
}
renderer.push_draw_command(DrawCommand::RRect(RRect {
pos,
size,
border_radius: Vec4::splat(thickness) / 1.5,
border_width: Vec4::ZERO,
fill_color: color,
stroke_color: 0x00000000,
box_blur: 0.0,
}));
}