Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use crate::framebuffer::FrameBuffer;
use crate::handlers::LineHandler;
use rusttype::LayoutIter;
use rusttype::{point, FontCollection, Scale};
use serenity::async_trait;
use serenity::http::AttachmentType;
use serenity::model::channel::Message;
use serenity::prelude::*;
use std::borrow::Cow;
const COLOUR: (u8, u8, u8) = (255, 0, 0);
const SCALE: Scale = Scale { x: 48.0, y: 48.0 };
const PADDING_X: u32 = 0;
const PADDING_Y: u32 = 0;
const MAX_WIDTH: i32 = 800;
const Y_GAP: i32 = 5;
// Wrap text when width exceeds MAX_WIDTH
struct WrappingLayoutIter<'a> {
iter: LayoutIter<'a, 'a>,
offset_x: i32,
offset_y: i32,
cur_x: i32,
cur_y: i32,
}
impl<'a> WrappingLayoutIter<'a> {
fn new(iter: LayoutIter<'a, 'a>, offset_x: i32, offset_y: i32) -> Self {
Self {
iter,
offset_x,
offset_y,
cur_x: 0,
cur_y: 0,
}
}
}
impl<'a> Iterator for WrappingLayoutIter<'a> {
type Item = Vec<(i32, i32, f32)>;
fn next(&mut self) -> Option<Self::Item> {
let (glyph, bounding_box) = loop {
let glyph = self.iter.next()?;
if let Some(bb) = glyph.pixel_bounding_box() {
break (glyph, bb);
}
};
if bounding_box.max.x + self.cur_x > MAX_WIDTH {
self.cur_x = -bounding_box.min.x;
self.cur_y += bounding_box.max.y - bounding_box.min.y + Y_GAP;
}
let mut buf = Vec::new();
glyph.draw(|x, y, a| {
let x = x as i32 + bounding_box.min.x + self.offset_x + self.cur_x;
let y = y as i32 + bounding_box.min.y + self.offset_y + self.cur_y;
buf.push((x, y, a));
});
Some(buf)
}
}
#[derive(Default)]
pub struct SusHandler;
impl SusHandler {
async fn render_font(&self, ctx: &Context, msg: &Message, s: &str) {
let font_data = include_bytes!("../amongus.ttf");
let collection = FontCollection::from_bytes(font_data as &[u8]);
let font = collection.into_font().unwrap();
let start = point(0.0, 0.0);
// Find image dimensions
let mut min_x = 0;
let mut max_x = 0;
let mut max_y = 0;
let mut min_y = 0;
for arr in WrappingLayoutIter::new(font.layout(s, SCALE, start), 0, 0) {
for (x, y, _) in arr {
if x < min_x {
min_x = x;
}
if x > max_x {
max_x = x;
}
if y < min_y {
min_y = y;
}
if y > max_y {
max_y = y;
}
}
}
let offset_x = -min_x;
let offset_y = -min_y;
max_x += offset_x;
max_y += offset_y;
let mut framebuffer =
FrameBuffer::new(max_x as u32 + PADDING_X * 2, max_y as u32 + PADDING_Y * 2);
for arr in WrappingLayoutIter::new(
font.layout(s, SCALE, start),
offset_x + PADDING_X as i32,
offset_y + PADDING_Y as i32,
) {
for (x, y, a) in arr {
framebuffer.set_pixel(
x as u32,
y as u32,
COLOUR.0,
COLOUR.1,
COLOUR.2,
(a * 255.0) as u8,
);
}
}
let buf = framebuffer.as_png_vec();
msg.channel_id
.send_message(&ctx, |e| {
e.add_file(AttachmentType::Bytes {
data: Cow::Borrowed(&buf),
filename: "output.png".to_string(),
});
e
})
.await
.unwrap();
}
}
#[async_trait]
impl LineHandler for SusHandler {
async fn line(&self, ctx: &Context, msg: &Message, line: &str) {
if line.starts_with("!SUS") {
let s = line.split(' ').collect::<Vec<_>>()[1..]
.join(" ")
.to_uppercase();
if s.is_empty() {
return;
}
self.render_font(ctx, msg, &s).await;
}
}
}