kobo-core 0.2.2

Kobo e-reader device SDK: device database, sysfs/ioctl, rendering, audio pipeline, trait surface
Documentation
use crate::rendering::common::BODY_PX;
use crate::rendering::layout::word_wrap_bytes;
use crate::rendering::text_render;

pub fn paint_placeholder_box(
    buf_bytes: &mut [u8],
    screen_w: usize,
    screen_h: usize,
    x: usize,
    y: usize,
    w: usize,
    h: usize,
) {
    for ry in 0..h {
        for rx in 0..w {
            let px = x + rx;
            let py = y + ry;
            if px >= screen_w || py >= screen_h {
                continue;
            }
            let off = (py * screen_w + px) * 2;
            if off + 2 > buf_bytes.len() {
                continue;
            }
            let border = rx == 0 || ry == 0 || rx == w - 1 || ry == h - 1;
            let v = if border { 0x6B4D } else { 0xEF5D };
            buf_bytes[off] = (v & 0xff) as u8;
            buf_bytes[off + 1] = (v >> 8) as u8;
        }
    }
}

pub fn fill_rounded_rect(
    buf_bytes: &mut [u8],
    screen_w: usize,
    screen_h: usize,
    x: usize,
    y: usize,
    w: usize,
    h: usize,
    fill: u16,
    border: u16,
    radius: usize,
) {
    let r = radius.min(w / 2).min(h / 2);
    let put = |buf: &mut [u8], px: usize, py: usize, v: u16| {
        if px >= screen_w || py >= screen_h {
            return;
        }
        let off = (py * screen_w + px) * 2;
        if off + 2 > buf.len() {
            return;
        }
        buf[off] = (v & 0xff) as u8;
        buf[off + 1] = (v >> 8) as u8;
    };
    for ry in 0..h {
        for rx in 0..w {
            let cx_edge = if rx < r {
                r - rx
            } else if rx >= w - r {
                rx - (w - 1 - r)
            } else {
                0
            };
            let cy_edge = if ry < r {
                r - ry
            } else if ry >= h - r {
                ry - (h - 1 - r)
            } else {
                0
            };
            let in_corner = (rx < r || rx >= w - r) && (ry < r || ry >= h - r);
            let v = if in_corner {
                let dist2 =
                    (cx_edge as i32) * (cx_edge as i32) + (cy_edge as i32) * (cy_edge as i32);
                if dist2 > (r as i32) * (r as i32) {
                    continue;
                } else if dist2 >= ((r - 1) as i32) * ((r - 1) as i32) {
                    border
                } else {
                    fill
                }
            } else {
                let on_edge = rx == 0 || ry == 0 || rx == w - 1 || ry == h - 1;
                if on_edge {
                    border
                } else {
                    fill
                }
            };
            put(buf_bytes, x + rx, y + ry, v);
        }
    }
}

pub fn paint_progress_bar(
    buf_bytes: &mut [u8],
    screen_w: usize,
    screen_h: usize,
    x: usize,
    y: usize,
    w: usize,
    frac: f32,
) {
    let bar_h = 8usize;
    for ry in 0..bar_h {
        let py = y + ry;
        if py >= screen_h {
            break;
        }
        for rx in 0..w {
            let px = x + rx;
            if px >= screen_w {
                break;
            }
            let off = (py * screen_w + px) * 2;
            if off + 2 > buf_bytes.len() {
                continue;
            }
            let filled = (rx as f32 / w as f32) < frac;
            let v = if filled { 0x1084 } else { 0xC639 };
            buf_bytes[off] = (v & 0xff) as u8;
            buf_bytes[off + 1] = (v >> 8) as u8;
        }
    }
}

pub fn measure_text(text: &str, px: f32) -> usize {
    let space_w = text_render::word_width(" ", px);
    let mut total = 0.0f32;
    for (i, word) in text.split(' ').enumerate() {
        if i > 0 {
            total += space_w;
        }
        if !word.is_empty() {
            total += text_render::word_width(word, px);
        }
    }
    total as usize
}

