KLGRQAFUQPJP75NVLS5MZZ3CY4TGNVGB4K2YHQSLYVGAPKCLBLUAC T5T2PZSJVBHIPHG7VRXZSH36WGSTR4I44BOI335X4F637E7GUWHAC PTQ26KY65NDVZ35EG66SARWRGGYKDJ2BT3EVK4RJFS2J5AQUX6OQC V2265WPBM2LBE4BGEFB3HYCZXA5C5MJKQ5IVPFWY3ARCVIAYBZGQC HKVKPIIWK6AB4DWCS6SB47RYEXTD4QAQIGDFNQGA3TDJEIAXBZKQC CTPHEJ2S7MMSZTNVO4LFEYHMMTKYWB5PC54RMVJHMATN5TY45GPQC B66IOHMWFZINTWC57Q5OD4YPBVQNBN3YUM3ZKX4MTDKHYGH5JHFAC X6QJYZJZSS7YSRCDOPQ2I2Y7U7VJA4HE5P74ZYENROR7FRUPPXWQC 576YRB7XSKS6Q265TEJQS7HUGMKSGHGDRGVP6WBZS7XMK6RVJRNQC VL4FY4YJUFRVCZR67KOTB22RILTEP2KX2GLHP4J662XF7VGS77WAC CVXA6LIYFYIZEVPTI335A332EXRVJXF2SC4B5F2NFYXZXYVDFBKQC AFZQUL4VKEEDRSA5CV6YXN7PMX6PQ2PMLECTG53S4HZKTNCAIMXQC LSPMIVEH5IYUPBMB7DL5VTM2HOMYDBA5ZY3GIW6ZFTCDGTUUDFFAC L4D4HCUQDTOJKMNBIZWHUHXYEEQKOL4BCNMDI3ZOP3ARK65YNCVQC use std::{collections::HashMap, error::Error};use fontdue::{FontSettings, Metrics};use pixels::{Pixels, SurfaceTexture};use rustybuzz::UnicodeBuffer;use winit::{dpi::PhysicalSize, event_loop::ActiveEventLoop, window::Window};use crate::json_ui;use crate::{HEIGHT, WIDTH};pub struct RenderState {/// pixels depends on this being alive#[allow(unused)]pub window: Window,pub pixels: Pixels<'static>,pub size: (u32, u32),}type GlyphIndex = u16;type FontIndex = usize;struct FontData {// This is referred by rb_face#[allow(unused)]data: Vec<u8>,rb_face: rustybuzz::Face<'static>,fd_face: fontdue::Font,}pub struct FontState {faces: Vec<FontData>,font_cache: HashMap<(FontIndex, GlyphIndex), (Metrics, Vec<u8>)>,}impl FontState {pub fn new() -> Result<Self, Box<dyn Error>> {let mut fontdb = fontdb::Database::new();fontdb.load_system_fonts();let mut faces = Vec::new();for font in ["Iosevka", "Manjari"] {let id = fontdb.query(&fontdb::Query {families: &[fontdb::Family::Name(font)],..Default::default()}).unwrap();fontdb.with_face_data(id, |data, _| {let data = data.to_vec();let rb_face =rustybuzz::Face::from_slice(unsafe { &*(&data[..] as *const [u8]) }, 0).ok_or("Failed to load rustybuzz font")?;let fd_face = fontdue::Font::from_bytes(&data[..], FontSettings::default())?;faces.push(FontData {data,rb_face,fd_face,});Ok(())}).ok_or("Failed to load font").flatten()?;}Ok(Self {faces,font_cache: HashMap::new(),})}fn line_height(&self) -> u32 {self.faces.get(0).and_then(|fd| Some(fd.fd_face.vertical_line_metrics(32.0)?.new_line_size as u32)).unwrap_or(40)}fn rasterize_glyph(&mut self, font: FontIndex, glyph: GlyphIndex) -> &(Metrics, Vec<u8>) {self.font_cache.entry((font, glyph)).or_insert_with(|| {self.faces[font].fd_face.rasterize_indexed_subpixel(glyph, 32.0)})}}impl RenderState {pub fn new(event_loop: &ActiveEventLoop) -> Result<Self, Box<dyn Error>> {let window = event_loop.create_window(Window::default_attributes().with_inner_size(PhysicalSize::new(WIDTH as f64, HEIGHT as f64)).with_title("Kakoune Client"),)?;let st = SurfaceTexture::new(WIDTH,HEIGHT,// We need to use unsafe here because otherwise we can't have self-referencial structsunsafe { &*(&window as *const Window) },);let pixels = Pixels::new(WIDTH, HEIGHT, st)?;Ok(Self {window,pixels,size: (WIDTH, HEIGHT),})}}fn render_segment(rs: &mut RenderState,fs: &mut FontState,font: FontIndex,face: &json_ui::Face,default_face: &json_ui::Face,ubuf: UnicodeBuffer,pos: &mut (i32, i32),) -> UnicodeBuffer {let glyph_buffer = rustybuzz::shape(&fs.faces[font].rb_face, &[], ubuf);let ginfo = glyph_buffer.glyph_infos();let gpos = glyph_buffer.glyph_positions();let upe = fs.faces[font].rb_face.units_per_em();let buf = rs.pixels.frame_mut();for i in 0..glyph_buffer.len() {let (metrics, data) = fs.rasterize_glyph(font, ginfo[i].glyph_id as u16);let x_off = gpos[i].x_offset * 32 / upe + metrics.xmin;let y_off = gpos[i].y_offset * 32 / upe - metrics.ymin - metrics.height as i32;for row in 0..metrics.height {for column in 0..metrics.width {let x = pos.0 + x_off + column as i32;let y = pos.1 + y_off + row as i32;let bidx = ((x + y * rs.size.0 as i32) * 4) as usize;let sidx = (column + row * metrics.width) * 3;if bidx >= buf.len() {break;}if sidx >= buf.len() {continue;}if x > rs.size.0 as i32 {break;}let lerp = |col1, col2, x| {(col1 as i32 + (col2 as i32 - col1 as i32) * x as i32 / 255) as u8};let fg = &face.fg.to_rgba(default_face.fg.to_rgba((255, 255, 255, 0)));let bg = &face.bg.to_rgba(default_face.bg.to_rgba((255, 255, 255, 0)));buf[bidx] = lerp(bg.0, fg.0, data[sidx]);buf[bidx + 1] = lerp(bg.1, fg.1, data[sidx + 1]);buf[bidx + 2] = lerp(bg.2, fg.2, data[sidx + 2]);buf[bidx + 3] = 255;}}pos.0 += gpos[i].x_advance * 32 / upe;pos.1 += gpos[i].y_advance * 32 / upe;}glyph_buffer.clear()}pub fn redraw(rs: &mut RenderState,fs: &mut FontState,lines: &[json_ui::Line],default_face: &json_ui::Face,) -> Result<(), Box<dyn Error>> {let bg = default_face.bg.to_rgba((0, 0, 0, 255));for c in rs.pixels.frame_mut().chunks_exact_mut(4) {c[0] = bg.0;c[1] = bg.1;c[2] = bg.2;c[3] = bg.3;}let mut ubuf = UnicodeBuffer::new();let mut curr_face = None;let mut curr_font = 0;let mut pos = (0, fs.line_height() as i32);for line in lines {for atom in line {let face = &atom.face;for c in atom.contents.chars() {let font = fs.faces.iter().position(|i| i.rb_face.glyph_index(c).is_some()).unwrap_or(0);if curr_face != Some(face) || curr_font != font || c == '\n' {ubuf = render_segment(rs,fs,curr_font,curr_face.unwrap_or(face),default_face,ubuf,&mut pos,);curr_face = Some(face);curr_font = font;ubuf.clear();}if c == '\n' {continue;}ubuf.add(c, 2);}}pos.0 = 0;pos.1 += fs.line_height() as i32;}rs.pixels.render().unwrap();Ok(())}
}}fn render_segment(rs: &mut RenderState,fs: &mut FontState,font: FontIndex,face: &json_ui::Face,default_face: &json_ui::Face,ubuf: UnicodeBuffer,pos: &mut (i32, i32),) -> UnicodeBuffer {let glyph_buffer = rustybuzz::shape(&fs.faces[font].rb_face, &[], ubuf);let ginfo = glyph_buffer.glyph_infos();let gpos = glyph_buffer.glyph_positions();let upe = fs.faces[font].rb_face.units_per_em();let buf = rs.pixels.frame_mut();for i in 0..glyph_buffer.len() {let (metrics, data) = fs.rasterize_glyph(font, ginfo[i].glyph_id as u16);let x_off = gpos[i].x_offset * 32 / upe + metrics.xmin;let y_off = gpos[i].y_offset * 32 / upe - metrics.ymin - metrics.height as i32;pos.0 += gpos[i].x_advance * 32 / upe;pos.1 += gpos[i].y_advance * 32 / upe;}glyph_buffer.clear()}fn redraw(rs: &mut RenderState,fs: &mut FontState,lines: &[json_ui::Line],default_face: &json_ui::Face,) -> Result<(), Box<dyn Error>> {let bg = default_face.bg.to_rgba((0, 0, 0, 255));for c in rs.pixels.frame_mut().chunks_exact_mut(4) {c[0] = bg.0;c[1] = bg.1;c[2] = bg.2;c[3] = bg.3;}let mut ubuf = UnicodeBuffer::new();let mut curr_face = None;let mut curr_font = 0;let mut pos = (0, fs.line_height() as i32);for line in lines {for atom in line {let face = &atom.face;for c in atom.contents.chars() {let font = fs.faces.iter().position(|i| i.rb_face.glyph_index(c).is_some()).unwrap_or(0);if curr_face != Some(face) || curr_font != font || c == '\n' {ubuf = render_segment(rs,fs,curr_font,curr_face.unwrap_or(face),default_face,ubuf,&mut pos,);curr_face = Some(face);curr_font = font;ubuf.clear();}if c == '\n' {continue;}ubuf.add(c, 2);}}pos.0 = 0;pos.1 += fs.line_height() as i32;
}for row in 0..metrics.height {for column in 0..metrics.width {let x = pos.0 + x_off + column as i32;let y = pos.1 + y_off + row as i32;let bidx = ((x + y * rs.size.0 as i32) * 4) as usize;let sidx = column + row * metrics.width;if bidx >= buf.len() {break;}if sidx >= buf.len() {continue;}if x > rs.size.0 as i32 {break;}let color = if data[sidx] > 200 {&face.fg.to_rgba(default_face.fg.to_rgba((255, 255, 255, 0)))} else {&face.bg.to_rgba(default_face.bg.to_rgba((0, 0, 0, 255)))};buf[bidx] = color.0;buf[bidx + 1] = color.1;buf[bidx + 2] = color.2;buf[bidx + 3] = 255;}}use crate::{json_ui::{Request, Response, Rpc},kakoune::Kakoune,};mod json_ui;mod kakoune;const WIDTH: u32 = 600;const HEIGHT: u32 = 400;struct RenderState {/// pixels depends on this being alive#[allow(unused)]window: Window,pixels: Pixels<'static>,size: (u32, u32),}type GlyphIndex = u16;type FontIndex = usize;struct FontData {// This is referred by rb_face#[allow(unused)]data: Vec<u8>,rb_face: rustybuzz::Face<'static>,fd_face: fontdue::Font,}struct FontState {faces: Vec<FontData>,font_cache: HashMap<(FontIndex, GlyphIndex), (Metrics, Vec<u8>)>,}impl FontState {fn new() -> Result<Self, Box<dyn Error>> {let mut fontdb = fontdb::Database::new();fontdb.load_system_fonts();let mut faces = Vec::new();for font in ["Iosevka", "Manjari"] {let id = fontdb.query(&fontdb::Query {families: &[fontdb::Family::Name(font)],..Default::default()}).unwrap();fontdb.with_face_data(id, |data, _| {let data = data.to_vec();let rb_face =rustybuzz::Face::from_slice(unsafe { &*(&data[..] as *const [u8]) }, 0).ok_or("Failed to load rustybuzz font")?;let fd_face = fontdue::Font::from_bytes(&data[..], FontSettings::default())?;faces.push(FontData {data,rb_face,fd_face,});Ok(())}).ok_or("Failed to load font").flatten()?;}Ok(Self {faces,font_cache: HashMap::new(),})}fn line_height(&self) -> u32 {self.faces.get(0).and_then(|fd| Some(fd.fd_face.vertical_line_metrics(32.0)?.new_line_size as u32)).unwrap_or(40)}fn rasterize_glyph(&mut self, font: FontIndex, glyph: GlyphIndex) -> &(Metrics, Vec<u8>) {self.font_cache.entry((font, glyph)).or_insert_with(|| self.faces[font].fd_face.rasterize_indexed(glyph, 32.0))}}impl RenderState {fn new(event_loop: &ActiveEventLoop) -> Result<Self, Box<dyn Error>> {let window = event_loop.create_window(Window::default_attributes().with_inner_size(PhysicalSize::new(WIDTH as f64, HEIGHT as f64)).with_title("Kakoune Client"),)?;let st = SurfaceTexture::new(WIDTH,HEIGHT,// We need to use unsafe here because otherwise we can't have self-referencial structsunsafe { &*(&window as *const Window) },);let pixels = Pixels::new(WIDTH, HEIGHT, st)?;Ok(Self {window,pixels,size: (WIDTH, HEIGHT),})}