sdocx 1.1.0

Reverse-engineered tooling and SDK for converting Samsung Notes (.sdocx) files
Documentation
use std::borrow::Cow;

use rustybuzz::ttf_parser::{FaceParsingError, RawFace};
use write_fonts::{FontBuilder, types::Tag};

pub(super) fn standalone_face(data: &[u8], index: u32) -> Result<Cow<'_, [u8]>, FaceParsingError> {
    let face = RawFace::parse(data, index)?;
    if !data.starts_with(b"ttcf") {
        return Ok(Cow::Borrowed(data));
    }
    if face.table_records.len() > 4095 {
        return Err(FaceParsingError::MalformedFont);
    }

    let mut builder = FontBuilder::new();
    let mut previous_tag = None;
    let mut ranges = Vec::with_capacity(usize::from(face.table_records.len()));
    let mut rebuilt_length = 12_u64 + u64::from(face.table_records.len()) * 16;
    for record in face.table_records {
        if previous_tag.is_some_and(|previous| previous >= record.tag) {
            return Err(FaceParsingError::MalformedFont);
        }
        previous_tag = Some(record.tag);
        let table = face
            .table(record.tag)
            .ok_or(FaceParsingError::MalformedFont)?;
        let start = usize::try_from(record.offset).map_err(|_| FaceParsingError::MalformedFont)?;
        if !table.is_empty() {
            ranges.push(start..start + table.len());
        }
        if record.tag.to_bytes() != *b"DSIG" {
            rebuilt_length += u64::from(record.length).next_multiple_of(4);
            if rebuilt_length > u64::from(u32::MAX) {
                return Err(FaceParsingError::MalformedFont);
            }
            builder.add_raw(Tag::new(&record.tag.to_bytes()), table);
        }
    }
    ranges.sort_unstable_by_key(|range| range.start);
    if ranges.windows(2).any(|pair| pair[0].end > pair[1].start) {
        return Err(FaceParsingError::MalformedFont);
    }
    Ok(Cow::Owned(builder.build()))
}

#[cfg(test)]
mod tests {
    use super::*;

    mod fixtures {
        include!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/tests/helpers/font_collection.rs"
        ));
    }

    const REGULAR: &[u8] = include_bytes!("../../../assets/fonts/Roboto-Regular.ttf");
    const MONO: &[u8] = include_bytes!("../../../assets/fonts/RobotoMono-Regular.ttf");

    fn checksum(bytes: &[u8]) -> u32 {
        bytes.chunks(4).fold(0_u32, |sum, chunk| {
            let mut word = [0; 4];
            word[..chunk.len()].copy_from_slice(chunk);
            sum.wrapping_add(u32::from_be_bytes(word))
        })
    }

    #[test]
    fn extracted_face_preserves_all_tables_and_repairs_checksums() {
        let collection = fixtures::collection(&[REGULAR, MONO]);
        for (index, original) in [REGULAR, MONO].into_iter().enumerate() {
            let extracted = standalone_face(&collection, index as u32).unwrap();
            assert_eq!(checksum(&extracted), 0xb1b0_afba);
            let source = RawFace::parse(original, 0).unwrap();
            let output = RawFace::parse(&extracted, 0).unwrap();
            for record in source.table_records {
                let tag = record.tag.to_bytes();
                if tag == *b"DSIG" {
                    assert!(output.table(record.tag).is_none());
                    continue;
                }
                let original_table = source.table(record.tag).unwrap();
                let output_table = output.table(record.tag).unwrap();
                if tag == *b"head" {
                    assert_eq!(&original_table[..8], &output_table[..8]);
                    assert_eq!(&original_table[12..], &output_table[12..]);
                } else {
                    assert_eq!(original_table, output_table, "table {tag:?}");
                }
            }
            for record in output.table_records {
                let mut table = output.table(record.tag).unwrap().to_vec();
                if record.tag.to_bytes() == *b"head" {
                    table[8..12].fill(0);
                }
                assert_eq!(checksum(&table), record.check_sum);
            }
        }
    }

    #[test]
    fn standalone_faces_are_borrowed_and_invalid_indices_are_rejected() {
        assert!(matches!(
            standalone_face(REGULAR, 0).unwrap(),
            Cow::Borrowed(_)
        ));
        assert!(standalone_face(REGULAR, 1).is_err());
        assert!(standalone_face(&fixtures::collection(&[REGULAR, MONO]), 2).is_err());
        assert!(standalone_face(b"ttcf", 0).is_err());
    }

    #[test]
    fn collection_extraction_preserves_cff_flavor_and_variation_tables() {
        for outlines in [*b"CFF ", *b"CFF2"] {
            let mut builder = FontBuilder::new();
            let tables = [
                (outlines, &[1, 0, 4, 4][..]),
                (*b"fvar", &[1, 0, 0, 0][..]),
                (*b"avar", &[1, 0, 0, 1][..]),
                (*b"STAT", &[1, 1, 0, 0][..]),
                (*b"xtra", &[7, 8, 9][..]),
            ];
            for (tag, bytes) in tables {
                builder.add_raw(Tag::new(&tag), bytes);
            }
            let original = builder.build();
            let collection = fixtures::collection(&[REGULAR, &original]);
            let extracted = standalone_face(&collection, 1).unwrap();
            assert!(extracted.starts_with(b"OTTO"));
            let output = RawFace::parse(&extracted, 0).unwrap();
            for (tag, bytes) in tables {
                assert_eq!(
                    output.table(rustybuzz::ttf_parser::Tag::from_bytes(&tag)),
                    Some(bytes)
                );
            }
        }
    }
}