use super::{ InstanceData, font::{self, CharAtlas}, image::ImageId, texture::TextureWrapper, }; use glam::{Mat2, Vec2}; use wgpu::Queue; pub enum HorizontalAlign { Left, Center, Right, } pub enum VerticalAlign { Top, Center, Bottom, } pub struct Text { pub text: String, pub origin: Vec2, pub scale: u32, pub h_align: HorizontalAlign, pub v_align: VerticalAlign, } pub struct Image { pub id: ImageId, pub origin: Vec2, pub rotation: f32, pub scale: Vec2, pub uv_offset: Vec2, pub uv_size: Vec2, } pub enum RenderCommand { Text(Text), Image(Image), } impl RenderCommand { /// 描画コマンドからインスタンス情報に変換するメソッド /// /// * screen_width - 画面幅 /// * screen_height - 画面高 /// * scale_factor - 画面拡大率 /// * queue - queue /// * char_atlas - 文字テクスチャアトラス /// * images - 画像テクスチャ /// * buf - インスタンスバッファ pub fn build( &self, screen_width: f32, screen_height: f32, scale_factor: f32, queue: &Queue, char_atlas: &mut CharAtlas, images: &[TextureWrapper], buf: &mut Vec, ) { match self { RenderCommand::Text(text) => build_text( text, screen_width, screen_height, scale_factor, queue, char_atlas, buf, ), RenderCommand::Image(image) => { build_image(image, screen_width, screen_height, images, buf) } } } } fn build_text( text: &Text, screen_width: f32, screen_height: f32, scale_factor: f32, queue: &Queue, char_atlas: &mut CharAtlas, buf: &mut Vec, ) { let scale = text.scale as f32; let line_height = font::line_height(scale); let line_count = text.text.matches('\n').count(); let height = line_height * line_count as f32 + font::height(scale); let top = match text.v_align { VerticalAlign::Top => text.origin.y, VerticalAlign::Center => text.origin.y - height / 2.0, VerticalAlign::Bottom => text.origin.y - height, }; let mut baseline = top + font::ascent(scale); for line in text.text.split('\n') { // NOTE: ピクセル境界に合わせるため、整数にする。 // 最終的にRust側でNDCに変換するが、fragmentation時に整数に直る。 let y = (baseline * scale_factor).round() / scale_factor; let start = buf.len(); let mut ox = 0.0; let mut prev = None; for c in line.chars() { let ci = char_atlas.acquire(queue, c, scale); if let Some(prev) = prev { ox += font::kern(scale, prev, ci.glyph_id); } prev = Some(ci.glyph_id); if ci.alloc_id.is_none() { ox += font::h_advance(scale, ci.glyph_id); continue; } // NOTE: ピクセル境界に合わせるため、整数にする。 // 最終的にRust側でNDCに変換するが、fragmentation時に整数に直る。 let x = (ox * scale_factor).round() / scale_factor; let center_x = x + ci.x_offset + ci.width / 2.0; let center_y = y + ci.y_offset + ci.height / 2.0; buf.push(InstanceData { transform: Mat2::from_diagonal(Vec2::new( ci.width / screen_width * 2.0, -ci.height / screen_height * 2.0, )), translation: Vec2::new( center_x / screen_width * 2.0 - 1.0, 1.0 - center_y / screen_height * 2.0, ), uv_offset: ci.uv_offset, uv_size: ci.uv_size, tex_id: u32::MAX, _pad: 0, }); ox += font::h_advance(scale, ci.glyph_id); } let left = match text.h_align { HorizontalAlign::Left => text.origin.x, HorizontalAlign::Center => text.origin.x - ox / 2.0, HorizontalAlign::Right => text.origin.x - ox, }; // NOTE: ピクセル境界に合わせるため、整数にする。 // 最終的にRust側でNDCに変換するが、fragmentation時に整数に直る。 let left = (left * scale_factor).round() / scale_factor; for instance in &mut buf[start..] { instance.translation.x += left / screen_width * 2.0; } baseline += line_height; } } fn build_image( image: &Image, screen_width: f32, screen_height: f32, images: &[TextureWrapper], buf: &mut Vec, ) { let texture = &images[image.id as usize]; let size = image.uv_size * Vec2::new(texture.width as f32, texture.height as f32) * image.scale; let ndc = Vec2::new(2.0 / screen_width, -2.0 / screen_height); buf.push(InstanceData { transform: Mat2::from_diagonal(ndc) * Mat2::from_scale_angle(size, image.rotation), translation: image.origin * ndc + Vec2::new(-1.0, 1.0), uv_offset: image.uv_offset, uv_size: image.uv_size, tex_id: image.id as u32, _pad: 0, }); }