Panic: debug_assert in SubroutineCollection::new on a CID-keyed font with 256 Font DICTs

Aperta Adatta ai principianti
#34 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
74/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
rust
Ambito
tooling

Direzione di ricerca

Inizia da subsetter-0.2.6/src/cff/subroutines.rs, in particolare da SubroutineCollection::new e dal percorso get_handler, poi segui come viene rappresentato il conteggio di FDArray. Esegui il reproducer fornito oppure aggiungi un test di regressione per 256 Font DICT. Il lavoro è completato quando un font valido con 256 Font DICT non causa più un panic nelle build di debug e i conteggi non supportati vengono gestiti senza panic.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

subset panics on a CID-keyed CFF font with 256 Font DICTs, in any build with debug assertions (debug builds, cargo test, fuzzing):

thread 'main' panicked at subsetter-0.2.6/src/cff/subroutines.rs:11:9:
assertion failed: subroutines.len() <= 255

SubroutineCollection::new asserts that there are at most 255 local subroutine lists, one per Font DICT. FDSelect gives each glyph's Font DICT as a Card8 (Technical Note #5176, FDSelect formats 0 and 3), so indices 0 to 255 address 256 Font DICTs, and a font with 256 is well formed. Without debug assertions subset returns Ok. This happens with 0.2.6 and with main (b0adcc1).

To reproduce with subsetter 0.2.6 (default-features = false). The font is built in code for this report:

/// A CFF INDEX of `items`.
fn index(items: &[Vec<u8>]) -> Vec<u8> {
    if items.is_empty() {
        return vec![0, 0];
    }
    let mut offsets = vec![1u32];
    for item in items {
        offsets.push(offsets.last().unwrap() + item.len() as u32);
    }
    let size = (1..=4u8).find(|&size| *offsets.last().unwrap() < 1 << (8 * size as u32)).unwrap();
    let mut out = (items.len() as u16).to_be_bytes().to_vec();
    out.push(size);
    for offset in offsets {
        out.extend(&offset.to_be_bytes()[4 - size as usize..]);
    }
    items.iter().for_each(|item| out.extend(item));
    out
}

/// A DICT integer operand in five bytes.
fn int5(value: i32) -> Vec<u8> {
    [&[29u8][..], &value.to_be_bytes()].concat()
}

/// A minimal OpenType font whose CFF table is CID-keyed: one glyph (.notdef, endchar)
/// and an FDArray of `count` Font DICTs, all pointing at one empty Private DICT. FDSelect
/// (format 0) puts the glyph in Font DICT 0.
fn font(count: usize) -> Vec<u8> {
    let header = vec![1, 0, 4, 1];
    let names = index(&[b"A".to_vec()]);
    let strings = index(&[b"Adobe".to_vec(), b"Identity".to_vec()]);
    let gsubrs = index(&[]);
    let charstrings = index(&[vec![14]]);
    let fdselect = vec![0, 0];
    let top = |charstrings_at: i32, fdarray_at: i32, fdselect_at: i32| {
        [
            int5(391), int5(392), int5(0), vec![12, 30], // ROS Adobe-Identity-0
            int5(charstrings_at), vec![17],
            int5(fdarray_at), vec![12, 36],
            int5(fdselect_at), vec![12, 37],
        ]
        .concat()
    };
    let font_dict = |private_at: i32| [int5(0), int5(private_at), vec![18]].concat();
    let top_len = index(&[top(0, 0, 0)]).len();
    let fdarray_len = index(&vec![font_dict(0); count]).len();
    let charstrings_at = header.len() + names.len() + top_len + strings.len() + gsubrs.len();
    let fdselect_at = charstrings_at + charstrings.len();
    let fdarray_at = fdselect_at + fdselect.len();
    let private_at = fdarray_at + fdarray_len;
    let top = index(&[top(charstrings_at as i32, fdarray_at as i32, fdselect_at as i32)]);
    let fdarray = index(&vec![font_dict(private_at as i32); count]);
    let cff = [header, names, top, strings, gsubrs, charstrings, fdselect, fdarray].concat();
    let mut font = b"OTTO".to_vec();
    for value in [1u16, 16, 0, 0] {
        font.extend(value.to_be_bytes());
    }
    font.extend(b"CFF ");
    for value in [0u32, 28, cff.len() as u32] {
        font.extend(value.to_be_bytes());
    }
    font.extend(cff);
    font
}

fn main() {
    for count in [255, 256] {
        let data = font(count);
        let remapper = subsetter::GlyphRemapper::new();
        println!("{count} Font DICTs: {:?}", subsetter::subset(&data, 0, &remapper).map(|font| font.len()));
    }
}

cargo run prints 255 Font DICTs: Ok(156) and then panics as above; cargo run --release prints Ok(156) for both.

It was found while reviewing how officewright guards against subsetter panics; until it is fixed, our call to subset turns the panic into an error.

A possible fix: assert subroutines.len() <= 256, or, since get_handler takes a u8, return a CFFError for an FDArray of more than 256 Font DICTs instead of asserting.

Lingua principale
Rust
Stelle
61
Fork
16
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di typst/subsetter

Tutte le issue di typst/subsetter

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.