666 lines
20 KiB
Rust
666 lines
20 KiB
Rust
// SPDX-License-Identifier: GPL-3.0-only
|
|
/*
|
|
* Copyright (c) 2024, Richard Acayan. All rights reserved.
|
|
*/
|
|
|
|
use crate::core::Layout;
|
|
use crate::core::button::ModState;
|
|
use crate::core::layout::Key;
|
|
use crate::core::layout::Part;
|
|
use freetype::freetype;
|
|
use imgref::ImgRef;
|
|
use imgref::ImgRefMut;
|
|
use imgref::ImgVec;
|
|
use rgb::alt::BGR;
|
|
use rgb::alt::BGRA;
|
|
use std::collections::HashMap;
|
|
use std::fs::File;
|
|
use std::io::Read;
|
|
use std::cmp;
|
|
use std::iter;
|
|
use std::ptr;
|
|
use std::sync::atomic::AtomicPtr;
|
|
|
|
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.saturating_add((*src as f32 * fg_color.r) as u8);
|
|
dest.g = dest.g.saturating_add((*src as f32 * fg_color.g) as u8);
|
|
dest.b = dest.b.saturating_add((*src as f32 * fg_color.b) as u8);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn copy_image(mut dest: ImgRefMut<u8>, src: ImgRef<u8>)
|
|
{
|
|
for (dest, src) in iter::zip(dest.pixels_mut(), src.pixels()) {
|
|
*dest = src.saturating_add(*dest);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
struct TextRaster {
|
|
img: ImgVec<u8>,
|
|
y: usize,
|
|
}
|
|
|
|
pub struct Graphics<D: Display> {
|
|
disp: D,
|
|
fonts: Vec<(String, freetype::FT_Long)>,
|
|
fontbufs: Vec<Vec<u8>>,
|
|
labels: HashMap<String, TextRaster>,
|
|
sublabels: HashMap<String, TextRaster>,
|
|
ft: AtomicPtr<freetype::FT_LibraryRec_>,
|
|
|
|
x_scale: f64,
|
|
y_scale: f64,
|
|
}
|
|
|
|
struct Label<'a> {
|
|
label: &'a str,
|
|
secondary: bool,
|
|
small: bool,
|
|
}
|
|
|
|
const LABELS: [Label; 24] = [
|
|
Label { label: "123+",
|
|
secondary: true, small: true, },
|
|
Label { label: "ABC",
|
|
secondary: true, small: true, },
|
|
Label { label: "Alt",
|
|
secondary: true, small: true, },
|
|
Label { label: "Ctrl",
|
|
secondary: true, small: true, },
|
|
Label { label: "Esc",
|
|
secondary: true, small: true, },
|
|
Label { label: "F11",
|
|
secondary: false, small: true, },
|
|
Label { label: "F12",
|
|
secondary: false, small: true, },
|
|
Label { label: "Fn",
|
|
secondary: true, small: true, },
|
|
Label { label: "Ins",
|
|
secondary: true, small: true, },
|
|
Label { label: "Meta",
|
|
secondary: true, small: true, },
|
|
Label { label: "\u{2190}", // LEFTWARDS ARROW
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{2191}", // UPWARDS ARROW
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{2192}", // RIGHTWARDS ARROW
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{2193}", // DOWNWARDS ARROW
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{21D1}", // UPWARDS DOUBLE ARROW
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{21D3}", // DOWNWARDS DOUBLE ARROW
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{21E4}", // LEFTWARDS ARROW TO BAR
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{21E5}", // RIGHTWARDS ARROW TO BAR
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{21E7}", // UPWARDS WHITE ARROW
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{2326}", // ERASE TO THE RIGHT
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{232B}", // ERASE TO THE LEFT
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{23CE}", // RETURN SYMBOL
|
|
secondary: true, small: false, },
|
|
Label { label: "\u{2423}", // OPEN BOX
|
|
secondary: true, small: true, },
|
|
Label { label: "\u{2B7E}", // HORIZONTAL TAB KEY
|
|
secondary: true, small: true, },
|
|
];
|
|
|
|
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);
|
|
}
|
|
|
|
AtomicPtr::new(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 is_label_small(label: &str) -> bool
|
|
{
|
|
match LABELS.binary_search_by_key(&label, |&Label { label, .. }| label) {
|
|
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.get_mut(),
|
|
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) -> TextRaster
|
|
{
|
|
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 = if text.is_empty() {
|
|
1
|
|
} else {
|
|
metrics.max_advance as usize / 64 * (text.len() - 1) + glyph_width
|
|
};
|
|
|
|
let y_max = cmp::max(metrics.ascender as isize / 64,
|
|
bbox.yMax as isize
|
|
* metrics.y_ppem as isize
|
|
/ units_per_em as isize);
|
|
let y_min = cmp::min(metrics.descender as isize / 64,
|
|
bbox.yMin as isize
|
|
* metrics.y_ppem as isize
|
|
/ units_per_em as isize);
|
|
let mut y = y_max as usize - metrics.ascender as usize / 64;
|
|
let height = (y_max - y_min) as usize;
|
|
|
|
let vec: Vec<u8> = vec![0; stride * height];
|
|
let mut img = ImgVec::new(vec, stride, height);
|
|
|
|
let mut pen: i64 = 0;
|
|
let mut height = (y_max as i64 - metrics.descender / 64) as usize;
|
|
|
|
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 = y_max as i64 - glyph.bitmap_top as i64;
|
|
|
|
if y > dest_y as usize {
|
|
y = dest_y as usize;
|
|
}
|
|
|
|
if height < dest_y as usize + glyph.bitmap.rows as usize {
|
|
height = dest_y as usize + glyph.bitmap.rows as usize;
|
|
}
|
|
|
|
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 mut vec = img.into_buf();
|
|
vec.truncate(stride * height);
|
|
let img = ImgVec::new_stride(vec, width, height, stride);
|
|
|
|
TextRaster {
|
|
img,
|
|
y,
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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, label: &str)
|
|
{
|
|
if !self.labels.contains_key(label) {
|
|
let size = if Self::is_label_small(label) {
|
|
size * 0.75
|
|
} else {
|
|
size
|
|
};
|
|
|
|
let img = self.rasterize_text(size, label);
|
|
self.labels.insert(String::from(label), 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, label: &str)
|
|
{
|
|
if !self.sublabels.contains_key(label) {
|
|
let size = if Self::is_label_small(label) {
|
|
size * 0.75
|
|
} else {
|
|
size
|
|
};
|
|
|
|
let img = self.rasterize_text(size, label);
|
|
self.sublabels.insert(String::from(label), img);
|
|
}
|
|
}
|
|
|
|
fn rasterize_labels(&mut self, layout: &Layout)
|
|
{
|
|
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 label = key.parts[0].display_label();
|
|
self.rasterize_label(label_size, label);
|
|
|
|
for part in &key.parts[1..] {
|
|
let label = part.display_label();
|
|
self.rasterize_sublabel(sublabel_size, label);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn draw_label_part(labels: &HashMap<String, TextRaster>,
|
|
x_anchor: Anchor, y_anchor: Anchor,
|
|
dest: &mut ImgRefMut<BGRA<u8>>,
|
|
label: &str, fg_color: BGR<f32>)
|
|
{
|
|
let src = labels.get(label)
|
|
.expect("Layout and size should be set before drawing");
|
|
|
|
let width = src.img.width();
|
|
let height = src.img.height() - src.y;
|
|
|
|
let x = match x_anchor {
|
|
Anchor::Min => 0,
|
|
Anchor::Center => (dest.width() - width) / 2,
|
|
Anchor::Max => dest.width() - width,
|
|
};
|
|
|
|
let y = match y_anchor {
|
|
Anchor::Min => 0,
|
|
Anchor::Center => (dest.height() - height) / 2,
|
|
Anchor::Max => dest.height() - height,
|
|
};
|
|
|
|
let src = src.img.sub_image(0, src.y, width, height);
|
|
let dest = dest.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
|
|
|| mod_state[modifier] == ModState::HeldLockedPressed {
|
|
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 label = part.display_label();
|
|
let res = LABELS.binary_search_by_key(&label, |&Label { label, .. }| label);
|
|
if let Ok(idx) = res {
|
|
if LABELS[idx].secondary {
|
|
return color_sublabel;
|
|
}
|
|
}
|
|
|
|
color_label
|
|
}
|
|
|
|
fn draw_key(labels: &HashMap<String, TextRaster>,
|
|
sublabels: &HashMap<String, TextRaster>,
|
|
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 label = key.parts[0].display_label();
|
|
Self::draw_label_part(labels, Anchor::Center, Anchor::Center,
|
|
&mut keyimg, label, color);
|
|
|
|
for i in 0..8 {
|
|
let color = Self::keypart_to_color(&key.parts[i + 1], true, mod_state);
|
|
let label = key.parts[i + 1].display_label();
|
|
Self::draw_label_part(sublabels, ANCHORS[i].0, ANCHORS[i].1,
|
|
&mut keyimg, label, 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])
|
|
{
|
|
let (width, height) = self.disp.size();
|
|
self.x_scale = width as f64 / layout.width();
|
|
self.y_scale = height as f64 / layout.height();
|
|
|
|
self.rasterize_labels(layout);
|
|
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);
|
|
|
|
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.get_mut());
|
|
}
|
|
}
|
|
}
|