pub const ACTION_BTN_FILL: u16 = 0x0953;
pub const ACTION_BTN_FG: u16 = 0xFFFF;
pub const ACTION_BTN_RADIUS: usize = 10;

pub fn paint_action_button(
    buf_bytes: &mut [u8],
    screen_w: usize,
    screen_h: usize,
    x: usize,
    y: usize,
    w: usize,
    h: usize,
    label: &str,
    px: f32,
) {
    fill_rounded_rect(
        buf_bytes,
        screen_w,
        screen_h,
        x,
        y,
        w,
        h,
        ACTION_BTN_FILL,
        ACTION_BTN_FILL,
        ACTION_BTN_RADIUS,
    );
    let lw = measure_text(label, px);
    let lh = text_render::line_height(px);
    let tx = x + (w.saturating_sub(lw)) / 2;
    let ty = y + (h.saturating_sub(lh)) / 2;
    text_render::blit_rgb565_color(
        buf_bytes,
        screen_w,
        label,
        px,
        tx,
        ty,
        ACTION_BTN_FG,
        screen_w,
        screen_h,
    );
}

pub fn paint_nav_bar(
    buf_bytes: &mut [u8],
    screen_w: usize,
    screen_h: usize,
    nav_bar_h: usize,
    y: usize,
    center: &str,
    clock: &str,
    battery: i32,
) {
    let h = nav_bar_h;
    let top = y;
    for ry in 0..h {
        let py = top + ry;
        if py >= screen_h {
            break;
        }
        let is_edge = ry == 0;
        for rx in 0..screen_w {
            let off = (py * screen_w + rx) * 2;
            if off + 2 > buf_bytes.len() {
                break;
            }
            let v = if is_edge { 0x1084 } else { 0xFFFF };
            buf_bytes[off] = (v & 0xff) as u8;
            buf_bytes[off + 1] = (v >> 8) as u8;
        }
    }
    let label_px = BODY_PX * 0.8;
    let small_px = BODY_PX * 0.66;
    let lh = text_render::line_height(label_px);
    let sh = text_render::line_height(small_px);
    let cy = top + (h.saturating_sub(lh)) / 2;
    let sy = top + (h.saturating_sub(sh)) / 2;

    let exit_w = 150usize;
    let exit_h = 56usize.min(h - 8);
    let exit_x = 12usize;
    let exit_y = top + (h.saturating_sub(exit_h)) / 2;
    paint_action_button(
        buf_bytes, screen_w, screen_h, exit_x, exit_y, exit_w, exit_h, "Exit", label_px,
    );

    let status = if battery > 0 {
        format!("{}  {}%", clock, battery)
    } else {
        clock.to_string()
    };
    if !status.is_empty() {
        let sw = measure_text(&status, small_px);
        let sx = screen_w.saturating_sub(sw + 24);
        text_render::blit_rgb565(
            buf_bytes, screen_w, &status, small_px, sx, sy, screen_w, screen_h,
        );
    }
    if !center.is_empty() {
        let label_w = measure_text(center, label_px);
        let cx = (screen_w / 2).saturating_sub(label_w / 2);
        text_render::blit_rgb565(
            buf_bytes, screen_w, center, label_px, cx, cy, screen_w, screen_h,
        );
    }
}

pub fn paint_wrapped_text(
    buf_bytes: &mut [u8],
    screen_w: usize,
    screen_h: usize,
    text: &str,
    x: usize,
    y: usize,
    max_w: usize,
    px: f32,
    max_lines: usize,
) -> usize {
    let lines = word_wrap_bytes(text, max_w, px);
    let lh = text_render::line_height(px);
    let mut cy = y;
    for (i, line) in lines.iter().enumerate() {
        if i >= max_lines {
            break;
        }
        text_render::blit_rgb565(
            buf_bytes, screen_w, &line.text, px, x, cy, screen_w, screen_h,
        );
        cy += lh;
    }
    cy - y
}

#[cfg(test)]
mod tests;