Panic in Bitmap::decode on zero-width EBDT glyphs (SimSun): "chunk size must be non-zero"

Open Beginner friendly
#139 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
rust

Research direction

Start in strike.rs at Bitmap::decode, especially the BitmapFormat::Alpha branches using chunks. Reproduce with the provided SimSun example through Render::render or Scaler::scale_bitmap, then verify zero-sized glyphs avoid a panic and fall back cleanly while non-empty bitmap decoding remains unchanged.

Written by the indexing model from the issue text.

Description

Summary

Bitmap::decode panics when decoding a bitmap glyph whose width is 0. Such glyphs are valid font data — e.g. blank glyphs in the embedded bitmap strikes (EBLC/EBDT) of CJK fonts like SimSun (simsun.ttc, strikes at ppem 12–17, ~28k glyphs per strike). Any caller using Source::Bitmap(..) / Scaler::scale_bitmap* on such a font will hit the panic while rendering ordinary text.

Environment

  • swash 0.2.10 (crates.io)
  • Windows 11 Pro (10.0.26200)
  • Font: C:\Windows\Fonts\simsun.ttc, collection index 0 (SimSun as shipped with Windows 11)
  • Render path: Render::new(&[Source::Bitmap(StrikeWith::ExactSize)]) (we enable this in a bevy_text patch to prefer embedded bitmap strikes)

Panic

thread 'Compute Task Pool (0)' panicked at ...\.cargo\registry\src\...\swash-0.2.10\src\strike.rs:439:36:
chunk size must be non-zero

strike.rs:439 is the 1-bit branch of BitmapFormat::Alpha in Bitmap::decode:

BitmapFormat::Alpha(bits) => match bits {
    1 => {
        let mut dst_idx = 0;
        for row in src.chunks(((w * bits as usize) + 7) / 8) {   // w == 0  =>  chunks(0) panics

The 2-bit and 4-bit branches below it compute the same row stride and panic identically.

Root cause

For w == 0 the row stride ((w * bits) + 7) / 8 evaluates to 0, and slice::chunks panics on zero chunk size by contract. decoded_size() is also 0 for such glyphs, so the earlier target.len() < size check does not filter them out.

Zero-width/zero-height bitmap entries are legitimate font data (blank glyphs); FreeType returns an empty bitmap for these rather than failing, so native apps render such fonts without issues.

Minimal reproduction

// Cargo.toml: swash = "0.2.10"
use swash::{
    FontRef,
    scale::{Render, ScaleContext, Source, StrikeWith},
    zeno::Format,
};

fn main() {
    let data = std::fs::read(r"C:\Windows\Fonts\simsun.ttc").unwrap();
    let font = FontRef::from_index(&data, 0).unwrap();
    let mut cx = ScaleContext::new();
    let mut scaler = cx.builder(font).size(12.0).build();
    let chmap = font.charmap();

    for u in 0u32..=0x10FFFF {
        let Some(ch) = char::from_u32(u) else { continue };
        let gid = chmap.map(ch);
        if gid == 0 { continue; }
        // Panics on the first glyph whose EBDT entry has width 0:
        let _ = Render::new(&[Source::Bitmap(StrikeWith::ExactSize)])
            .format(Format::Alpha)
            .render(&mut scaler, gid);
    }
    println!("no panic");
}

Rendering ordinary text (e.g. a chat message) through the bitmap source panics the same way; we hit this in a live app.

Suggested fix

Treat zero-sized bitmaps as a decode failure so callers can fall back to the next source (this is the one-line guard we run locally):

         let w = self.width as usize;
         let h = self.height as usize;
+        // Zero-sized bitmaps are valid font data (blank glyphs in some EBDT
+        // strikes, e.g. SimSun). Fail the decode so callers can fall back
+        // to another source instead of panicking on chunks(0).
+        if w == 0 || h == 0 {
+            return false;
+        }
         let src = self.data;

With this guard, Render::render_into cleanly falls through to the next source in the chain (e.g. Source::Outline), and Scaler::scale_bitmap returns None — no behavior change for non-empty bitmaps. We verified by scanning SimSun's entire BMP charmap across all strikes (ppem 12–17, ~28,428 bitmap glyphs per strike): stock 0.2.10 panics, patched build renders everything with graceful fallback for the zero-sized entries.

Alternatively, if an empty 0×0 image is considered the more correct representation of a blank glyph, decode could succeed with an empty buffer — either way, panicking on valid font data should be avoided.

Dominant language
Rust
Stars
872
Forks
62
PR merge metrics
No merged PRs in 30d

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dfrg/swash

All issues in dfrg/swash

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.