rewrite in rust

This commit is contained in:
Richard Acayan 2024-07-18 20:32:55 -04:00
parent d43f61ec76
commit 4a6b261be0
51 changed files with 3104 additions and 9319 deletions

694
src/core/graphics.rs Normal file
View file

@ -0,0 +1,694 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Copyright (c) 2024, Richard Acayan. All rights reserved.
*/
use core::button::ModState;
use core::fontconfig;
use core::freetype::freetype;
use core::imgref::ImgRefMut;
use core::imgref::ImgRef;
use core::imgref::ImgVec;
use core::Layout;
use core::layout::Key;
use core::layout::Part;
use core::rgb::alt::BGR;
use core::rgb::alt::BGRA;
use core::xkeysym::Keysym;
use std::collections::HashMap;
use std::convert::TryInto;
use std::fs::File;
use std::io::Read;
use std::iter;
use std::ptr;
fn convert_gray_to_bgrx(mut dest: ImgRefMut<BGRA<u8>>, src: ImgRef<u8>, fg_color: BGR<f32>)
{
for (dest, src) in iter::zip(dest.rows_mut(), src.rows()) {
for (dest, src) in iter::zip(dest, src) {
dest.r = ((dest.r as f32 + *src as f32 * fg_color.r) as u16).try_into().unwrap_or(255);
dest.g = ((dest.g as f32 + *src as f32 * fg_color.g) as u16).try_into().unwrap_or(255);
dest.b = ((dest.b as f32 + *src as f32 * fg_color.b) as u16).try_into().unwrap_or(255);
}
}
}
fn copy_image(mut dest: ImgRefMut<u8>, src: ImgRef<u8>)
{
for (dest, src) in iter::zip(dest.pixels_mut(), src.pixels()) {
*dest = (src as u16 + *dest as u16).try_into().unwrap_or(255);
}
}
fn fill_image(dest: &mut ImgRefMut<BGRA<u8>>, src: BGRA<u8>)
{
for dest in dest.pixels_mut() {
*dest = src;
}
}
fn bitmap_to_imgref(bitmap: &freetype::FT_Bitmap) -> ImgRef<u8>
{
unsafe {
let buf = &*ptr::slice_from_raw_parts(bitmap.buffer,
bitmap.pitch as usize
* bitmap.rows as usize);
ImgRef::new_stride(buf,
bitmap.width as usize,
bitmap.rows as usize,
bitmap.pitch as usize)
}
}
#[derive(Clone, Copy)]
enum Anchor {
Min,
Center,
Max,
}
pub trait Display {
fn size(&self) -> (u32, u32);
fn begin(&mut self) -> ImgRefMut<BGRA<u8>>;
fn resize(&mut self, width: u32, height: u32);
fn end(&mut self, x: u32, y: u32, width: u32, height: u32);
}
pub struct Graphics<D: Display> {
disp: D,
fonts: Vec<(String, freetype::FT_Long)>,
fontbufs: Vec<Vec<u8>>,
labels: HashMap<Keysym, ImgVec<u8>>,
sublabels: HashMap<Keysym, ImgVec<u8>>,
ft: freetype::FT_Library,
x_scale: f64,
y_scale: f64,
}
struct Label<'a> {
sym: Keysym,
label: &'a str,
secondary: bool,
small: bool,
}
const LABELS: [Label; 43] = [
Label { sym: Keysym::Greek_MU, label: "\u{039C}",
secondary: false, small: false, },
Label { sym: Keysym::emdash, label: "\u{2014}",
secondary: false, small: false, },
Label { sym: Keysym::endash, label: "\u{2013}",
secondary: false, small: false, },
Label { sym: Keysym::ellipsis, label: "\u{2026}",
secondary: false, small: false, },
Label { sym: Keysym::leftsinglequotemark, label: "\u{2018}",
secondary: false, small: false, },
Label { sym: Keysym::rightsinglequotemark, label: "\u{2019}",
secondary: false, small: false, },
Label { sym: Keysym::leftdoublequotemark, label: "\u{201C}",
secondary: false, small: false, },
Label { sym: Keysym::rightdoublequotemark, label: "\u{201D}",
secondary: false, small: false, },
Label { sym: Keysym::permille, label: "\u{2030}",
secondary: false, small: false, },
Label { sym: Keysym::singlelowquotemark, label: "\u{201A}",
secondary: false, small: false, },
Label { sym: Keysym::doublelowquotemark, label: "\u{201E}",
secondary: false, small: false, },
Label { sym: Keysym::OE, label: "\u{0152}",
secondary: false, small: false, },
Label { sym: Keysym::oe, label: "\u{0153}",
secondary: false, small: false, },
Label { sym: Keysym::BackSpace, label: "\u{232B}",
secondary: true, small: false, },
Label { sym: Keysym::Tab, label: "\u{2B7E}",
secondary: true, small: true, },
Label { sym: Keysym::Return, label: "\u{23CE}",
secondary: true, small: false, },
Label { sym: Keysym::Escape, label: "Esc",
secondary: true, small: true, },
Label { sym: Keysym::Home, label: "\u{21E4}",
secondary: true, small: false, },
Label { sym: Keysym::Left, label: "\u{2190}",
secondary: true, small: false, },
Label { sym: Keysym::Up, label: "\u{2191}",
secondary: true, small: false, },
Label { sym: Keysym::Right, label: "\u{2192}",
secondary: true, small: false, },
Label { sym: Keysym::Down, label: "\u{2193}",
secondary: true, small: false, },
Label { sym: Keysym::Page_Up, label: "\u{21D1}",
secondary: true, small: false, },
Label { sym: Keysym::Page_Down, label: "\u{21D3}",
secondary: true, small: false, },
Label { sym: Keysym::End, label: "\u{21E5}",
secondary: true, small: false, },
Label { sym: Keysym::Insert, label: "Ins",
secondary: true, small: true, },
Label { sym: Keysym::F1, label: "F1",
secondary: false, small: false, },
Label { sym: Keysym::F2, label: "F2",
secondary: false, small: false, },
Label { sym: Keysym::F3, label: "F3",
secondary: false, small: false, },
Label { sym: Keysym::F4, label: "F4",
secondary: false, small: false, },
Label { sym: Keysym::F5, label: "F5",
secondary: false, small: false, },
Label { sym: Keysym::F6, label: "F6",
secondary: false, small: false, },
Label { sym: Keysym::F7, label: "F7",
secondary: false, small: false, },
Label { sym: Keysym::F8, label: "F8",
secondary: false, small: false, },
Label { sym: Keysym::F9, label: "F9",
secondary: false, small: false, },
Label { sym: Keysym::F10, label: "F10",
secondary: false, small: false, },
Label { sym: Keysym::F11, label: "F11",
secondary: false, small: true, },
Label { sym: Keysym::F12, label: "F12",
secondary: false, small: true, },
Label { sym: Keysym::Shift_L, label: "\u{21E7}",
secondary: true, small: false, },
Label { sym: Keysym::Control_L, label: "Ctrl",
secondary: true, small: true, },
Label { sym: Keysym::Alt_L, label: "Alt",
secondary: true, small: true, },
Label { sym: Keysym::Delete, label: "\u{2326}",
secondary: true, small: false, },
Label { sym: Keysym::XF86_Fn, label: "Fn",
secondary: true, small: true, },
/*
Label { sym: Keysym::approxeq, label: "\u{2248}",
secondary: false, small: false, },
*/
];
const ANCHORS: [(Anchor, Anchor); 8] = [
(Anchor::Min, Anchor::Min),
(Anchor::Max, Anchor::Min),
(Anchor::Min, Anchor::Max),
(Anchor::Max, Anchor::Max),
(Anchor::Min, Anchor::Center),
(Anchor::Max, Anchor::Center),
(Anchor::Center, Anchor::Min),
(Anchor::Center, Anchor::Max),
];
impl<D: Display> Graphics<D> {
pub fn new(disp: D) -> Graphics<D>
{
let ft = unsafe {
let mut ft = std::ptr::null_mut();
let err = freetype::FT_Init_FreeType(&mut ft);
if err != 0 {
panic!("FreeType error {}", err);
}
ft
};
/*
* We want a pattern with default and config substitution, so use the
* side effect in font_match(). The first font might not provide all
* glyphs needed, so the returned font is unused.
*/
let fc = fontconfig::Fontconfig::new().unwrap();
let mut pattern = fontconfig::Pattern::new(&fc);
pattern.font_match();
let fonts = fontconfig::sort_fonts(&pattern, true);
let mut fontfiles = Vec::new();
for font in fonts.iter() {
let fname = match font.filename() {
Some(fname) => String::from(fname),
None => continue,
};
let idx = match font.face_index() {
Some(idx) => idx.into(),
None => 0,
};
fontfiles.push((fname, idx));
}
Graphics {
disp,
ft,
fonts: fontfiles,
fontbufs: Vec::new(),
labels: HashMap::new(),
sublabels: HashMap::new(),
x_scale: 0.0,
y_scale: 0.0,
}
}
fn keysym_to_label(sym: Keysym) -> String
{
let res = LABELS.binary_search_by_key(&sym, |&Label { sym, .. }| sym);
if let Ok(idx) = res {
return String::from(LABELS[idx].label);
}
// The ! symbol is bitwise NOT.
match char::from_u32(sym.raw() & !0x01000000) {
Some(c) => {
let mut label = String::new();
label.push(c);
label
},
None => {
format!("U+{}", sym.raw())
},
}
}
fn is_keysym_small(sym: Keysym) -> bool
{
match LABELS.binary_search_by_key(&sym, |&Label { sym, .. }| sym) {
Ok(idx) => LABELS[idx].small,
Err(_) => false,
}
}
fn open_font(&mut self, idx: usize) -> Option<freetype::FT_Face>
{
// Read the font if it is not already cached.
if self.fontbufs.len() <= idx {
let mut f = File::open(&self.fonts[idx].0).ok()?;
let mut fontbuf = Vec::new();
f.read_to_end(&mut fontbuf).ok()?;
self.fontbufs.push(fontbuf);
}
let fontbuf = &self.fontbufs[idx];
unsafe {
let mut ftface = std::ptr::null_mut();
freetype::FT_New_Memory_Face(self.ft,
fontbuf.as_ptr(),
fontbuf.len() as freetype::FT_Long,
self.fonts[idx].1 as freetype::FT_Long,
&mut ftface);
if ptr::eq(ftface, ptr::null_mut()) {
None
} else {
Some(ftface)
}
}
}
fn can_font_render_text(face: Option<freetype::FT_Face>, text: &str) -> bool
{
let face = match face {
Some(face) => face,
None => return false,
};
for c in text.chars() {
unsafe {
if freetype::FT_Get_Char_Index(face, c.into()) == 0 {
return false;
}
}
}
true
}
fn open_font_for_text(&mut self, text: &str) -> Option<freetype::FT_Face>
{
let mut ftface = self.open_font(0);
let mut i = 1;
while !Self::can_font_render_text(ftface, text) {
if let Some(ftface) = ftface {
unsafe {
freetype::FT_Done_Face(ftface);
}
}
ftface = self.open_font(i);
i += 1;
if i >= self.fonts.len() {
// Fall back to the first font with placeholder glyphs
return self.open_font(0);
}
}
ftface
}
fn rasterize_text(&mut self, size: f64, text: &str) -> ImgVec<u8>
{
let ftface = self.open_font_for_text(text).unwrap();
unsafe {
freetype::FT_Set_Char_Size(ftface, (size * 64.0) as i64, 0, 0, 0);
};
let (metrics, units_per_em, bbox) = unsafe {
((*(*ftface).size).metrics,
(*ftface).units_per_EM,
(*ftface).bbox)
};
let mut width = 0;
let glyph_width = (bbox.xMax - bbox.xMin) as usize
* metrics.x_ppem as usize
/ units_per_em as usize;
let stride = metrics.max_advance as usize / 64 * (text.len() - 1) + glyph_width;
let height = (metrics.ascender - metrics.descender) as usize / 64;
let vec: Vec<u8> = vec![0; stride * height];
let mut img = ImgVec::new(vec, stride, height);
let mut pen: i64 = 0;
for c in text.chars() {
let glyph = unsafe {
freetype::FT_Load_Char(ftface, c.into(), 0);
freetype::FT_Render_Glyph((*ftface).glyph, freetype::FT_Render_Mode::FT_RENDER_MODE_NORMAL);
*(*ftface).glyph
};
if glyph.bitmap.rows != 0 && glyph.bitmap.width != 0 {
if pen < -glyph.bitmap_left as i64 {
pen -= glyph.bitmap_left as i64;
}
let dest_x = pen + glyph.bitmap_left as i64;
let dest_y = metrics.ascender / 64 - glyph.bitmap_top as i64;
let src = bitmap_to_imgref(&glyph.bitmap);
let dest = img.sub_image_mut(dest_x as usize,
dest_y as usize,
glyph.bitmap.width as usize,
glyph.bitmap.rows as usize);
copy_image(dest, src);
}
let new_width = (pen + glyph.bitmap_left as i64
+ glyph.bitmap.width as i64) as usize;
if width < new_width {
width = new_width;
}
pen += glyph.advance.x / 64;
}
unsafe {
freetype::FT_Done_Face(ftface);
};
let vec = img.into_buf();
ImgVec::new_stride(vec, width, height, stride)
}
/*
* Rasterize a key symbol and store it in the labels hash table.
*
* We need two functions, one for labels and one for sublabels, in order to
* borrow the hash tables mutably. The text rasterizing operation borrows
* the self as mutable to store fonts for later use. Since the self and hash
* table cannot be mutable at the same time, this cannot be abstracted to a
* mutable self and mutable hash table.
*/
fn rasterize_label(&mut self, size: f64, sym: Keysym)
{
if !self.labels.contains_key(&sym) {
let label = Self::keysym_to_label(sym);
let size = if Self::is_keysym_small(sym) {
size * 0.75
} else {
size
};
let img = self.rasterize_text(size, &label);
self.labels.insert(sym, img);
}
}
/*
* Rasterize a key symbol and store it in the sublabels hash table.
*
* We need two functions, one for labels and one for sublabels, in order to
* borrow the hash tables mutably. The text rasterizing operation borrows
* the self as mutable to store fonts for later use. Since the self and hash
* table cannot be mutable at the same time, this cannot be abstracted to a
* mutable self and mutable hash table.
*/
fn rasterize_sublabel(&mut self, size: f64, sym: Keysym)
{
if !self.sublabels.contains_key(&sym) {
let label = Self::keysym_to_label(sym);
let size = if Self::is_keysym_small(sym) {
size * 0.75
} else {
size
};
let img = self.rasterize_text(size, &label);
self.sublabels.insert(sym, img);
}
}
fn rasterize_labels(&mut self, layout: &Layout, mod_state: &[ModState])
{
self.labels = HashMap::new();
self.sublabels = HashMap::new();
let label_size = self.y_scale * 0.33;
let sublabel_size = self.y_scale * 0.22;
for row in layout.rows() {
self.labels.reserve(row.len());
self.sublabels.reserve(row.len() * 8);
for key in row {
let sym = key.parts[0].display_symbol(mod_state);
self.rasterize_label(label_size, sym);
for part in &key.parts[1..] {
let sym = part.display_symbol(mod_state);
self.rasterize_sublabel(sublabel_size, sym);
}
}
}
}
fn draw_label_part(labels: &HashMap<Keysym, ImgVec<u8>>,
x_anchor: Anchor, y_anchor: Anchor,
img: &mut ImgRefMut<BGRA<u8>>,
sym: Keysym, fg_color: BGR<f32>)
{
let src = labels.get(&sym)
.expect("Layout and size should be set before drawing");
let x = match x_anchor {
Anchor::Min => 0,
Anchor::Center => (img.width() - src.width()) / 2,
Anchor::Max => img.width() - src.width(),
};
let y = match y_anchor {
Anchor::Min => 0,
Anchor::Center => (img.height() - src.height()) / 2,
Anchor::Max => img.height() - src.height(),
};
let width = src.width();
let height = src.height();
let src = src.sub_image(0, 0, width, height);
let dest = img.sub_image_mut(x, y, width, height);
convert_gray_to_bgrx(dest, src, fg_color);
}
fn keypart_to_color(part: &Part, sublabel: bool,
mod_state: &[ModState]) -> BGR<f32>
{
let color_locked = BGR { r: 0.0, g: 1.0, b: 0.1, };
let color_pressed = BGR { r: 0.0, g: 0.6, b: 1.0, };
let color_label = BGR { r: 1.0, g: 1.0, b: 1.0, };
let color_sublabel = BGR { r: 0.7, g: 0.7, b: 0.7, };
let modifier = part.modifier_id();
if modifier != 0 {
let modifier = modifier - 1;
if mod_state[modifier] == ModState::Locked
|| mod_state[modifier] == ModState::HeldLocked {
return color_locked;
} else if mod_state[modifier] != ModState::Released {
return color_pressed;
}
}
if part.presses() != 0 {
return color_pressed;
}
if sublabel {
return color_sublabel;
}
let res = LABELS.binary_search_by_key(&part.sym(), |&Label { sym, .. }| sym);
if let Ok(idx) = res {
if LABELS[idx].secondary {
return color_sublabel;
}
}
color_label
}
fn draw_key(labels: &HashMap<Keysym, ImgVec<u8>>,
sublabels: &HashMap<Keysym, ImgVec<u8>>,
x_scale: f64, y_scale: f64,
img: &mut ImgRefMut<BGRA<u8>>,
key: &Key, mod_state: &[ModState])
{
let x1 = (key.x1 * x_scale) as usize;
let y1 = (key.y1 * y_scale) as usize;
let x2 = (key.x2 * x_scale) as usize;
let y2 = (key.y2 * y_scale) as usize;
let mut keyimg = img.sub_image_mut(x1, y1, x2 - x1, y2 - y1);
let mut pressed = false;
for part in &key.parts {
let modifier = part.modifier_id();
if modifier != 0 && mod_state[modifier - 1] != ModState::Released {
pressed = true;
break;
}
if part.presses() != 0 {
pressed = true;
break;
}
}
if pressed {
fill_image(&mut keyimg, BGRA { r: 51, g: 51, b: 51, a: 0, });
} else {
fill_image(&mut keyimg, BGRA { r: 0, g: 0, b: 0, a: 0, });
}
let color = Self::keypart_to_color(&key.parts[0], false, mod_state);
let sym = key.parts[0].display_symbol(mod_state);
Self::draw_label_part(labels, Anchor::Center, Anchor::Center,
&mut keyimg, sym, color);
for i in 0..8 {
let color = Self::keypart_to_color(&key.parts[i + 1], true, mod_state);
let sym = key.parts[i + 1].display_symbol(mod_state);
Self::draw_label_part(sublabels, ANCHORS[i].0, ANCHORS[i].1,
&mut keyimg, sym, color);
}
}
pub fn draw_single(&mut self, key: &Key, mod_state: &[ModState])
{
let mut img = self.disp.begin();
let x1 = (key.x1 * self.x_scale) as u32;
let y1 = (key.y1 * self.y_scale) as u32;
let x2 = (key.x2 * self.x_scale) as u32;
let y2 = (key.y2 * self.y_scale) as u32;
Self::draw_key(&self.labels, &self.sublabels,
self.x_scale, self.y_scale,
&mut img, key, mod_state);
self.disp.end(x1, y1, x2 - x1, y2 - y1);
}
pub fn draw_modifiers(&mut self,
layout: &Layout,
mod_state: &[ModState],
modifiers: &[bool])
{
for row in layout.rows() {
for key in row {
let draw = key.parts.iter().any(|p| p.modifier_id() != 0
&& modifiers[p.modifier_id() - 1]);
if draw {
self.draw_single(key, mod_state);
}
}
}
}
pub fn draw(&mut self, layout: &Layout, mod_state: &[ModState])
{
let mut img = self.disp.begin();
fill_image(&mut img, BGRA { r: 0, g: 0, b: 0, a: 0, });
for row in layout.rows() {
for key in row {
Self::draw_key(&self.labels, &self.sublabels,
self.x_scale, self.y_scale,
&mut img, key, mod_state);
}
}
let (width, height) = (img.width() as u32, img.height() as u32);
self.disp.end(0, 0, width, height);
}
pub fn change_layout(&mut self, layout: &Layout, mod_state: &[ModState])
{
self.rasterize_labels(layout, mod_state);
self.draw(layout, mod_state);
}
pub fn resize(&mut self,
layout: &Layout, mod_state: &[ModState],
width: u32, height: u32)
{
self.x_scale = width as f64 / layout.width();
self.y_scale = height as f64 / layout.height();
self.rasterize_labels(layout, mod_state);
self.disp.resize(width, height);
self.draw(layout, mod_state);
}
#[inline(always)]
pub fn display(&self) -> &D
{
&self.disp
}
#[inline(always)]
pub fn display_mut(&mut self) -> &mut D
{
&mut self.disp
}
}
impl<D: Display> Drop for Graphics<D> {
fn drop(&mut self)
{
unsafe {
freetype::FT_Done_FreeType(self.ft);
}
}
}