use super::{ InstanceData, font::{self, CharAtlas}, }; use glam::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 enum RenderCommand { Text(Text), } impl RenderCommand { /// 描画コマンドからインスタンス情報に変換するメソッド /// /// * screen_width - 画面幅 /// * screen_height - 画面高 /// * scale_factor - 画面拡大率 /// * queue - queue /// * char_atlas - 文字テクスチャアトラス /// * buf - インスタンスバッファ pub fn build( &self, screen_width: f32, screen_height: f32, scale_factor: f32, queue: &Queue, char_atlas: &mut CharAtlas, buf: &mut Vec, ) { match self { RenderCommand::Text(text) => build_text( text, screen_width, screen_height, scale_factor, queue, char_atlas, 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 { position: Vec2::new( center_x / screen_width * 2.0 - 1.0, 1.0 - center_y / screen_height * 2.0, ), scale: Vec2::new(ci.width / screen_width, ci.height / screen_height), uv: ci.uv, }); 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.position.x += left / screen_width * 2.0; } baseline += line_height; } }