Skip to content

Bug: Double-byte non-ASCII font display #132

Description

@dengyt2018

Describe the bug
在显示双字节的文字后会多出一个空白点位。
定位到 Backend 的 draw() 函数。当处理完一个 non-ascii 的双字节字符时会当两个字符占位,x 坐标多增加了一次。ascii 字符是正常显示。自定义字体也有同样问题。
测试的字体使用的是 embedded_graphics_unicodefonts::MONO_18X18_JA

After displaying double-byte characters, an extra blank space appears.

I've located the issue to the Backend's draw() function. When processing a non-ASCII double-byte character, it's being treated as two characters for spacing purposes, causing the x-coordinate to be incremented twice. ASCII characters display correctly.

Custom fonts have the same issue.

Test font used: embedded_graphics_unicodefonts::MONO_18X18_JA

Expected display image

Image

Abnormal display image

Image

Image from ESP32 development board

Image

Cargo.toml

[dependencies]
embedded-graphics-unicodefonts = "0.2"
embedded-graphics-simulator = "0.7"
embedded-graphics = "0.8.1"
ratatui = { version = "0.30.0-beta.0", default-features = false }
mousefood = { git = "https://github.com/j-g00da/mousefood.git", rev = "c8d5e4a5", default-features = false, features = ["fonts"] }

Test code

#![allow(unused)]

use embedded_graphics::image::ImageRaw;
use embedded_graphics::mono_font::MonoFont;
use embedded_graphics::pixelcolor::Bgr565;
use embedded_graphics::{
    mono_font::MonoTextStyle, pixelcolor::BinaryColor, prelude::*, text::Text,
};
use embedded_graphics_simulator::{
    BinaryColorTheme, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
};
use mousefood::error::Error;
use mousefood::{EmbeddedBackend, EmbeddedBackendConfig};
use ratatui::layout::{Constraint, Layout};
use ratatui::style::Stylize;
use ratatui::widgets::{Paragraph, Wrap};
use ratatui::{Frame, Terminal};

const TEXT: &str = "A点陣字ACああA";

fn main() -> Result<(), Error> {
    //let font = embedded_graphics_unicodefonts::MONO_6X12;
    let font = embedded_graphics_unicodefonts::MONO_18X18_JA;
    //let font = CUSTOM_FONT_16X16;

    let output_settings = OutputSettingsBuilder::new()
        .theme(BinaryColorTheme::OledBlue)
        .build();
    let mut display = SimulatorDisplay::<BinaryColor>::new(Size::new(320, 128));
    let text_style = MonoTextStyle::new(&font, BinaryColor::On);
    Text::new(TEXT, Point::new(0, 20), text_style)
        .draw(&mut display)
        .unwrap();
    Text::new(TEXT, Point::new(0, 80), text_style)
        .draw(&mut display)
        .unwrap();
    Window::new(
        "Display with normal spacing for double-byte characters",
        &output_settings,
    )
    .show_static(&display);

    let mut display = SimulatorDisplay::<Bgr565>::new(Size::new(320, 120));

    let mut simulator_window = Window::new(
        "Ratatui display with double-byte character spacing",
        &output_settings,
    );
    let mut backend_config = EmbeddedBackendConfig {
        flush_callback: Box::new(move |display| {
            simulator_window.update(display);
            if simulator_window.events().any(|e| e == SimulatorEvent::Quit) {
                panic!("simulator window closed");
            }
        }),
        ..Default::default()
    };
    backend_config.font_regular = font;

    let backend: EmbeddedBackend<SimulatorDisplay<_>, _> =
        EmbeddedBackend::new(&mut display, backend_config);

    let mut terminal = Terminal::new(backend)?;

    loop {
        terminal.draw(draw)?;
    }
}

fn draw(frame: &mut Frame) {
    let [a, b] = Layout::vertical([Constraint::Ratio(1, 2); 2])
        .spacing(0)
        .areas(frame.area());
    let paragraph = Paragraph::new(TEXT.dark_gray()).wrap(Wrap { trim: true });
    let paragraph2 = Paragraph::new(TEXT.dark_gray()).wrap(Wrap { trim: true });
    frame.render_widget(paragraph, a);
    frame.render_widget(paragraph2, b);
}

const CUSTOM_FONT_16X16: MonoFont = MonoFont {
    image: ImageRaw::<BinaryColor>::new(&FONT_DATA, FONT_DATA.len().div_ceil(2) as u32),
    character_size: Size::new(16, 16),
    character_spacing: 2,
    baseline: 13,
    strikethrough: embedded_graphics::mono_font::DecorationDimensions::default_strikethrough(16),
    underline: embedded_graphics::mono_font::DecorationDimensions::default_underline(16),
    glyph_mapping: &CustomGlyphMapping,
};

/// ImageRaw Font Data: 点陣字ABCあ
const FONT_DATA: [u8; 224] = [
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x7C, 0x20, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x02, 0x00, 0x7F, 0xFE, 0x02, 0x00, 0x45, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x02, 0xC0, 0x40, 0x02, 0x03, 0xFC, 0x48, 0x20, 0x18, 0x00, 0x7C, 0x00, 0x3C, 0x00, 0x1F, 0x00,
    0x80, 0x04, 0x02, 0x00, 0x49, 0xFC, 0x24, 0x00, 0x42, 0x00, 0x42, 0x00, 0x04, 0x80, 0x1F, 0xE0,
    0x02, 0x00, 0x51, 0x24, 0x24, 0x00, 0x42, 0x00, 0x42, 0x00, 0x04, 0x80, 0x00, 0x40, 0x1F, 0xF0,
    0x49, 0x24, 0x42, 0x00, 0x42, 0x00, 0x40, 0x00, 0x07, 0xE0, 0x00, 0x80, 0x20, 0x10, 0x49, 0xFC,
    0x42, 0x00, 0x7C, 0x00, 0x40, 0x00, 0x0D, 0x10, 0x01, 0x00, 0x20, 0x10, 0x45, 0x24, 0x7E, 0x00,
    0x42, 0x00, 0x40, 0x00, 0x15, 0x08, 0xFF, 0xFE, 0x20, 0x10, 0x45, 0x24, 0x42, 0x00, 0x42, 0x00,
    0x40, 0x00, 0x22, 0x08, 0x01, 0x00, 0x3F, 0xF0, 0x45, 0xFC, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00,
    0x26, 0x08, 0x01, 0x00, 0x00, 0x00, 0x68, 0x20, 0x42, 0x00, 0x42, 0x00, 0x42, 0x00, 0x2A, 0x10,
    0x01, 0x00, 0x24, 0x88, 0x53, 0xFE, 0x42, 0x00, 0x7C, 0x00, 0x3C, 0x00, 0x10, 0x60, 0x01, 0x00,
    0x22, 0x44, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x42, 0x44,
    0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x80, 0x04, 0x40, 0x20,
];
struct CustomGlyphMapping;

impl embedded_graphics::mono_font::mapping::GlyphMapping for CustomGlyphMapping {
    fn index(&self, c: char) -> usize {
        match c {
            'A' => 0,
            'B' => 1,
            'C' => 2,
            'あ' => 3,
            '字' => 4,
            '点' => 5,
            '陣' => 6,
            _ => 7,
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions