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

Abierto Apto para principiantes
#34 1 comentario 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
74/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
rust
Área
tooling

Línea de trabajo

Empieza con subsetter-0.2.6/src/cff/subroutines.rs, especialmente con SubroutineCollection::new y la ruta de get_handler, y luego sigue cómo se representa el recuento de FDArray. Ejecuta el reproducer proporcionado o añade una prueba de regresión para 256 Font DICTs. Se considera terminado cuando una fuente válida con 256 Font DICTs ya no provoca un panic en las compilaciones de depuración y los recuentos no compatibles se gestionan sin panic.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
Rust
Estrellas
61
Forks
16
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de typst/subsetter

Todos los issues de typst/subsetter

Issues similares

Más issues de Rust

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.