sdocx 1.0.0

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

use serde::Deserialize;
use sha2::{Digest, Sha256};

use super::TextIndex;

#[derive(Deserialize)]
struct Capture {
    apk_sha256: String,
    text_library_sha256: String,
    base_library_sha256: String,
    memory_fills: [u8; 3],
    entry_constructor: String,
    producer_window: [String; 2],
    range_convention: String,
    cases: Vec<Case>,
}

#[derive(Deserialize)]
struct Case {
    name: String,
    document_utf16: Vec<u16>,
    source_vector_start_utf16: usize,
    source_vector_utf16: Vec<u16>,
    requested_range_utf16: [u32; 2],
    supplied_shaped_glyphs: Vec<SuppliedGlyph>,
    produced_entries: Vec<Entry>,
}

#[derive(Deserialize)]
struct SuppliedGlyph {
    relative_owner_utf16: u32,
    glyph_id: u32,
}

#[derive(Deserialize)]
struct Entry {
    absolute_utf16: u32,
    kind: u32,
    advance: f32,
    font_size: f32,
    ink_rect: [f32; 4],
    cache_drawable: bool,
    glyphs: Vec<Glyph>,
}

#[derive(Deserialize)]
struct Glyph {
    glyph_id: u32,
}

fn checked_capture() -> Capture {
    let bytes = include_bytes!("../../../../conformance/table-text-owner-bases.json");
    assert_eq!(
        format!("{:x}", Sha256::digest(bytes)),
        "504bbe262a6bde8f9db3ecd1f5162039f6ed644cc9d85fe7c766e2e941a390ed"
    );
    let capture: Capture = serde_json::from_slice(bytes).unwrap();
    assert_eq!(
        capture.apk_sha256,
        "daed1eff8c8ee9dfb8afe2771e39e893a8808f3230d6d522a8aa647db09b8667"
    );
    assert_eq!(
        capture.text_library_sha256,
        "5483711673a499743625eb3275e34b37a006919af346212b46b8d8857834308b"
    );
    assert_eq!(
        capture.base_library_sha256,
        "e10da0116946691cf68302437ef261282e1dfe0eec15bf2dfa66093286985deb"
    );
    assert_eq!(capture.memory_fills, [0, 0xa5, 0xff]);
    assert_eq!(capture.entry_constructor, "0x65920");
    assert_eq!(capture.producer_window, ["0x77324", "0x77894"]);
    assert_eq!(capture.range_convention, "half-open UTF-16");
    assert_eq!(capture.cases.len(), 9);
    capture
}

#[test]
fn supplied_relative_utf16_owners_project_to_actual_native_cache_owners() {
    let capture = checked_capture();
    let mut checked_glyphs = 0;
    let mut owned_slots = 0;
    let mut scalar_unit_negative_controls = 0;
    for case in &capture.cases {
        let document = String::from_utf16(&case.document_utf16).unwrap();
        let index = TextIndex::new(&document);
        assert_eq!(
            case.source_vector_utf16,
            case.document_utf16[case.source_vector_start_utf16..],
            "{} source-vector base",
            case.name
        );
        let [start, end] = case.requested_range_utf16;
        let characters = index.utf16_to_char(start).unwrap()..index.utf16_to_char(end).unwrap();
        let requested = index.source(characters.clone()).unwrap();
        let local = TextIndex::new(index.slice(characters).unwrap());
        let mut glyph_ids = BTreeSet::new();
        let mut projected_owners = BTreeSet::new();
        for supplied in &case.supplied_shaped_glyphs {
            assert!(glyph_ids.insert(supplied.glyph_id));
            let local_character = local.utf16_to_char(supplied.relative_owner_utf16).unwrap();
            assert!(local_character < local.len());
            let global_character = requested.characters().start + local_character;
            let source = index
                .source(global_character..global_character + 1)
                .unwrap();
            let relative = source.relative_to(&requested).unwrap();
            assert_eq!(
                relative.utf16().start,
                supplied.relative_owner_utf16,
                "{} relative owner {}",
                case.name,
                supplied.glyph_id
            );
            let native = case
                .produced_entries
                .iter()
                .filter(|entry| {
                    entry
                        .glyphs
                        .iter()
                        .any(|glyph| glyph.glyph_id == supplied.glyph_id)
                })
                .collect::<Vec<_>>();
            assert_eq!(native.len(), 1, "{} native glyph owner", case.name);
            assert_eq!(
                source.utf16().start,
                native[0].absolute_utf16,
                "{} glyph {}",
                case.name,
                supplied.glyph_id
            );
            assert_ne!(source.utf16().start, supplied.relative_owner_utf16);
            assert_ne!(
                native[0].absolute_utf16,
                u32::try_from(case.source_vector_start_utf16).unwrap()
                    + supplied.relative_owner_utf16,
                "{} source-vector base is not the requested owner base",
                case.name
            );
            if u32::try_from(global_character).unwrap() != native[0].absolute_utf16 {
                scalar_unit_negative_controls += 1;
            }
            projected_owners.insert(source.utf16().start);
            checked_glyphs += 1;
        }
        assert_eq!(
            case.produced_entries
                .iter()
                .map(|entry| entry.glyphs.len())
                .sum::<usize>(),
            case.supplied_shaped_glyphs.len(),
            "{} retained glyph count",
            case.name
        );
        owned_slots += projected_owners.len();
    }
    assert_eq!(checked_glyphs, 21);
    assert_eq!(owned_slots, 18);
    assert_eq!(scalar_unit_negative_controls, 19);
}

#[test]
fn captured_continuations_and_source_base_space_keep_bounded_producer_metadata() {
    let capture = checked_capture();
    let mut entries = 0;
    let mut unowned = 0;
    let mut surrogate_continuations = 0;
    let mut spaces = 0;
    for case in &capture.cases {
        let document = String::from_utf16(&case.document_utf16).unwrap();
        let index = TextIndex::new(&document);
        assert_eq!(case.produced_entries.len(), case.document_utf16.len());
        for (offset, entry) in case.produced_entries.iter().enumerate() {
            assert_eq!(usize::try_from(entry.absolute_utf16).unwrap(), offset);
            if entry.glyphs.is_empty() {
                assert_eq!(entry.kind, 3, "{} unowned constructor kind", case.name);
                assert!(
                    !entry.cache_drawable,
                    "{} supplied unowned cache flag",
                    case.name
                );
                assert_eq!(entry.advance.to_bits(), 0);
                assert_eq!(entry.font_size.to_bits(), 0);
                assert_eq!(entry.ink_rect.map(f32::to_bits), [0; 4]);
                unowned += 1;
            } else {
                assert!(
                    entry.cache_drawable,
                    "{} supplied owner cache flag",
                    case.name
                );
                if case.name == "space-source-base" && entry.absolute_utf16 == 4 {
                    assert_eq!(case.source_vector_start_utf16, 3);
                    assert_eq!(case.source_vector_utf16[1], u16::from(b' '));
                    assert_eq!(entry.kind, 1);
                    spaces += 1;
                } else {
                    assert_eq!(entry.kind, 0);
                }
            }
            if index.utf16_to_char(entry.absolute_utf16).is_none() {
                assert!(entry.glyphs.is_empty());
                surrogate_continuations += 1;
            }
            entries += 1;
        }
    }
    assert_eq!(entries, 70);
    assert_eq!(unowned, 52);
    assert_eq!(surrogate_continuations, 9);
    assert_eq!(spaces, 1);
    assert_eq!(entries * capture.memory_fills.len(), 210);
    assert_eq!(capture.cases.len() * capture.memory_fills.len(), 27);
}