1use std::io::Cursor;
2
3use anyhow::{Context, Result};
4use image::{DynamicImage, ImageFormat, Rgba};
5use rusttype::{Font, PositionedGlyph, Scale, point};
6
7pub struct FontRenderer {
8 font: Font<'static>,
9}
10
11impl FontRenderer {
12 pub fn new(ttf_bin: Vec<u8>) -> Result<Self> {
13 let font = Font::try_from_vec(ttf_bin).context("Invalid font data")?;
15
16 Ok(Self { font })
17 }
18
19 pub fn draw_multiline_text(
20 &self,
21 fgcolor: (u8, u8, u8),
22 bgcolor: (u8, u8, u8),
23 text: &str,
24 scale: u32,
25 width: u32,
26 ) -> Vec<u8> {
27 let scale = Scale::uniform(scale as f32);
28 let vmet = self.font.v_metrics(scale);
29 let glyphs_h = (vmet.ascent - vmet.descent).ceil() as u32;
30
31 let mut lines = Vec::new();
32 for orig_line in text.split('\n') {
34 let mut start = 0_usize;
37 if orig_line.is_empty() {
39 lines.push(Vec::new());
40 }
41 while start < orig_line.len() {
42 let rest = &orig_line[start..orig_line.len()];
45 let idxlist: Vec<_> = rest.char_indices().map(|(i, _c)| i).collect();
47
48 let mut le = 0_usize;
49 let mut ri = idxlist.len() + 1;
50 while le < ri {
51 let m = le + (ri - le) / 2;
52 let m_idx = *idxlist.get(m).unwrap_or(&rest.len());
53 let (glyphs_w, _) = self.calc_line_w(
54 &rest[0..m_idx],
55 scale,
56 (glyphs_h * lines.len() as u32) as f32,
57 vmet.ascent,
58 );
59 if glyphs_w <= width {
60 le = m + 1;
61 } else {
62 ri = m;
63 }
64 }
65 let le_idx = *idxlist.get(le.saturating_sub(1)).unwrap_or(&rest.len());
67 let (_w, glyphs) = self.calc_line_w(
68 &rest[0..le_idx],
69 scale,
70 (glyphs_h * lines.len() as u32) as f32,
71 vmet.ascent,
72 );
73 lines.push(glyphs);
74
75 start += le_idx;
76 }
77 }
78
79 let height = glyphs_h * lines.len() as u32;
80 let mut image = DynamicImage::new_rgba8(width, height).to_rgba8();
81 for (_x, _y, pixel) in image.enumerate_pixels_mut() {
82 *pixel = Rgba([bgcolor.0, bgcolor.1, bgcolor.2, 255_u8]);
83 }
84
85 for glyphs in lines {
86 for glyph in glyphs {
87 if let Some(bounding_box) = glyph.pixel_bounding_box() {
88 glyph.draw(|x, y, v| {
90 let x = x + bounding_box.min.x as u32;
91 let y = y + bounding_box.min.y as u32;
92 if x >= width || y >= height {
93 return;
94 }
95 let [r1, g1, b1, _a1] = image.get_pixel(x, y).0;
96 let [r2, g2, b2] = [fgcolor.0, fgcolor.1, fgcolor.2];
97 let r3 = (r1 as f32 * (1.0 - v) + r2 as f32 * v) as u8;
98 let g3 = (g1 as f32 * (1.0 - v) + g2 as f32 * v) as u8;
99 let b3 = (b1 as f32 * (1.0 - v) + b2 as f32 * v) as u8;
100 image.put_pixel(
101 x,
103 y,
104 Rgba([r3, g3, b3, 255]),
105 )
106 });
107 }
108 }
109 }
110
111 let mut filebuf = Vec::new();
113 image
114 .write_to(&mut Cursor::new(&mut filebuf), ImageFormat::Png)
115 .unwrap();
116
117 filebuf
118 }
119
120 fn calc_line_w(
121 &self,
122 text: &str,
123 scale: Scale,
124 y: f32,
125 height: f32,
126 ) -> (u32, Vec<PositionedGlyph<'_>>) {
127 if text.is_empty() {
128 return (0, Vec::new());
129 }
130
131 let glyphs: Vec<_> = self
132 .font
133 .layout(text, scale, point(0.0, y + height))
134 .collect();
135
136 let mut min_x = i32::MAX;
137 let mut max_x = i32::MIN;
138 for g in glyphs.iter() {
139 let (le, ri) = match g.pixel_bounding_box() {
140 Some(bb) => (bb.min.x, bb.max.x),
141 None => (g.position().x as i32, g.position().x as i32),
142 };
143 min_x = min_x.min(le);
144 max_x = max_x.max(ri);
145 }
146
147 ((max_x - min_x) as u32, glyphs)
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use std::fs;
155
156 #[test]
157 #[ignore]
158 fn font() -> Result<()> {
161 let fname = "/usr/share/fonts/truetype/fonts-japanese-gothic.ttf";
162 println!("Load font file from: {fname}");
163 let ttf_bin = fs::read(fname)?;
164
165 let r = FontRenderer::new(ttf_bin)?;
166 let text =
167"役割論理(Role logic)は、形式言語の一種であり、論理学やコンピュータ科学の分野で用いられる言語です。役割論理では、対象の役割に着目し、役割に関する命題を表現します。役割論理には、役割論理の基本的な概念である「役割」、「役割変数」、「役割制約」、「役割コンストラクタ」などがあります。
168役割論理では、対象の役割を表現するために、役割変数を用います。役割変数は、役割を表す変数であり、通常はRやSなどのアルファベットで表されます。役割制約は、役割変数が取りうる値に制限を加える制約のことです。たとえば、「人間は動物である」という役割制約は、「人間」の役割変数に対して、「動物」の値を許容することを意味します。
169役割コンストラクタは、複数の役割を組み合わせて新しい役割を構成するための方法です。たとえば、「親子関係」という役割は、「親」と「子」の役割を組み合わせて構成されます。役割コンストラクタには、合成(composition)や逆(inverse)などがあります。
170役割論理は、主に知識表現や推論に用いられます。例えば、役割論理を用いて、複数のエージェントの間で共有される知識を表現することができます。また、役割論理は、オントロジー言語の一種であるOWL(Web Ontology Language)の基盤としても用いられています。";
171
172 let png = r.draw_multiline_text((0xff, 0xff, 0xff), (0x80, 0x00, 0x80), text, 16, 640);
173
174 let fname = "font_test.png";
175 println!("Write image to: {fname}");
176 fs::write(fname, png)?;
177
178 Ok(())
179 }
180}