sdocx 1.0.0

Reverse-engineered tooling and SDK for converting Samsung Notes (.sdocx) files
Documentation
use super::super::cell_text_bounds;
use super::*;
use serde::Deserialize;
use sha2::{Digest, Sha256};

#[derive(Deserialize)]
struct Capture {
    apk_version: String,
    apk_sha256: String,
    allocation_fills: Vec<u8>,
    model_library_sha256: String,
    drawing_library_sha256: String,
    base_library_sha256: String,
    widget_library_sha256: String,
    text_library_sha256: String,
    layout_cell_address: String,
    object_layout_address: String,
    split_address: String,
    measurement_inputs: String,
    null_and_uncached_cell_delta_bits: u32,
    cases: Vec<Case>,
}

#[derive(Deserialize)]
struct Case {
    name: String,
    rows: usize,
    columns: usize,
    spans: Vec<[u32; 2]>,
    frames: Vec<[f32; 4]>,
    bands: Vec<Vec<[f32; 4]>>,
    measured_heights: Vec<f32>,
    calls: Vec<Call>,
}

#[derive(Deserialize)]
struct Call {
    slot: usize,
    native_dimensions: [f32; 2],
    text_dimensions: [i32; 2],
    rectangles: Vec<[f32; 4]>,
    height_delta_bits: u32,
}

fn rect([x_min, y_min, x_max, y_max]: [f32; 4]) -> BoundingBox {
    BoundingBox {
        x_min: f64::from(x_min),
        y_min: f64::from(y_min),
        x_max: f64::from(x_max),
        y_max: f64::from(y_max),
    }
}

fn bits(bounds: BoundingBox) -> [u32; 4] {
    [bounds.x_min, bounds.y_min, bounds.x_max, bounds.y_max].map(|value| (value as f32).to_bits())
}

#[test]
fn native_cell_frames_bands_and_integer_text_dimensions_match() {
    let bytes = include_bytes!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/../../conformance/table-cell-inputs.json"
    ));
    assert_eq!(
        format!("{:x}", Sha256::digest(bytes)),
        "0e936c9d767d9db6556d62e4298a2aa63435be41e64d8d31d093f93182249020"
    );
    let capture: Capture = serde_json::from_slice(bytes).unwrap();
    assert_eq!(capture.apk_version, "4.4.45.37");
    assert_eq!(
        capture.apk_sha256,
        "daed1eff8c8ee9dfb8afe2771e39e893a8808f3230d6d522a8aa647db09b8667"
    );
    assert_eq!(capture.allocation_fills, [0, 165, 255]);
    assert_eq!(
        capture.model_library_sha256,
        "4fbcf6d4213e929f1535d32abb487743643fd5d0dfc366e50dfeb2e7d8015b7a"
    );
    assert_eq!(
        capture.drawing_library_sha256,
        "788bf413ddeb0b9d352062c5f1b7b8ed11babca911df72691da58ff1a0a5a4bd"
    );
    assert_eq!(
        capture.base_library_sha256,
        "e10da0116946691cf68302437ef261282e1dfe0eec15bf2dfa66093286985deb"
    );
    assert_eq!(
        capture.widget_library_sha256,
        "cfaaccbfd62763f0e514271cc372c0de7b6df41f0d2f991887b8b9584abd1ec9"
    );
    assert_eq!(
        capture.text_library_sha256,
        "5483711673a499743625eb3275e34b37a006919af346212b46b8d8857834308b"
    );
    assert_eq!(capture.layout_cell_address, "0xb06d4");
    assert_eq!(capture.object_layout_address, "0xd3b88");
    assert_eq!(capture.split_address, "0xae794");
    assert_eq!(
        capture.measurement_inputs,
        "supplied cached frames and measured heights; native table and object layout, text shaping and padding assignment intercepted"
    );
    assert_eq!(capture.null_and_uncached_cell_delta_bits, 0);
    assert_eq!(capture.cases.len(), 138);
    let mut calls = 0;
    for case in capture.cases {
        let count = case.rows * case.columns;
        assert_eq!(case.spans.len(), count);
        assert_eq!(case.frames.len(), count);
        assert_eq!(case.bands.len(), count);
        assert_eq!(case.measured_heights.len(), count);
        assert_eq!(case.calls.len(), count);
        for (slot, call) in case.calls.into_iter().enumerate() {
            calls += 1;
            assert_eq!(call.slot, slot);
            let frame = rect(case.frames[slot]);
            let dimensions = [
                native_sub(frame.x_max, frame.x_min).unwrap(),
                native_sub(frame.y_max, frame.y_min).unwrap(),
            ];
            assert_eq!(
                dimensions.map(|value| (value as f32).to_bits()),
                call.native_dimensions.map(f32::to_bits),
                "{}, slot {slot}",
                case.name
            );
            let bounds = cell_text_bounds(frame).unwrap();
            assert_eq!([bounds.x_min, bounds.y_min], [0.0, 0.0]);
            assert_eq!(
                [bounds.x_max, bounds.y_max],
                call.text_dimensions.map(f64::from),
                "{}, slot {slot}",
                case.name
            );
            let local_bands = BandList::new(case.bands[slot].iter().copied().map(rect).collect())
                .unwrap()
                .for_row(frame.y_min)
                .unwrap();
            assert_eq!(
                local_bands
                    .rectangles
                    .into_iter()
                    .map(bits)
                    .collect::<Vec<_>>(),
                call.rectangles
                    .into_iter()
                    .map(|r| r.map(f32::to_bits))
                    .collect::<Vec<_>>(),
                "{}, slot {slot}",
                case.name
            );
            let delta = native_sub(f64::from(case.measured_heights[slot]), dimensions[1]).unwrap();
            assert_eq!(
                (delta as f32).to_bits(),
                call.height_delta_bits,
                "{}, slot {slot}",
                case.name
            );
        }
    }
    assert_eq!(calls, 850);
}