Skip to main content

kobo_core/rendering/
text_render.rs

1pub mod blit;
2pub mod fonts;
3
4pub use blit::*;
5pub use fonts::*;
6
7const FONT_LATIN: &[u8] = include_bytes!("../../fonts/NotoSansLatin.ttf");
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Script {
11    Latin,
12    Bengali,
13    Devanagari,
14    Arabic,
15    Hebrew,
16    Cjk,
17    Thai,
18    Other,
19}
20
21impl Script {
22    pub fn is_rtl(self) -> bool {
23        matches!(self, Script::Arabic | Script::Hebrew)
24    }
25
26    pub fn uses_word_spacing(self) -> bool {
27        !matches!(self, Script::Cjk | Script::Thai)
28    }
29
30    pub fn lang_tag(self) -> &'static str {
31        match self {
32            Script::Latin => "en-US",
33            Script::Bengali => "bn-BD",
34            Script::Devanagari => "hi-IN",
35            Script::Arabic => "ar-SA",
36            Script::Hebrew => "he-IL",
37            Script::Cjk => "ja-JP",
38            Script::Thai => "th-TH",
39            Script::Other => "en-US",
40        }
41    }
42}
43
44pub fn detect_script(text: &str) -> Script {
45    for c in text.chars() {
46        if !c.is_alphabetic() {
47            continue;
48        }
49        let code = c as u32;
50        return match code {
51            0x0980..=0x09FF => Script::Bengali,
52            0x0900..=0x097F => Script::Devanagari,
53            0x0600..=0x06FF | 0x0750..=0x077F | 0xFB50..=0xFDFF | 0xFE70..=0xFEFF => Script::Arabic,
54            0x0590..=0x05FF => Script::Hebrew,
55            0x3040..=0x30FF | 0x4E00..=0x9FFF | 0x3400..=0x4DBF | 0xAC00..=0xD7AF => Script::Cjk,
56            0x0E00..=0x0E7F => Script::Thai,
57            0x0000..=0x024F => Script::Latin,
58            _ => Script::Other,
59        };
60    }
61    Script::Latin
62}
63
64static WIDTH_CACHE: std::sync::OnceLock<
65    std::sync::Mutex<std::collections::HashMap<(String, u32), f32>>,
66> = std::sync::OnceLock::new();
67
68pub(crate) fn width_cache(
69) -> &'static std::sync::Mutex<std::collections::HashMap<(String, u32), f32>> {
70    WIDTH_CACHE.get_or_init(std::sync::Mutex::default)
71}
72
73#[allow(dead_code)]
74pub fn clear_width_cache() {
75    if let Ok(mut cache) = width_cache().lock() {
76        cache.clear();
77    }
78}
79
80pub struct DecodedImage {
81    pub rgb: Vec<u8>,
82    pub width: usize,
83    pub height: usize,
84}
85
86pub fn decode_image(raw: &[u8], max_w: usize, max_h: usize) -> Option<DecodedImage> {
87    let img = image::load_from_memory(raw).ok()?;
88    let (ow, oh) = (img.width() as usize, img.height() as usize);
89    if ow == 0 || oh == 0 {
90        return None;
91    }
92    let scale = max_w as f32 / ow as f32;
93    let mut nw = max_w;
94    let mut nh = (oh as f32 * scale).round() as usize;
95    if nh == 0 {
96        return None;
97    }
98    if nh > max_h {
99        let hscale = max_h as f32 / nh as f32;
100        nh = max_h;
101        nw = (nw as f32 * hscale).round() as usize;
102        if nw == 0 {
103            return None;
104        }
105    }
106    let resized = img.resize(nw as u32, nh as u32, image::imageops::FilterType::Triangle);
107    let rgb = resized.to_rgb8();
108    let (rw, rh) = (rgb.width() as usize, rgb.height() as usize);
109    Some(DecodedImage {
110        rgb: rgb.into_raw(),
111        width: rw,
112        height: rh,
113    })
114}
115
116pub fn blit_rgb565_image(
117    buf: &mut [u8],
118    buf_stride: usize,
119    rgb: &[u8],
120    iw: usize,
121    ih: usize,
122    ox: usize,
123    oy: usize,
124    max_w: usize,
125    max_h: usize,
126) {
127    for ry in 0..ih {
128        let py = oy + ry;
129        if py >= max_h {
130            break;
131        }
132        for rx in 0..iw {
133            let px = ox + rx;
134            if px >= max_w {
135                break;
136            }
137            let idx = (ry * iw + rx) * 3;
138            let r = rgb[idx] as u16;
139            let g = rgb[idx + 1] as u16;
140            let b = rgb[idx + 2] as u16;
141            let r5 = (r >> 3) & 0x1f;
142            let g6 = (g >> 2) & 0x3f;
143            let b5 = (b >> 3) & 0x1f;
144            let v = (r5 << 11) | (g6 << 5) | b5;
145            let off = (py * buf_stride + px) * 2;
146            if off + 2 > buf.len() {
147                continue;
148            }
149            buf[off] = (v & 0xff) as u8;
150            buf[off + 1] = (v >> 8) as u8;
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests;